api/src/services/bridge.tsblame
View source
791afd41/**
791afd42 * BridgeService provides read access to repos via grove-bridge (Mononoke HTTP API).
791afd43 * Replaces the old GitService that used bare git clones + shell commands.
791afd44 */
791afd45export class BridgeService {
791afd46 constructor(private bridgeUrl: string) {}
791afd47
791afd48 async listTree(
791afd49 _owner: string,
791afd410 repo: string,
791afd411 ref: string,
791afd412 path: string = ""
791afd413 ): Promise<TreeEntry[]> {
791afd414 try {
791afd415 const url = path
791afd416 ? `${this.bridgeUrl}/repos/${repo}/tree/${ref}/${path}`
791afd417 : `${this.bridgeUrl}/repos/${repo}/tree/${ref}/`;
791afd418 const res = await fetch(url);
791afd419 if (!res.ok) return [];
791afd420 const data = await res.json();
791afd421 return (data.entries ?? []).map((e: any) => ({
791afd422 name: e.name,
791afd423 type: e.type as "blob" | "tree",
791afd424 mode: e.type === "tree" ? "040000" : "100644",
791afd425 hash: "",
791afd426 }));
791afd427 } catch {
791afd428 return [];
791afd429 }
791afd430 }
791afd431
791afd432 async getBlob(
791afd433 _owner: string,
791afd434 repo: string,
791afd435 ref: string,
791afd436 path: string
791afd437 ): Promise<{ content: string; size: number } | null> {
791afd438 try {
791afd439 const res = await fetch(
791afd440 `${this.bridgeUrl}/repos/${repo}/blob/${ref}/${path}`
791afd441 );
791afd442 if (!res.ok) return null;
791afd443 const data = await res.json();
791afd444 return { content: data.content, size: data.size };
791afd445 } catch {
791afd446 return null;
791afd447 }
791afd448 }
791afd449
791afd450 async getCommits(
791afd451 _owner: string,
791afd452 repo: string,
791afd453 ref: string,
791afd454 options: { path?: string; limit?: number; offset?: number } = {}
791afd455 ): Promise<CommitInfo[]> {
791afd456 try {
791afd457 const limit = options.limit ?? 30;
791afd458 const res = await fetch(
791afd459 `${this.bridgeUrl}/repos/${repo}/commit/${ref}/history?limit=${limit}`
791afd460 );
791afd461 if (!res.ok) return [];
791afd462 const data = await res.json();
791afd463 const commits = (data.commits ?? []).map((c: any) => ({
791afd464 hash: c.hash,
791afd465 author: c.author,
791afd466 email: "",
791afd467 timestamp: c.timestamp,
791afd468 subject: (c.message ?? "").split("\n")[0],
791afd469 body: (c.message ?? "").split("\n").slice(1).join("\n").trim(),
791afd470 parents: c.parents ?? [],
791afd471 }));
791afd472 const offset = options.offset ?? 0;
791afd473 return offset > 0 ? commits.slice(offset) : commits;
791afd474 } catch {
791afd475 return [];
791afd476 }
791afd477 }
791afd478
791afd479 async getBlame(
791afd480 _owner: string,
791afd481 repo: string,
791afd482 ref: string,
791afd483 path: string
791afd484 ): Promise<BlameLine[]> {
791afd485 try {
791afd486 const res = await fetch(
791afd487 `${this.bridgeUrl}/repos/${repo}/blame/${ref}/${path}`
791afd488 );
791afd489 if (!res.ok) return [];
791afd490 const data = await res.json();
791afd491 return (data.blame ?? []).map((b: any) => ({
791afd492 hash: b.hash,
791afd493 originalLine: b.original_line,
791afd494 author: b.author,
791afd495 timestamp: b.timestamp,
791afd496 summary: "",
791afd497 content: b.content,
791afd498 }));
791afd499 } catch {
791afd4100 return [];
791afd4101 }
791afd4102 }
791afd4103
791afd4104 async getDiff(
791afd4105 _owner: string,
791afd4106 repo: string,
791afd4107 base: string,
791afd4108 head: string
99f1a2e109 ): Promise<{ base: string; head: string; diffs: any[] }> {
791afd4110 try {
791afd4111 const res = await fetch(
791afd4112 `${this.bridgeUrl}/repos/${repo}/diff/${base}/${head}`
791afd4113 );
99f1a2e114 if (!res.ok) return { base, head, diffs: [] };
791afd4115 const data = await res.json();
99f1a2e116 return { base: data.base ?? base, head: data.head ?? head, diffs: data.diffs ?? [] };
791afd4117 } catch {
99f1a2e118 return { base, head, diffs: [] };
791afd4119 }
791afd4120 }
791afd4121
791afd4122 async getBranches(_owner: string, repo: string): Promise<BranchInfo[]> {
791afd4123 try {
791afd4124 const res = await fetch(
791afd4125 `${this.bridgeUrl}/repos/${repo}/bookmarks`
791afd4126 );
791afd4127 if (!res.ok) return [];
791afd4128 const data = await res.json();
791afd4129 return (data.bookmarks ?? []).map((b: any) => ({
791afd4130 name: b.name,
791afd4131 hash: b.commit_id,
791afd4132 timestamp: 0,
791afd4133 subject: "",
791afd4134 }));
791afd4135 } catch {
791afd4136 return [];
791afd4137 }
791afd4138 }
791afd4139
791afd4140 async getReadme(
791afd4141 owner: string,
791afd4142 repo: string,
791afd4143 ref: string
791afd4144 ): Promise<string | null> {
791afd4145 const readmeNames = [
791afd4146 "README.md",
791afd4147 "README.markdown",
791afd4148 "README.txt",
791afd4149 "README",
791afd4150 "readme.md",
791afd4151 ];
791afd4152
791afd4153 for (const name of readmeNames) {
791afd4154 const blob = await this.getBlob(owner, repo, ref, name);
791afd4155 if (blob) return blob.content;
791afd4156 }
791afd4157 return null;
791afd4158 }
791afd4159}
791afd4160
791afd4161// Types
791afd4162
791afd4163export interface TreeEntry {
791afd4164 mode: string;
791afd4165 type: "blob" | "tree";
791afd4166 hash: string;
791afd4167 name: string;
791afd4168}
791afd4169
791afd4170export interface CommitInfo {
791afd4171 hash: string;
791afd4172 author: string;
791afd4173 email: string;
791afd4174 timestamp: number;
791afd4175 subject: string;
791afd4176 body: string;
791afd4177 parents: string[];
791afd4178}
791afd4179
791afd4180export interface BlameLine {
791afd4181 hash: string;
791afd4182 originalLine: number;
791afd4183 author: string;
791afd4184 timestamp: number;
791afd4185 summary: string;
791afd4186 content: string;
791afd4187}
791afd4188
791afd4189export interface BranchInfo {
791afd4190 name: string;
791afd4191 hash: string;
791afd4192 timestamp: number;
791afd4193 subject: string;
791afd4194}