- 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
Unified · Split
| @@ -1295,5 +1295,54 @@ it.layer(TestLayer)("git integration", (it) => { · 1294 unmodified lines | |||
| 1295 | expect(didFailRecency).toBe(true); | 1295 | expect(didFailRecency).toBe(true); |
| 1296 | }), | 1296 | }), |
| 1297 | ); | 1297 | ); |
| 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 | ); | ||
| 1298 | }); | 1347 | }); |
| 1299 | }); | 1348 | }); |
| @@ -664,6 +664,38 @@ const makeGitCore = Effect.gen(function* () { · 663 unmodified lines | |||
| 664 | ); | 664 | ); |
| 665 | } | 665 | } |
| 666 | 666 | ||
| 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 | |||
| 667 | const [defaultRef, worktreeList, remoteBranchResult, remoteNamesResult, branchLastCommit] = | 699 | const [defaultRef, worktreeList, remoteBranchResult, remoteNamesResult, branchLastCommit] = |
| 668 | yield* Effect.all( | 700 | yield* Effect.all( |
| 669 | [ | 701 | [ |
| @@ -685,25 +717,24 @@ const makeGitCore = Effect.gen(function* () { · 15 unmodified lines | |||
| 685 | allowNonZeroExit: true, | 717 | allowNonZeroExit: true, |
| 686 | }, | 718 | }, |
| 687 | ), | 719 | ), |
| 688 | executeGit( | 720 | remoteBranchResultEffect, |
| 689 | "GitCore.listBranches.remoteBranches", | 721 | remoteNamesResultEffect, |
| 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 | }), | ||
| 701 | branchRecencyPromise, | 722 | branchRecencyPromise, |
| 702 | ], | 723 | ], |
| 703 | { concurrency: "unbounded" }, | 724 | { concurrency: "unbounded" }, |
| 704 | ); | 725 | ); |
| 705 | 726 | ||
| 706 | const remoteNames = remoteNamesResult.code === 0 ? parseRemoteNames(remoteNamesResult.stdout) : []; | 727 | 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 | } | ||
| 707 | 738 | ||
| 708 | const defaultBranch = | 739 | const defaultBranch = |
| 709 | defaultRef.code === 0 | 740 | defaultRef.code === 0 |