diff --git a/apps/server/src/git.test.ts b/apps/server/src/git.test.ts index 9635998ce07f22d7c8a05e2903b7aa235bddfb88..075207c6e34bf871b88269470dab77f0b4af097a 100644 --- a/apps/server/src/git.test.ts +++ b/apps/server/src/git.test.ts @@ -279,6 +279,51 @@ describe("git integration", () => { expect(details.behindCount).toBe(1); }); + it("keeps checkout successful when upstream refresh fails", async () => { + await using remote = await makeTmpDir(); + await using source = await makeTmpDir(); + await git(remote.path, "init --bare"); + + await initRepoWithCommit(source.path); + const defaultBranch = (await listGitBranches({ cwd: source.path })).branches.find( + (branch) => branch.current, + )!.name; + await git(source.path, `remote add origin ${JSON.stringify(remote.path)}`); + await git(source.path, `push -u origin ${defaultBranch}`); + + const featureBranch = "feature-refresh-failure"; + await git(source.path, `branch ${featureBranch}`); + await git(source.path, `checkout ${featureBranch}`); + await writeFile(path.join(source.path, "feature.txt"), "feature base\n"); + await git(source.path, "add feature.txt"); + await git(source.path, "commit -m 'feature base'"); + await git(source.path, `push -u origin ${featureBranch}`); + await git(source.path, `checkout ${defaultBranch}`); + + class RefreshFailingGitCoreService extends GitCoreService { + refreshFetchAttempts = 0; + + override async git( + cwd: string, + args: readonly string[], + allowNonZeroExit = false, + ): Promise { + if (args[0] === "fetch") { + this.refreshFetchAttempts += 1; + throw new Error("simulated fetch timeout"); + } + await super.git(cwd, args, allowNonZeroExit); + } + } + + const core = new RefreshFailingGitCoreService(); + await expect(core.checkoutBranch({ cwd: source.path, branch: featureBranch })).resolves.toBe( + undefined, + ); + expect(core.refreshFetchAttempts).toBe(1); + expect(await git(source.path, "branch --show-current")).toBe(featureBranch); + }); + it("throws when branch does not exist", async () => { await using tmp = await makeTmpDir(); await initRepoWithCommit(tmp.path); diff --git a/apps/server/src/git.ts b/apps/server/src/git.ts index 31f85bf02178c9e4a314e3d00b1c8405ff4ce926..1e310952611c0f24514b73f1e57ed4320eb383fb 100644 --- a/apps/server/src/git.ts +++ b/apps/server/src/git.ts @@ -574,10 +574,7 @@ export class GitCoreService { return; } - await executeGit(cwd, ["fetch", "--quiet", "--no-tags", remoteName], { - timeoutMs: 20_000, - allowNonZeroExit: true, - }); + await this.git(cwd, ["fetch", "--quiet", "--no-tags", remoteName], true); } async checkoutBranch(input: GitCheckoutInput): Promise { @@ -585,7 +582,11 @@ export class GitCoreService { timeoutMs: 10_000, fallbackErrorMessage: "git checkout failed", }); - await this.refreshCheckedOutBranchUpstream(input.cwd); + try { + await this.refreshCheckedOutBranchUpstream(input.cwd); + } catch { + // Best effort: checkout already succeeded, so avoid surfacing refresh failures. + } } async initRepo(input: GitInitInput): Promise {