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}