History

fetch in background

Julius Marminge committed 9 months ago

Commit 724ecbe1 parent 067c5c72 files · +75 −11Browse files

Unified · Split

2 files
  1. apps/server/src/git.test.ts +73 −6
  2. apps/server/src/git.ts +2 −5
apps/server/src/git.test.ts+73−6
@@ -272,11 +272,12 @@ describe("git integration", () => { · 271 unmodified lines
272 272
273 await checkoutGitBranch({ cwd: source.path, branch: featureBranch });273 await checkoutGitBranch({ cwd: source.path, branch: featureBranch });
274 const core = new GitCoreService();274 const core = new GitCoreService();
275 const details = await core.statusDetails(source.path);275 await vi.waitFor(async () => {
276 276 const details = await core.statusDetails(source.path);
277 expect(details.branch).toBe(featureBranch);277 expect(details.branch).toBe(featureBranch);
278 expect(details.aheadCount).toBe(0);278 expect(details.aheadCount).toBe(0);
279 expect(details.behindCount).toBe(1);279 expect(details.behindCount).toBe(1);
280 });
280 });281 });
281 282
282 it("keeps checkout successful when upstream refresh fails", async () => {283 it("keeps checkout successful when upstream refresh fails", async () => {
@@ -320,7 +321,9 @@ describe("git integration", () => { · 37 unmodified lines
320 await expect(core.checkoutBranch({ cwd: source.path, branch: featureBranch })).resolves.toBe(321 await expect(core.checkoutBranch({ cwd: source.path, branch: featureBranch })).resolves.toBe(
321 undefined,322 undefined,
322 );323 );
323 expect(core.refreshFetchAttempts).toBe(1);324 await vi.waitFor(() => {
325 expect(core.refreshFetchAttempts).toBe(1);
326 });
324 expect(await git(source.path, "branch --show-current")).toBe(featureBranch);327 expect(await git(source.path, "branch --show-current")).toBe(featureBranch);
325 });328 });
326 329
@@ -362,6 +365,9 @@ describe("git integration", () => { · 35 unmodified lines
362 365
363 const core = new FetchRecordingGitCoreService();366 const core = new FetchRecordingGitCoreService();
364 await core.checkoutBranch({ cwd: source.path, branch: featureBranch });367 await core.checkoutBranch({ cwd: source.path, branch: featureBranch });
368 await vi.waitFor(() => {
369 expect(core.fetchArgs).not.toBeNull();
370 });
365 371
366 expect(await git(source.path, "branch --show-current")).toBe(featureBranch);372 expect(await git(source.path, "branch --show-current")).toBe(featureBranch);
367 expect(core.fetchArgs).toEqual([373 expect(core.fetchArgs).toEqual([
@@ -373,6 +379,67 @@ describe("git integration", () => { · 5 unmodified lines
373 ]);379 ]);
374 });380 });
375 381
382 it("returns checkout result before background upstream refresh completes", async () => {
383 await using remote = await makeTmpDir();
384 await using source = await makeTmpDir();
385 await git(remote.path, "init --bare");
386
387 await initRepoWithCommit(source.path);
388 const defaultBranch = (await listGitBranches({ cwd: source.path })).branches.find(
389 (branch) => branch.current,
390 )!.name;
391 await git(source.path, `remote add origin ${JSON.stringify(remote.path)}`);
392 await git(source.path, `push -u origin ${defaultBranch}`);
393
394 const featureBranch = "feature/background-refresh";
395 await git(source.path, `checkout -b ${featureBranch}`);
396 await writeFile(path.join(source.path, "feature.txt"), "feature base\n");
397 await git(source.path, "add feature.txt");
398 await git(source.path, "commit -m 'feature base'");
399 await git(source.path, `push -u origin ${featureBranch}`);
400 await git(source.path, `checkout ${defaultBranch}`);
401
402 class SlowRefreshGitCoreService extends GitCoreService {
403 fetchStarted = false;
404 private readonly waitForReleasePromise: Promise<void>;
405 private releaseWait: () => void = () => undefined;
406
407 constructor() {
408 super();
409 this.waitForReleasePromise = new Promise<void>((resolve) => {
410 this.releaseWait = resolve;
411 });
412 }
413
414 releaseFetch(): void {
415 this.releaseWait();
416 }
417
418 override async git(
419 cwd: string,
420 args: readonly string[],
421 allowNonZeroExit = false,
422 ): Promise<void> {
423 if (args[0] === "fetch") {
424 this.fetchStarted = true;
425 await this.waitForReleasePromise;
426 return;
427 }
428 await super.git(cwd, args, allowNonZeroExit);
429 }
430 }
431
432 const core = new SlowRefreshGitCoreService();
433 await expect(core.checkoutBranch({ cwd: source.path, branch: featureBranch })).resolves.toBe(
434 undefined,
435 );
436 await vi.waitFor(() => {
437 expect(core.fetchStarted).toBe(true);
438 });
439 expect(await git(source.path, "branch --show-current")).toBe(featureBranch);
440 core.releaseFetch();
441 });
442
376 it("throws when branch does not exist", async () => {443 it("throws when branch does not exist", async () => {
377 await using tmp = await makeTmpDir();444 await using tmp = await makeTmpDir();
378 await initRepoWithCommit(tmp.path);445 await initRepoWithCommit(tmp.path);
apps/server/src/git.ts+2−5
@@ -587,11 +587,8 @@ export class GitCoreService { · 586 unmodified lines
587 timeoutMs: 10_000,587 timeoutMs: 10_000,
588 fallbackErrorMessage: "git checkout failed",588 fallbackErrorMessage: "git checkout failed",
589 });589 });
590 try {590 // Refresh upstream refs in the background so checkout remains responsive.
591 await this.refreshCheckedOutBranchUpstream(input.cwd);591 void this.refreshCheckedOutBranchUpstream(input.cwd).catch(() => undefined);
592 } catch {
593 // Best effort: checkout already succeeded, so avoid surfacing refresh failures.
594 }
595 }592 }
596 593
597 async initRepo(input: GitInitInput): Promise<void> {594 async initRepo(input: GitInitInput): Promise<void> {