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
56// ── Fetch helpers ─────────────────────────────────────────────────────────────
57
58async function get<T>(path: string, params: Record<string, string> = {}): Promise<T> {
59  const search = new URLSearchParams(params).toString()
60  const url = `${BASE}${path}${search ? `?${search}` : ''}`
61  const res = await fetch(url, { credentials: 'include' })
62  if (!res.ok) {
63    const text = await res.text().catch(() => res.statusText)
64    throw new Error(text || res.statusText)
65  }
66  return res.json()
67}
68
69// ── API calls ─────────────────────────────────────────────────────────────────
70
71export function getRefs(): Promise<GitRef[]> {
72  return get('/git/refs')
73}
74
75export function getTree(ref: string, path: string): Promise<GitTreeEntry[]> {
76  return get(`/git/trees/${encodeURIComponent(ref)}`, path ? { path } : {})
77}
78
79export function getBlob(ref: string, path: string): Promise<GitBlob> {
80  return get(`/git/blobs/${encodeURIComponent(ref)}`, { path })
81}
82
83export function getRawUrl(ref: string, path: string): string {
84  return `${BASE}/git/raw/${encodeURIComponent(ref)}/${path}`
85}
86
87export function getCommits(
88  ref: string,
89  opts: { path?: string; limit?: number; after?: string } = {},
90): Promise<GitCommit[]> {
91  const params: Record<string, string> = { ref, limit: String(opts.limit ?? 20) }
92  if (opts.path) params.path = opts.path
93  if (opts.after) params.after = opts.after
94  return get('/git/commits', params)
95}
96
97export function getCommit(sha: string): Promise<GitCommitDetail> {
98  return get(`/git/commits/${sha}`)
99}