fix(server): bypass diff cache on working-tree watch refresh
CheckoutDiffManager.refreshTarget was calling workspaceGitService.getCheckoutDiff
without forcing a cache bypass, so watch-fired refreshes within the 15s consumer
TTL returned the pre-mutation cached diff — fingerprint matched, no
checkout_diff_update was emitted, and the sidebar diff appeared frozen while an
agent edited files. Route watch-fired refreshes through the service with
{ force: true, reason: "working-tree-watch" } so they always recompute.
This commit is contained in:
@@ -115,6 +115,7 @@ describe("CheckoutDiffManager", () => {
|
|||||||
expect(workspaceGitService.getCheckoutDiff).toHaveBeenCalledWith(
|
expect(workspaceGitService.getCheckoutDiff).toHaveBeenCalledWith(
|
||||||
"/tmp/repo",
|
"/tmp/repo",
|
||||||
expect.objectContaining({ mode: "uncommitted", includeStructured: true }),
|
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 () => {
|
test("falls back to cwd when the working tree watch returns no repo root", async () => {
|
||||||
const { manager, workspaceGitService } = createManager({ repoRoot: null });
|
const { manager, workspaceGitService } = createManager({ repoRoot: null });
|
||||||
|
|
||||||
@@ -171,6 +215,7 @@ describe("CheckoutDiffManager", () => {
|
|||||||
expect(workspaceGitService.getCheckoutDiff).toHaveBeenCalledWith(
|
expect(workspaceGitService.getCheckoutDiff).toHaveBeenCalledWith(
|
||||||
"/tmp/plain",
|
"/tmp/plain",
|
||||||
expect.objectContaining({ mode: "uncommitted", includeStructured: true }),
|
expect.objectContaining({ mode: "uncommitted", includeStructured: true }),
|
||||||
|
undefined,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -164,16 +164,22 @@ export class CheckoutDiffManager {
|
|||||||
private async computeCheckoutDiffSnapshot(
|
private async computeCheckoutDiffSnapshot(
|
||||||
cwd: string,
|
cwd: string,
|
||||||
compare: CheckoutDiffCompareInput,
|
compare: CheckoutDiffCompareInput,
|
||||||
options?: { diffCwd?: string },
|
options?: { diffCwd?: string; force?: boolean; reason?: string },
|
||||||
): Promise<CheckoutDiffSnapshotPayload> {
|
): Promise<CheckoutDiffSnapshotPayload> {
|
||||||
const diffCwd = options?.diffCwd ?? cwd;
|
const diffCwd = options?.diffCwd ?? cwd;
|
||||||
try {
|
try {
|
||||||
const diffResult = await this.workspaceGitService.getCheckoutDiff(diffCwd, {
|
const diffResult = await this.workspaceGitService.getCheckoutDiff(
|
||||||
mode: compare.mode,
|
diffCwd,
|
||||||
baseRef: compare.baseRef,
|
{
|
||||||
ignoreWhitespace: compare.ignoreWhitespace,
|
mode: compare.mode,
|
||||||
includeStructured: true,
|
baseRef: compare.baseRef,
|
||||||
});
|
ignoreWhitespace: compare.ignoreWhitespace,
|
||||||
|
includeStructured: true,
|
||||||
|
},
|
||||||
|
options?.force
|
||||||
|
? { force: true, reason: options.reason ?? "checkout-diff-refresh" }
|
||||||
|
: undefined,
|
||||||
|
);
|
||||||
const files = [...(diffResult.structured ?? [])];
|
const files = [...(diffResult.structured ?? [])];
|
||||||
files.sort((a, b) => {
|
files.sort((a, b) => {
|
||||||
if (a.path === b.path) return 0;
|
if (a.path === b.path) return 0;
|
||||||
@@ -204,6 +210,8 @@ export class CheckoutDiffManager {
|
|||||||
target.refreshQueued = false;
|
target.refreshQueued = false;
|
||||||
const snapshot = await this.computeCheckoutDiffSnapshot(target.cwd, target.compare, {
|
const snapshot = await this.computeCheckoutDiffSnapshot(target.cwd, target.compare, {
|
||||||
diffCwd: target.diffCwd,
|
diffCwd: target.diffCwd,
|
||||||
|
force: true,
|
||||||
|
reason: "working-tree-watch",
|
||||||
});
|
});
|
||||||
target.latestPayload = snapshot;
|
target.latestPayload = snapshot;
|
||||||
const fingerprint = JSON.stringify(snapshot);
|
const fingerprint = JSON.stringify(snapshot);
|
||||||
|
|||||||
Reference in New Issue
Block a user