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}