diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index 2e647e0f4789..dc3478dde782 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -957,7 +957,15 @@ export default function Page() { ? (details.properties as Record) : undefined const file = typeof props?.file === "string" ? props.file : undefined - if (!file || file.startsWith(".git/")) return + if (!file) return + // .git is watched separately; rewrites of index (stage/commit) and HEAD + // (branch switch) change the VCS state the review panel renders, so refresh + // for those and skip the rest of .git + const segments = file.split(/[\\/]/) + if (segments.includes(".git")) { + const name = segments[segments.length - 1] + if (name !== "index" && name !== "HEAD") return + } refreshVcs() }) onCleanup(stopVcs) diff --git a/packages/core/src/filesystem/watcher.ts b/packages/core/src/filesystem/watcher.ts index c5e20631917e..78badeb14a22 100644 --- a/packages/core/src/filesystem/watcher.ts +++ b/packages/core/src/filesystem/watcher.ts @@ -116,8 +116,11 @@ const layer = Layer.effect( const resolved = (yield* git.repo.discover(location.directory))?.gitDirectory const vcs = resolved ? yield* fs.realPath(resolved).pipe(Effect.catch(() => Effect.succeed(resolved))) : undefined if (vcs && !config.includes(".git") && !config.includes(vcs) && (!resolved || !config.includes(resolved))) { + // watch HEAD (branch switches) and index (staging/commits) so clients can + // refresh VCS state after out-of-band git commands; everything else under + // .git is noise const ignore = (yield* fs.readDirectoryEntries(vcs).pipe(Effect.catch(() => Effect.succeed([])))).flatMap( - (entry) => (entry.name === "HEAD" ? [] : [entry.name]), + (entry) => (entry.name === "HEAD" || entry.name === "index" ? [] : [entry.name]), ) yield* Effect.forkScoped(subscribe(vcs, ignore)) } diff --git a/packages/core/test/filesystem/watcher.test.ts b/packages/core/test/filesystem/watcher.test.ts index 0a287ea010d9..99123540968b 100644 --- a/packages/core/test/filesystem/watcher.test.ts +++ b/packages/core/test/filesystem/watcher.test.ts @@ -197,19 +197,34 @@ describeWatcher("Watcher", () => { }).pipe(Effect.provide(AppNodeBuilder.build(LayerNode.group([FSUtil.node, EventV2.node])))), ) - it.live("ignores .git/index changes", () => + it.live("publishes .git/index events", () => withTmp( (directory) => Effect.gen(function* () { const fs = yield* FSUtil.Service const index = path.join(directory, ".git", "index") yield* ready(directory) - yield* noUpdate( - (event) => event.file === index, - fs - .writeFileString(path.join(directory, "tracked.txt"), "a") - .pipe(Effect.andThen(Effect.promise(() => $`git add .`.cwd(directory).quiet())), Effect.asVoid), - ) + expect( + yield* nextUpdate( + (event) => event.file === index, + fs + .writeFileString(path.join(directory, "tracked.txt"), "a") + .pipe(Effect.andThen(Effect.promise(() => $`git add .`.cwd(directory).quiet())), Effect.asVoid), + ), + ).toMatchObject({ file: index }) + }), + { git: true }, + ), + ) + + it.live("ignores other .git changes", () => + withTmp( + (directory) => + Effect.gen(function* () { + const fs = yield* FSUtil.Service + const msg = path.join(directory, ".git", "COMMIT_EDITMSG") + yield* ready(directory) + yield* noUpdate((event) => event.file === msg, fs.writeFileString(msg, "test")) }), { git: true }, ),