| 791afd4 | | | 1 | /** |
| 791afd4 | | | 2 | * BridgeService provides read access to repos via grove-bridge (Mononoke HTTP API). |
| 791afd4 | | | 3 | * Replaces the old GitService that used bare git clones + shell commands. |
| 791afd4 | | | 4 | */ |
| 791afd4 | | | 5 | export class BridgeService { |
| 791afd4 | | | 6 | constructor(private bridgeUrl: string) {} |
| 791afd4 | | | 7 | |
| 791afd4 | | | 8 | async listTree( |
| 791afd4 | | | 9 | _owner: string, |
| 791afd4 | | | 10 | repo: string, |
| 791afd4 | | | 11 | ref: string, |
| 791afd4 | | | 12 | path: string = "" |
| 791afd4 | | | 13 | ): Promise<TreeEntry[]> { |
| 791afd4 | | | 14 | try { |
| 791afd4 | | | 15 | const url = path |
| 791afd4 | | | 16 | ? `${this.bridgeUrl}/repos/${repo}/tree/${ref}/${path}` |
| 791afd4 | | | 17 | : `${this.bridgeUrl}/repos/${repo}/tree/${ref}/`; |
| 791afd4 | | | 18 | const res = await fetch(url); |
| 791afd4 | | | 19 | if (!res.ok) return []; |
| 791afd4 | | | 20 | const data = await res.json(); |
| 791afd4 | | | 21 | return (data.entries ?? []).map((e: any) => ({ |
| 791afd4 | | | 22 | name: e.name, |
| 791afd4 | | | 23 | type: e.type as "blob" | "tree", |
| 791afd4 | | | 24 | mode: e.type === "tree" ? "040000" : "100644", |
| 791afd4 | | | 25 | hash: "", |
| 791afd4 | | | 26 | })); |
| 791afd4 | | | 27 | } catch { |
| 791afd4 | | | 28 | return []; |
| 791afd4 | | | 29 | } |
| 791afd4 | | | 30 | } |
| 791afd4 | | | 31 | |
| 791afd4 | | | 32 | async getBlob( |
| 791afd4 | | | 33 | _owner: string, |
| 791afd4 | | | 34 | repo: string, |
| 791afd4 | | | 35 | ref: string, |
| 791afd4 | | | 36 | path: string |
| 791afd4 | | | 37 | ): Promise<{ content: string; size: number } | null> { |
| 791afd4 | | | 38 | try { |
| 791afd4 | | | 39 | const res = await fetch( |
| 791afd4 | | | 40 | `${this.bridgeUrl}/repos/${repo}/blob/${ref}/${path}` |
| 791afd4 | | | 41 | ); |
| 791afd4 | | | 42 | if (!res.ok) return null; |
| 791afd4 | | | 43 | const data = await res.json(); |
| 791afd4 | | | 44 | return { content: data.content, size: data.size }; |
| 791afd4 | | | 45 | } catch { |
| 791afd4 | | | 46 | return null; |
| 791afd4 | | | 47 | } |
| 791afd4 | | | 48 | } |
| 791afd4 | | | 49 | |
| 791afd4 | | | 50 | async getCommits( |
| 791afd4 | | | 51 | _owner: string, |
| 791afd4 | | | 52 | repo: string, |
| 791afd4 | | | 53 | ref: string, |
| 791afd4 | | | 54 | options: { path?: string; limit?: number; offset?: number } = {} |
| 791afd4 | | | 55 | ): Promise<CommitInfo[]> { |
| 791afd4 | | | 56 | try { |
| 791afd4 | | | 57 | const limit = options.limit ?? 30; |
| 791afd4 | | | 58 | const res = await fetch( |
| 791afd4 | | | 59 | `${this.bridgeUrl}/repos/${repo}/commit/${ref}/history?limit=${limit}` |
| 791afd4 | | | 60 | ); |
| 791afd4 | | | 61 | if (!res.ok) return []; |
| 791afd4 | | | 62 | const data = await res.json(); |
| 791afd4 | | | 63 | const commits = (data.commits ?? []).map((c: any) => ({ |
| 791afd4 | | | 64 | hash: c.hash, |
| 791afd4 | | | 65 | author: c.author, |
| 791afd4 | | | 66 | email: "", |
| 791afd4 | | | 67 | timestamp: c.timestamp, |
| 791afd4 | | | 68 | subject: (c.message ?? "").split("\n")[0], |
| 791afd4 | | | 69 | body: (c.message ?? "").split("\n").slice(1).join("\n").trim(), |
| 791afd4 | | | 70 | parents: c.parents ?? [], |
| 791afd4 | | | 71 | })); |
| 791afd4 | | | 72 | const offset = options.offset ?? 0; |
| 791afd4 | | | 73 | return offset > 0 ? commits.slice(offset) : commits; |
| 791afd4 | | | 74 | } catch { |
| 791afd4 | | | 75 | return []; |
| 791afd4 | | | 76 | } |
| 791afd4 | | | 77 | } |
| 791afd4 | | | 78 | |
| 791afd4 | | | 79 | async getBlame( |
| 791afd4 | | | 80 | _owner: string, |
| 791afd4 | | | 81 | repo: string, |
| 791afd4 | | | 82 | ref: string, |
| 791afd4 | | | 83 | path: string |
| 791afd4 | | | 84 | ): Promise<BlameLine[]> { |
| 791afd4 | | | 85 | try { |
| 791afd4 | | | 86 | const res = await fetch( |
| 791afd4 | | | 87 | `${this.bridgeUrl}/repos/${repo}/blame/${ref}/${path}` |
| 791afd4 | | | 88 | ); |
| 791afd4 | | | 89 | if (!res.ok) return []; |
| 791afd4 | | | 90 | const data = await res.json(); |
| 791afd4 | | | 91 | return (data.blame ?? []).map((b: any) => ({ |
| 791afd4 | | | 92 | hash: b.hash, |
| 791afd4 | | | 93 | originalLine: b.original_line, |
| 791afd4 | | | 94 | author: b.author, |
| 791afd4 | | | 95 | timestamp: b.timestamp, |
| 791afd4 | | | 96 | summary: "", |
| 791afd4 | | | 97 | content: b.content, |
| 791afd4 | | | 98 | })); |
| 791afd4 | | | 99 | } catch { |
| 791afd4 | | | 100 | return []; |
| 791afd4 | | | 101 | } |
| 791afd4 | | | 102 | } |
| 791afd4 | | | 103 | |
| 791afd4 | | | 104 | async getDiff( |
| 791afd4 | | | 105 | _owner: string, |
| 791afd4 | | | 106 | repo: string, |
| 791afd4 | | | 107 | base: string, |
| 791afd4 | | | 108 | head: string |
| 99f1a2e | | | 109 | ): Promise<{ base: string; head: string; diffs: any[] }> { |
| 791afd4 | | | 110 | try { |
| 791afd4 | | | 111 | const res = await fetch( |
| 791afd4 | | | 112 | `${this.bridgeUrl}/repos/${repo}/diff/${base}/${head}` |
| 791afd4 | | | 113 | ); |
| 99f1a2e | | | 114 | if (!res.ok) return { base, head, diffs: [] }; |
| 791afd4 | | | 115 | const data = await res.json(); |
| 99f1a2e | | | 116 | return { base: data.base ?? base, head: data.head ?? head, diffs: data.diffs ?? [] }; |
| 791afd4 | | | 117 | } catch { |
| 99f1a2e | | | 118 | return { base, head, diffs: [] }; |
| 791afd4 | | | 119 | } |
| 791afd4 | | | 120 | } |
| 791afd4 | | | 121 | |
| 791afd4 | | | 122 | async getBranches(_owner: string, repo: string): Promise<BranchInfo[]> { |
| 791afd4 | | | 123 | try { |
| 791afd4 | | | 124 | const res = await fetch( |
| 791afd4 | | | 125 | `${this.bridgeUrl}/repos/${repo}/bookmarks` |
| 791afd4 | | | 126 | ); |
| 791afd4 | | | 127 | if (!res.ok) return []; |
| 791afd4 | | | 128 | const data = await res.json(); |
| 791afd4 | | | 129 | return (data.bookmarks ?? []).map((b: any) => ({ |
| 791afd4 | | | 130 | name: b.name, |
| 791afd4 | | | 131 | hash: b.commit_id, |
| 791afd4 | | | 132 | timestamp: 0, |
| 791afd4 | | | 133 | subject: "", |
| 791afd4 | | | 134 | })); |
| 791afd4 | | | 135 | } catch { |
| 791afd4 | | | 136 | return []; |
| 791afd4 | | | 137 | } |
| 791afd4 | | | 138 | } |
| 791afd4 | | | 139 | |
| 791afd4 | | | 140 | async getReadme( |
| 791afd4 | | | 141 | owner: string, |
| 791afd4 | | | 142 | repo: string, |
| 791afd4 | | | 143 | ref: string |
| 791afd4 | | | 144 | ): Promise<string | null> { |
| 791afd4 | | | 145 | const readmeNames = [ |
| 791afd4 | | | 146 | "README.md", |
| 791afd4 | | | 147 | "README.markdown", |
| 791afd4 | | | 148 | "README.txt", |
| 791afd4 | | | 149 | "README", |
| 791afd4 | | | 150 | "readme.md", |
| 791afd4 | | | 151 | ]; |
| 791afd4 | | | 152 | |
| 791afd4 | | | 153 | for (const name of readmeNames) { |
| 791afd4 | | | 154 | const blob = await this.getBlob(owner, repo, ref, name); |
| 791afd4 | | | 155 | if (blob) return blob.content; |
| 791afd4 | | | 156 | } |
| 791afd4 | | | 157 | return null; |
| 791afd4 | | | 158 | } |
| 791afd4 | | | 159 | } |
| 791afd4 | | | 160 | |
| 791afd4 | | | 161 | // Types |
| 791afd4 | | | 162 | |
| 791afd4 | | | 163 | export interface TreeEntry { |
| 791afd4 | | | 164 | mode: string; |
| 791afd4 | | | 165 | type: "blob" | "tree"; |
| 791afd4 | | | 166 | hash: string; |
| 791afd4 | | | 167 | name: string; |
| 791afd4 | | | 168 | } |
| 791afd4 | | | 169 | |
| 791afd4 | | | 170 | export interface CommitInfo { |
| 791afd4 | | | 171 | hash: string; |
| 791afd4 | | | 172 | author: string; |
| 791afd4 | | | 173 | email: string; |
| 791afd4 | | | 174 | timestamp: number; |
| 791afd4 | | | 175 | subject: string; |
| 791afd4 | | | 176 | body: string; |
| 791afd4 | | | 177 | parents: string[]; |
| 791afd4 | | | 178 | } |
| 791afd4 | | | 179 | |
| 791afd4 | | | 180 | export interface BlameLine { |
| 791afd4 | | | 181 | hash: string; |
| 791afd4 | | | 182 | originalLine: number; |
| 791afd4 | | | 183 | author: string; |
| 791afd4 | | | 184 | timestamp: number; |
| 791afd4 | | | 185 | summary: string; |
| 791afd4 | | | 186 | content: string; |
| 791afd4 | | | 187 | } |
| 791afd4 | | | 188 | |
| 791afd4 | | | 189 | export interface BranchInfo { |
| 791afd4 | | | 190 | name: string; |
| 791afd4 | | | 191 | hash: string; |
| 791afd4 | | | 192 | timestamp: number; |
| 791afd4 | | | 193 | subject: string; |
| 791afd4 | | | 194 | } |