Skip to content

Commit 7f7f4ad

Browse files
committed
fix(watcher): check both original and realpath against cfgIgnores, add symlink test
Address review feedback on PR #27016: - Check both pre-realpath (resolved) and post-realpath (vcsDir) against cfgIgnores so user configs referencing the symlink path still work. - Add regression test for symlinked .git directory verifying HEAD change events propagate through the resolved real path.
1 parent a189758 commit 7f7f4ad

2 files changed

Lines changed: 38 additions & 1 deletion

File tree

packages/opencode/src/file/watcher.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ export const layer = Layer.effect(
135135
const vcsDir = resolved
136136
? yield* Effect.promise(() => realpath(resolved).catch(() => resolved))
137137
: undefined
138-
if (vcsDir && !cfgIgnores.includes(".git") && !cfgIgnores.includes(vcsDir)) {
138+
if (vcsDir && !cfgIgnores.includes(".git") && !cfgIgnores.includes(vcsDir) && (!resolved || !cfgIgnores.includes(resolved))) {
139139
const ignore = (yield* Effect.promise(() => readdir(vcsDir).catch(() => []))).filter(
140140
(entry) => entry !== "HEAD",
141141
)

packages/opencode/test/file/watcher.test.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,4 +247,41 @@ describeWatcher("FileWatcher", () => {
247247
),
248248
)
249249
})
250+
251+
// Symlink support varies by platform; skip where unavailable
252+
const canSymlink = process.platform !== "win32"
253+
const testSymlink = canSymlink ? test : test.skip
254+
255+
testSymlink("publishes .git/HEAD events through a symlinked .git directory", async () => {
256+
await using tmp = await tmpdir({ git: true })
257+
const dir = tmp.path
258+
const actualGit = path.join(dir, "..", "tmp_actual_git_" + Math.random().toString(36).slice(2))
259+
// Move .git to a sibling directory and replace with a symlink
260+
await fs.rename(path.join(dir, ".git"), actualGit)
261+
await fs.symlink(actualGit, path.join(dir, ".git"))
262+
263+
try {
264+
const head = path.join(dir, ".git", "HEAD")
265+
const branch = `watch-${Math.random().toString(36).slice(2)}`
266+
await $`git branch ${branch}`.cwd(dir).quiet()
267+
268+
await withWatcher(
269+
dir,
270+
nextUpdate(
271+
dir,
272+
(evt) => evt.file === path.join(actualGit, "HEAD") && evt.event !== "unlink",
273+
Effect.promise(() => fs.writeFile(head, `ref: refs/heads/${branch}\n`)),
274+
).pipe(
275+
Effect.tap((evt) =>
276+
Effect.sync(() => {
277+
expect(evt.file).toBe(path.join(actualGit, "HEAD"))
278+
expect(["add", "change"]).toContain(evt.event)
279+
}),
280+
),
281+
),
282+
)
283+
} finally {
284+
await fs.rm(actualGit, { recursive: true, force: true }).catch(() => undefined)
285+
}
286+
})
250287
})

0 commit comments

Comments
 (0)