History

Handle remote lookup failures in branch listing

- Fall back to empty remote branch and remote name data when git remote queries fail - Log warnings instead of failing `listBranches` - Add integration test covering remote lookup failure fallback

Julius Marminge committed 8 months ago

Commit 16fed121 parent 29825f22 files · +93 −13Browse files

Unified · Split

2 files
  1. apps/server/src/git/Layers/GitCore.test.ts +49 −0
  2. apps/server/src/git/Layers/GitCore.ts +44 −13
apps/server/src/git/Layers/GitCore.test.ts+49−0
@@ -1295,5 +1295,54 @@ it.layer(TestLayer)("git integration", (it) => { · 1294 unmodified lines
12951295 expect(didFailRecency).toBe(true);
12961296 }),
12971297 );
1298+
1299+ it.effect("falls back to empty remote branch data when remote lookups fail", () =>
1300+ Effect.gen(function* () {
1301+ const tmp = yield* makeTmpDir();
1302+ const remote = yield* makeTmpDir();
1303+ yield* initRepoWithCommit(tmp);
1304+ yield* git(remote, ["init", "--bare"]);
1305+ yield* git(tmp, ["remote", "add", "origin", remote]);
1306+
1307+ const realGitService = yield* GitService;
1308+ let didFailRemoteBranches = false;
1309+ let didFailRemoteNames = false;
1310+ const core = yield* makeIsolatedGitCore({
1311+ execute: (input) => {
1312+ if (input.args.join(" ") === "branch --no-color --remotes") {
1313+ didFailRemoteBranches = true;
1314+ return Effect.fail(
1315+ new GitCommandError({
1316+ operation: "git.test.listBranchesRemoteBranches",
1317+ command: `git ${input.args.join(" ")}`,
1318+ cwd: input.cwd,
1319+ detail: "remote unavailable",
1320+ }),
1321+ );
1322+ }
1323+ if (input.args.join(" ") === "remote") {
1324+ didFailRemoteNames = true;
1325+ return Effect.fail(
1326+ new GitCommandError({
1327+ operation: "git.test.listBranchesRemoteNames",
1328+ command: `git ${input.args.join(" ")}`,
1329+ cwd: input.cwd,
1330+ detail: "remote unavailable",
1331+ }),
1332+ );
1333+ }
1334+ return realGitService.execute(input);
1335+ },
1336+ });
1337+
1338+ const result = yield* core.listBranches({ cwd: tmp });
1339+
1340+ expect(result.isRepo).toBe(true);
1341+ expect(result.branches.length).toBeGreaterThan(0);
1342+ expect(result.branches.every((branch) => !branch.isRemote)).toBe(true);
1343+ expect(didFailRemoteBranches).toBe(true);
1344+ expect(didFailRemoteNames).toBe(true);
1345+ }),
1346+ );
12981347 });
12991348 });
apps/server/src/git/Layers/GitCore.ts+44−13
@@ -664,6 +664,38 @@ const makeGitCore = Effect.gen(function* () { · 663 unmodified lines
664664 );
665665 }
666666
667+ const remoteBranchResultEffect = executeGit(
668+ "GitCore.listBranches.remoteBranches",
669+ input.cwd,
670+ ["branch", "--no-color", "--remotes"],
671+ {
672+ timeoutMs: 10_000,
673+ allowNonZeroExit: true,
674+ },
675+ ).pipe(
676+ Effect.catch((error) =>
677+ Effect.logWarning(
678+ `GitCore.listBranches: remote branch lookup failed for ${input.cwd}: ${error.message}. Falling back to an empty remote branch list.`,
679+ ).pipe(Effect.as({ code: 1, stdout: "", stderr: "" })),
680+ ),
681+ );
682+
683+ const remoteNamesResultEffect = executeGit(
684+ "GitCore.listBranches.remoteNames",
685+ input.cwd,
686+ ["remote"],
687+ {
688+ timeoutMs: 5_000,
689+ allowNonZeroExit: true,
690+ },
691+ ).pipe(
692+ Effect.catch((error) =>
693+ Effect.logWarning(
694+ `GitCore.listBranches: remote name lookup failed for ${input.cwd}: ${error.message}. Falling back to an empty remote name list.`,
695+ ).pipe(Effect.as({ code: 1, stdout: "", stderr: "" })),
696+ ),
697+ );
698+
667699 const [defaultRef, worktreeList, remoteBranchResult, remoteNamesResult, branchLastCommit] =
668700 yield* Effect.all(
669701 [
@@ -685,25 +717,24 @@ const makeGitCore = Effect.gen(function* () { · 15 unmodified lines
685717 allowNonZeroExit: true,
686718 },
687719 ),
688− executeGit(
689− "GitCore.listBranches.remoteBranches",
690− input.cwd,
691− ["branch", "--no-color", "--remotes"],
692− {
693− timeoutMs: 10_000,
694− allowNonZeroExit: true,
695− },
696− ),
697− executeGit("GitCore.listBranches.remoteNames", input.cwd, ["remote"], {
698− timeoutMs: 5_000,
699− allowNonZeroExit: true,
700− }),
720+ remoteBranchResultEffect,
721+ remoteNamesResultEffect,
701722 branchRecencyPromise,
702723 ],
703724 { concurrency: "unbounded" },
704725 );
705726
706727 const remoteNames = remoteNamesResult.code === 0 ? parseRemoteNames(remoteNamesResult.stdout) : [];
728+ if (remoteBranchResult.code !== 0 && remoteBranchResult.stderr.trim().length > 0) {
729+ yield* Effect.logWarning(
730+ `GitCore.listBranches: remote branch lookup returned code ${remoteBranchResult.code} for ${input.cwd}: ${remoteBranchResult.stderr.trim()}. Falling back to an empty remote branch list.`,
731+ );
732+ }
733+ if (remoteNamesResult.code !== 0 && remoteNamesResult.stderr.trim().length > 0) {
734+ yield* Effect.logWarning(
735+ `GitCore.listBranches: remote name lookup returned code ${remoteNamesResult.code} for ${input.cwd}: ${remoteNamesResult.stderr.trim()}. Falling back to an empty remote name list.`,
736+ );
737+ }
707738
708739 const defaultBranch =
709740 defaultRef.code === 0