gitApi.ts

  1// REST API client for git repository browsing.
  2// Endpoints are served by the Go backend under /api/repos/{owner}/{repo}/git/*.
  3// "_" is the wildcard value for both owner and repo (resolves to local / default).
  4
  5const BASE = '/api/repos/_/_'
  6
  7export interface GitRef {
  8  name: string       // full ref: "refs/heads/main"
  9  shortName: string  // "main"
 10  type: 'branch' | 'tag'
 11  hash: string
 12  isDefault: boolean
 13}
 14
 15export interface GitTreeEntry {
 16  name: string
 17  type: 'tree' | 'blob'
 18  hash: string
 19  mode: string
 20  // Last commit touching this entry (may be absent if expensive to compute)
 21  lastCommit?: {
 22    hash: string
 23    shortHash: string
 24    message: string
 25    authorName: string
 26    date: string
 27  }
 28}
 29
 30export interface GitBlob {
 31  path: string
 32  content: string   // UTF-8 text; empty string when isBinary is true
 33  size: number
 34  isBinary: boolean
 35}
 36
 37export interface GitCommit {
 38  hash: string
 39  shortHash: string
 40  message: string
 41  authorName: string
 42  authorEmail: string
 43  date: string
 44  parents: string[]
 45}
 46
 47export interface GitCommitDetail extends GitCommit {
 48  fullMessage: string
 49  files: Array<{
 50    path: string
 51    oldPath?: string
 52    status: 'added' | 'modified' | 'deleted' | 'renamed'
 53  }>
 54}
 55
 56export interface DiffLine {
 57  type: 'context' | 'added' | 'deleted'
 58  content: string
 59  oldLine: number
 60  newLine: number
 61}
 62
 63export interface DiffHunk {
 64  oldStart: number
 65  oldLines: number
 66  newStart: number
 67  newLines: number
 68  lines: DiffLine[]
 69}
 70
 71export interface FileDiff {
 72  path: string
 73  oldPath?: string
 74  isBinary: boolean
 75  isNew: boolean
 76  isDelete: boolean
 77  hunks: DiffHunk[]
 78}
 79
 80// ── Fetch helpers ─────────────────────────────────────────────────────────────
 81
 82async function get<T>(path: string, params: Record<string, string> = {}): Promise<T> {
 83  const search = new URLSearchParams(params).toString()
 84  const url = `${BASE}${path}${search ? `?${search}` : ''}`
 85  const res = await fetch(url, { credentials: 'include' })
 86  if (!res.ok) {
 87    const text = await res.text().catch(() => res.statusText)
 88    throw new Error(text || res.statusText)
 89  }
 90  return res.json()
 91}
 92
 93// ── API calls ─────────────────────────────────────────────────────────────────
 94
 95export function getRefs(): Promise<GitRef[]> {
 96  return get('/git/refs')
 97}
 98
 99export function getTree(ref: string, path: string): Promise<GitTreeEntry[]> {
100  return get(`/git/trees/${encodeURIComponent(ref)}`, path ? { path } : {})
101}
102
103export function getBlob(ref: string, path: string): Promise<GitBlob> {
104  return get(`/git/blobs/${encodeURIComponent(ref)}`, { path })
105}
106
107export function getRawUrl(ref: string, path: string): string {
108  return `${BASE}/git/raw/${encodeURIComponent(ref)}/${path}`
109}
110
111export function getCommits(
112  ref: string,
113  opts: { path?: string; limit?: number; after?: string } = {},
114): Promise<GitCommit[]> {
115  const params: Record<string, string> = { ref, limit: String(opts.limit ?? 20) }
116  if (opts.path) params.path = opts.path
117  if (opts.after) params.after = opts.after
118  return get('/git/commits', params)
119}
120
121export function getCommit(sha: string): Promise<GitCommitDetail> {
122  return get(`/git/commits/${sha}`)
123}
124
125export function getCommitDiff(sha: string, path: string): Promise<FileDiff> {
126  return get(`/git/commits/${sha}/diff`, { path })
127}