From 850471276a1fad22b53516f4ea69bf9f6c3362e8 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Fri, 6 Nov 2026 09:18:45 +0100 Subject: [PATCH] fix(diff): keep non-Git workspace refresh successful Diff invalidation has no target repository for an ordinary folder. Treat that expected absence as a no-op while preserving other root-resolution errors. --- packages/server/src/server/diff/index.ts | 9 +++- .../src/server/git-filename-diffs.e2e.test.ts | 41 +++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/packages/server/src/server/diff/index.ts b/packages/server/src/server/diff/index.ts index 3316678f5a..7abd80cea8 100644 --- a/packages/server/src/server/diff/index.ts +++ b/packages/server/src/server/diff/index.ts @@ -216,7 +216,14 @@ export class DiffSubscriptions { }; } async invalidateForCwd(cwd: string): Promise { - const matches = createRealpathAwarePathMatcher(await worktreeRoot(expandTilde(cwd))); + let root: string; + try { + root = await worktreeRoot(expandTilde(cwd)); + } catch (error) { + if (error instanceof DiffReadError && error.code === "NOT_GIT_REPO") return; + throw error; + } + const matches = createRealpathAwarePathMatcher(root); for (const target of this.targets.values()) if (matches(target.compare.cwd)) this.invalidate(target); } diff --git a/packages/server/src/server/git-filename-diffs.e2e.test.ts b/packages/server/src/server/git-filename-diffs.e2e.test.ts index 3991cb8096..aa3549f764 100644 --- a/packages/server/src/server/git-filename-diffs.e2e.test.ts +++ b/packages/server/src/server/git-filename-diffs.e2e.test.ts @@ -194,3 +194,44 @@ test("checkout refresh from a subdirectory updates a pinned streamed comparison" rmSync(cwd, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); + +test("refreshing a non-Git workspace succeeds while a Git comparison is open", async () => { + const cwd = mkdtempSync(join(tmpdir(), "stream-refresh-git-")); + const plain = mkdtempSync(join(tmpdir(), "stream-refresh-plain-")); + const git = (...args: string[]) => execFileSync("git", args, { cwd, encoding: "utf8" }).trim(); + const daemon = await createTestPaseoDaemon({ agentClients: {} }); + const client = new DaemonClient({ url: `ws://127.0.0.1:${daemon.port}/ws` }); + try { + git("init", "-b", "main"); + git("config", "user.name", "Test"); + git("config", "user.email", "test@example.com"); + writeFileSync(join(cwd, "file.txt"), "committed content\n"); + git("add", "."); + git("-c", "commit.gpgsign=false", "commit", "-m", "base"); + await client.connect(); + const states: import("@getpaseo/client").DiffState[] = []; + const observation = client.observeDiff({ + cwd, + base: { kind: "empty" }, + head: { kind: "rev", rev: git("rev-parse", "HEAD") }, + }); + const unsubscribe = observation.subscribe({ + snapshot: (state) => states.push(state), + update: () => {}, + }); + try { + await expect.poll(() => states.at(-1)?.phase, { timeout: 15000 }).toBe("ready"); + const revision = states.at(-1)?.revision; + expect(await client.checkoutRefresh(plain)).toMatchObject({ success: true, error: null }); + expect(states.at(-1)).toMatchObject({ phase: "ready", revision }); + } finally { + unsubscribe(); + await observation.release(); + } + } finally { + await client.close(); + await daemon.close(); + rmSync(cwd, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + rmSync(plain, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + } +});