Skip to content

Commit b775459

Browse files
committed
fix(snapshot): use local ignore library instead of git check-ignore to avoid ENAMETOOLONG error on Windows
When a worktree has many ignored files (e.g., 75,000+), calling 'git check-ignore --no-index' with all file paths as command-line arguments exceeds Windows' 32KB limit, causing ENAMETOOLONG errors. This replaces the git check-ignore calls in add(), patch(), and diffFull() functions with the local 'ignore' library (already used in src/file/index.ts), which provides equivalent gitignore pattern matching without command-line length limits. Fixes #16336, #18072
1 parent d25a7fb commit b775459

1 file changed

Lines changed: 27 additions & 53 deletions

File tree

packages/opencode/src/snapshot/index.ts

Lines changed: 27 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
33
import { formatPatch, structuredPatch } from "diff"
44
import path from "path"
55
import z from "zod"
6+
import ignore from "ignore"
67
import * as CrossSpawnSpawner from "@/effect/cross-spawn-spawner"
78
import { InstanceState } from "@/effect/instance-state"
89
import { AppFileSystem } from "@/filesystem"
@@ -148,6 +149,17 @@ export namespace Snapshot {
148149
yield* fs.writeFileString(target, text ? `${text}\n` : "").pipe(Effect.orDie)
149150
})
150151

152+
const parseIgnore = Effect.fnUntraced(function* () {
153+
const ig = ignore()
154+
const gitignorePath = path.join(state.worktree, ".gitignore")
155+
const gitignoreText = yield* read(gitignorePath)
156+
if (gitignoreText) ig.add(gitignoreText)
157+
const ignorePath = path.join(state.worktree, ".ignore")
158+
const ignoreText = yield* read(ignorePath)
159+
if (ignoreText) ig.add(ignoreText)
160+
return ig.ignores.bind(ig)
161+
})
162+
151163
const add = Effect.fnUntraced(function* () {
152164
yield* sync()
153165
const [diff, other] = yield* Effect.all(
@@ -179,21 +191,12 @@ export namespace Snapshot {
179191
// Filter out files that are now gitignored even if previously tracked
180192
// Files may have been tracked before being gitignored, so we need to check
181193
// against the source project's current gitignore rules
182-
// Use --no-index to check purely against patterns (ignoring whether file is tracked)
183-
const checkArgs = [
184-
...quote,
185-
"--git-dir",
186-
path.join(state.worktree, ".git"),
187-
"--work-tree",
188-
state.worktree,
189-
"check-ignore",
190-
"--no-index",
191-
"--",
192-
...all,
193-
]
194-
const check = yield* git(checkArgs, { cwd: state.directory })
195-
const ignored =
196-
check.code === 0 ? new Set(check.text.trim().split("\n").filter(Boolean)) : new Set<string>()
194+
// Use local ignore library to avoid command line length limits
195+
const ignored = new Set<string>()
196+
const isIgnored = yield* parseIgnore()
197+
for (const item of all) {
198+
if (isIgnored(item) || isIgnored(item + "/")) ignored.add(item)
199+
}
197200
const filtered = all.filter((item) => !ignored.has(item))
198201

199202
// Remove newly-ignored files from snapshot index to prevent re-adding
@@ -297,25 +300,11 @@ export namespace Snapshot {
297300

298301
// Filter out files that are now gitignored
299302
if (files.length > 0) {
300-
const checkArgs = [
301-
...quote,
302-
"--git-dir",
303-
path.join(state.worktree, ".git"),
304-
"--work-tree",
305-
state.worktree,
306-
"check-ignore",
307-
"--no-index",
308-
"--",
309-
...files,
310-
]
311-
const check = yield* git(checkArgs, { cwd: state.directory })
312-
if (check.code === 0) {
313-
const ignored = new Set(check.text.trim().split("\n").filter(Boolean))
314-
const filtered = files.filter((item) => !ignored.has(item))
315-
return {
316-
hash,
317-
files: filtered.map((x) => path.join(state.worktree, x).replaceAll("\\", "/")),
318-
}
303+
const isIgnored = yield* parseIgnore()
304+
const filtered = files.filter((item) => !isIgnored(item) && !isIgnored(item + "/"))
305+
return {
306+
hash,
307+
files: filtered.map((x) => path.join(state.worktree, x).replaceAll("\\", "/")),
319308
}
320309
}
321310

@@ -674,25 +663,10 @@ export namespace Snapshot {
674663

675664
// Filter out files that are now gitignored
676665
if (rows.length > 0) {
677-
const files = rows.map((r) => r.file)
678-
const checkArgs = [
679-
...quote,
680-
"--git-dir",
681-
path.join(state.worktree, ".git"),
682-
"--work-tree",
683-
state.worktree,
684-
"check-ignore",
685-
"--no-index",
686-
"--",
687-
...files,
688-
]
689-
const check = yield* git(checkArgs, { cwd: state.directory })
690-
if (check.code === 0) {
691-
const ignored = new Set(check.text.trim().split("\n").filter(Boolean))
692-
const filtered = rows.filter((r) => !ignored.has(r.file))
693-
rows.length = 0
694-
rows.push(...filtered)
695-
}
666+
const isIgnored = yield* parseIgnore()
667+
const filtered = rows.filter((r) => !isIgnored(r.file) && !isIgnored(r.file + "/"))
668+
rows.length = 0
669+
rows.push(...filtered)
696670
}
697671

698672
const step = 100

0 commit comments

Comments
 (0)