Skip to content

Commit 036a100

Browse files
author
Richard Cool
committed
fix(watcher): filter ignored paths before publishing events, handle worktree git dirs
The parcel/watcher callback was shared and published all filesystem events without re-filtering through FileIgnore. Build artifacts and lock files in ignored directories (out/, .turbo/, etc.) fired watcher events that drove repeated refreshVcs() calls and made the review panel flicker. Also fixes the frontend listener to handle absolute .git/worktrees/... paths produced by git worktrees, which bypassed the old startsWith('.git/') check. Fixes #21452
1 parent ae614d9 commit 036a100

2 files changed

Lines changed: 30 additions & 11 deletions

File tree

packages/app/src/pages/session.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -931,7 +931,11 @@ export default function Page() {
931931
? (evt.details.properties as Record<string, unknown>)
932932
: undefined
933933
const file = typeof props?.file === "string" ? props.file : undefined
934-
if (!file || file.startsWith(".git/")) return
934+
if (!file) return
935+
const path = file.replaceAll("\\", "/")
936+
// Worktree watcher events can arrive as absolute .git/worktrees/... paths.
937+
const git = path.startsWith(".git/") || path.includes("/.git/")
938+
if (git) return
935939
refreshVcs()
936940
})
937941
onCleanup(stopVcs)

packages/opencode/src/file/watcher.ts

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,13 @@ export namespace FileWatcher {
5959
})
6060
}
6161

62+
function rel(dir: string, file: string) {
63+
const next = path.relative(dir, file).replaceAll("\\", "/")
64+
if (path.isAbsolute(next)) return
65+
if (next === ".." || next.startsWith("../")) return
66+
return next
67+
}
68+
6269
export const hasNativeBinding = () => !!watcher()
6370

6471
export interface Interface {
@@ -95,16 +102,24 @@ export namespace FileWatcher {
95102
Effect.promise(() => Promise.allSettled(subs.map((sub) => sub.unsubscribe()))),
96103
)
97104

98-
const cb: ParcelWatcher.SubscribeCallback = Instance.bind((err, evts) => {
99-
if (err) return
100-
for (const evt of evts) {
101-
if (evt.type === "create") Bus.publish(Event.Updated, { file: evt.path, event: "add" })
102-
if (evt.type === "update") Bus.publish(Event.Updated, { file: evt.path, event: "change" })
103-
if (evt.type === "delete") Bus.publish(Event.Updated, { file: evt.path, event: "unlink" })
104-
}
105-
})
105+
const subscribe = (dir: string, ignore: string[], git = false) => {
106+
const cb: ParcelWatcher.SubscribeCallback = Instance.bind((err, evts) => {
107+
if (err) return
108+
for (const evt of evts) {
109+
const file = rel(dir, evt.path)
110+
if (file === undefined) continue
111+
if (git) {
112+
if (file !== "HEAD") continue
113+
} else if (FileIgnore.match(file, { extra: cfgIgnores })) {
114+
continue
115+
}
116+
117+
if (evt.type === "create") Bus.publish(Event.Updated, { file: evt.path, event: "add" })
118+
if (evt.type === "update") Bus.publish(Event.Updated, { file: evt.path, event: "change" })
119+
if (evt.type === "delete") Bus.publish(Event.Updated, { file: evt.path, event: "unlink" })
120+
}
121+
})
106122

107-
const subscribe = (dir: string, ignore: string[]) => {
108123
const pending = w.subscribe(dir, cb, { ignore, backend })
109124
return Effect.gen(function* () {
110125
const sub = yield* Effect.promise(() => pending)
@@ -142,7 +157,7 @@ export namespace FileWatcher {
142157
const ignore = (yield* Effect.promise(() => readdir(vcsDir).catch(() => []))).filter(
143158
(entry) => entry !== "HEAD",
144159
)
145-
yield* subscribe(vcsDir, ignore)
160+
yield* subscribe(vcsDir, ignore, true)
146161
}
147162
}
148163
},

0 commit comments

Comments
 (0)