fix(diff): refresh equivalent worktree paths across platforms

Explicit refreshes must match filesystem-equivalent roots, including Windows path aliases. Use the existing realpath-aware matcher, budget real Git completion waits consistently, and assert optional status metadata in the filesystem commit-file journey.
This commit is contained in:
Mohamed Boudra
2026-11-06 08:48:13 +01:00
parent 8b5527e7e6
commit ad21f9eb93
3 changed files with 57 additions and 5 deletions
@@ -690,6 +690,7 @@ describe("commit file diffs", () => {
expect(result).toEqual({
file: {
path: filePath,
status: "ok",
isNew: false,
isDeleted: false,
additions: 1,
+3 -2
View File
@@ -1,6 +1,6 @@
import { TOTAL_DIFF_MAX_BYTES } from "../../utils/checkout-git.js";
import { DEFAULT_MAX_OUTPUT_BYTES, runGitCommand } from "../../utils/run-git-command.js";
import { expandTilde } from "../../utils/path.js";
import { expandTilde, createRealpathAwarePathMatcher } from "../../utils/path.js";
import { packBodies } from "./batches.js";
import { setImmediate } from "node:timers/promises";
import {
@@ -216,8 +216,9 @@ export class DiffSubscriptions {
};
}
invalidateForCwd(cwd: string): void {
const matches = createRealpathAwarePathMatcher(cwd);
for (const target of this.targets.values())
if (target.compare.cwd === cwd) this.invalidate(target);
if (matches(target.compare.cwd)) this.invalidate(target);
}
private invalidate(target: Target): void {
if (target.closed) return;
@@ -67,7 +67,9 @@ test("shares computation, highlights only the changed file, recovers refs and re
const refreshStarted = performance.now();
await writeFile(join(cwd, "9.ts"), "const value = 999;\n");
await expect
.poll(() => first.filter((frame) => frame.opcode === DiffOpcode.End).length)
.poll(() => first.filter((frame) => frame.opcode === DiffOpcode.End).length, {
timeout: 10000,
})
.toBeGreaterThan(0);
const begin = first.find((frame) => frame.opcode === DiffOpcode.Begin);
if (!begin || begin.opcode !== DiffOpcode.Begin) throw new Error("Missing begin");
@@ -133,7 +135,7 @@ test("shares computation, highlights only the changed file, recovers refs and re
await observer.close();
await rm(cwd, { recursive: true, force: true });
}
}, 30_000);
}, 60_000);
test("supersedes an in-flight revision at a batch boundary and ends the replacement", async () => {
const cwd = await mkdtemp(join(tmpdir(), "diff-supersede-"));
@@ -344,7 +346,9 @@ test("a cached replay completes before a live refresh can depend on it", async (
resume();
await replay;
await expect
.poll(() => frames.filter((frame) => frame.opcode === DiffOpcode.End).length)
.poll(() => frames.filter((frame) => frame.opcode === DiffOpcode.End).length, {
timeout: 10000,
})
.toBe(2);
let committed = 0;
for (const frame of frames) {
@@ -534,3 +538,49 @@ test.each([true, false])(
}
},
);
test("explicit refresh accepts an equivalent worktree path alias", async () => {
const { symlink } = await import("node:fs/promises");
const root = await mkdtemp(join(tmpdir(), "diff-refresh-alias-"));
const cwd = join(root, "repo");
const alias = join(root, "alias");
const git = (...args: string[]) => execFileSync("git", args, { cwd, encoding: "utf8" }).trim();
const streams = new DiffSubscriptions({
async requestWorkingTreeWatch(directory) {
return { repoRoot: directory, unsubscribe: async () => {} };
},
});
const frames: DiffFrame[] = [];
try {
await mkdir(cwd);
await symlink(cwd, alias, "junction");
git("init", "-b", "main");
git("config", "user.name", "Test");
git("config", "user.email", "test@example.com");
await writeFile(join(cwd, "a.txt"), "base\n");
git("add", ".");
git("-c", "commit.gpgsign=false", "commit", "-m", "base");
const base = git("rev-parse", "HEAD");
await writeFile(join(cwd, "a.txt"), "first\n");
git("add", ".");
git("-c", "commit.gpgsign=false", "commit", "-m", "edit");
const head = git("rev-parse", "HEAD");
const subscription = await streams.subscribe(
{ cwd, base: { kind: "rev", rev: base }, head: { kind: "rev", rev: head } },
async (frame) => {
frames.push(frame);
},
);
await subscription.start();
frames.length = 0;
streams.invalidateForCwd(alias);
await expect
.poll(() => frames.some((frame) => frame.opcode === DiffOpcode.End), { timeout: 10000 })
.toBe(true);
expect(frames.at(-1)).toMatchObject({ opcode: DiffOpcode.End, revision: 2, error: null });
await subscription.release();
} finally {
await streams.close();
await rm(root, { recursive: true, force: true });
}
});