diff --git a/packages/server/src/server/checkout-diff-manager.test.ts b/packages/server/src/server/checkout-diff-manager.test.ts index bfbd4fd63..a14f0d386 100644 --- a/packages/server/src/server/checkout-diff-manager.test.ts +++ b/packages/server/src/server/checkout-diff-manager.test.ts @@ -115,6 +115,7 @@ describe("CheckoutDiffManager", () => { expect(workspaceGitService.getCheckoutDiff).toHaveBeenCalledWith( "/tmp/repo", expect.objectContaining({ mode: "uncommitted", includeStructured: true }), + undefined, ); }); @@ -157,6 +158,49 @@ describe("CheckoutDiffManager", () => { }); }); + test("watch-triggered refresh forces a cache bypass on getCheckoutDiff", async () => { + const getCheckoutDiff = vi + .fn() + .mockResolvedValueOnce({ + diff: "", + structured: [{ path: "a.ts", additions: 1, deletions: 0, status: "modified" }], + }) + .mockResolvedValueOnce({ + diff: "", + structured: [{ path: "b.ts", additions: 2, deletions: 0, status: "modified" }], + }); + + const { manager, getOnChange } = createManager({ + getCheckoutDiffImplementation: getCheckoutDiff, + }); + + await manager.subscribe( + { + cwd: "/tmp/repo/packages/server", + compare: { mode: "uncommitted" }, + }, + vi.fn(), + ); + + expect(getCheckoutDiff).toHaveBeenNthCalledWith( + 1, + "/tmp/repo", + expect.objectContaining({ mode: "uncommitted" }), + undefined, + ); + + const onChange = getOnChange(); + onChange?.(); + await vi.advanceTimersByTimeAsync(150); + + expect(getCheckoutDiff).toHaveBeenCalledTimes(2); + const watchFiredCall = getCheckoutDiff.mock.calls[1]; + expect(watchFiredCall[2]).toEqual({ + force: true, + reason: expect.stringContaining("working-tree"), + }); + }); + test("falls back to cwd when the working tree watch returns no repo root", async () => { const { manager, workspaceGitService } = createManager({ repoRoot: null }); @@ -171,6 +215,7 @@ describe("CheckoutDiffManager", () => { expect(workspaceGitService.getCheckoutDiff).toHaveBeenCalledWith( "/tmp/plain", expect.objectContaining({ mode: "uncommitted", includeStructured: true }), + undefined, ); }); }); diff --git a/packages/server/src/server/checkout-diff-manager.ts b/packages/server/src/server/checkout-diff-manager.ts index 8f361ee2f..64a45c60e 100644 --- a/packages/server/src/server/checkout-diff-manager.ts +++ b/packages/server/src/server/checkout-diff-manager.ts @@ -164,16 +164,22 @@ export class CheckoutDiffManager { private async computeCheckoutDiffSnapshot( cwd: string, compare: CheckoutDiffCompareInput, - options?: { diffCwd?: string }, + options?: { diffCwd?: string; force?: boolean; reason?: string }, ): Promise { const diffCwd = options?.diffCwd ?? cwd; try { - const diffResult = await this.workspaceGitService.getCheckoutDiff(diffCwd, { - mode: compare.mode, - baseRef: compare.baseRef, - ignoreWhitespace: compare.ignoreWhitespace, - includeStructured: true, - }); + const diffResult = await this.workspaceGitService.getCheckoutDiff( + diffCwd, + { + mode: compare.mode, + baseRef: compare.baseRef, + ignoreWhitespace: compare.ignoreWhitespace, + includeStructured: true, + }, + options?.force + ? { force: true, reason: options.reason ?? "checkout-diff-refresh" } + : undefined, + ); const files = [...(diffResult.structured ?? [])]; files.sort((a, b) => { if (a.path === b.path) return 0; @@ -204,6 +210,8 @@ export class CheckoutDiffManager { target.refreshQueued = false; const snapshot = await this.computeCheckoutDiffSnapshot(target.cwd, target.compare, { diffCwd: target.diffCwd, + force: true, + reason: "working-tree-watch", }); target.latestPayload = snapshot; const fingerprint = JSON.stringify(snapshot);