summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDax <[email protected]>2026-04-12 14:05:46 -0400
committerGitHub <[email protected]>2026-04-12 14:05:46 -0400
commit264418c0cdda37e214c01688dca66c0dcfd3e0b0 (patch)
treec1f8332520bec4127e1836da3c2253e72e087d4c
parentfa2c69f09c031c175d0872bd6406e18241ff2d78 (diff)
downloadopencode-264418c0cdda37e214c01688dca66c0dcfd3e0b0.tar.gz
opencode-264418c0cdda37e214c01688dca66c0dcfd3e0b0.zip
fix(snapshot): complete gitignore respect for previously tracked files (#22172)
-rw-r--r--packages/opencode/src/snapshot/index.ts27
-rw-r--r--packages/opencode/test/snapshot/snapshot.test.ts35
2 files changed, 62 insertions, 0 deletions
diff --git a/packages/opencode/src/snapshot/index.ts b/packages/opencode/src/snapshot/index.ts
index 3b522a03e..995e8d3fd 100644
--- a/packages/opencode/src/snapshot/index.ts
+++ b/packages/opencode/src/snapshot/index.ts
@@ -180,6 +180,7 @@ export namespace Snapshot {
// Filter out files that are now gitignored even if previously tracked
// Files may have been tracked before being gitignored, so we need to check
// against the source project's current gitignore rules
+ // Use --no-index to check purely against patterns (ignoring whether file is tracked)
const checkArgs = [
...quote,
"--git-dir",
@@ -187,6 +188,7 @@ export namespace Snapshot {
"--work-tree",
state.worktree,
"check-ignore",
+ "--no-index",
"--",
...all,
]
@@ -303,6 +305,7 @@ export namespace Snapshot {
"--work-tree",
state.worktree,
"check-ignore",
+ "--no-index",
"--",
...files,
]
@@ -669,6 +672,30 @@ export namespace Snapshot {
} satisfies Row,
]
})
+
+ // Filter out files that are now gitignored
+ if (rows.length > 0) {
+ const files = rows.map((r) => r.file)
+ const checkArgs = [
+ ...quote,
+ "--git-dir",
+ path.join(state.worktree, ".git"),
+ "--work-tree",
+ state.worktree,
+ "check-ignore",
+ "--no-index",
+ "--",
+ ...files,
+ ]
+ const check = yield* git(checkArgs, { cwd: state.directory })
+ if (check.code === 0) {
+ const ignored = new Set(check.text.trim().split("\n").filter(Boolean))
+ const filtered = rows.filter((r) => !ignored.has(r.file))
+ rows.length = 0
+ rows.push(...filtered)
+ }
+ }
+
const step = 100
const patch = (file: string, before: string, after: string) =>
formatPatch(structuredPatch(file, file, before, after, "", "", { context: Number.MAX_SAFE_INTEGER }))
diff --git a/packages/opencode/test/snapshot/snapshot.test.ts b/packages/opencode/test/snapshot/snapshot.test.ts
index 22253ecab..971d053bd 100644
--- a/packages/opencode/test/snapshot/snapshot.test.ts
+++ b/packages/opencode/test/snapshot/snapshot.test.ts
@@ -612,6 +612,41 @@ test("files tracked in snapshot but now gitignored are filtered out", async () =
})
})
+test("gitignore updated between track calls filters from diff", async () => {
+ await using tmp = await bootstrap()
+ await Instance.provide({
+ directory: tmp.path,
+ fn: async () => {
+ // a.txt is already committed from bootstrap - track it in snapshot
+ const before = await Snapshot.track()
+ expect(before).toBeTruthy()
+
+ // Modify a.txt (so it appears in diff-files)
+ await Filesystem.write(`${tmp.path}/a.txt`, "modified content")
+
+ // Now add gitignore that would exclude a.txt
+ await Filesystem.write(`${tmp.path}/.gitignore`, "a.txt\n")
+
+ // Also modify b.txt which is not gitignored
+ await Filesystem.write(`${tmp.path}/b.txt`, "also modified")
+
+ // Second track - should not include a.txt even though it changed
+ const after = await Snapshot.track()
+ expect(after).toBeTruthy()
+
+ // Verify a.txt is NOT in the diff between snapshots
+ const diffs = await Snapshot.diffFull(before!, after!)
+ expect(diffs.some((x) => x.file === "a.txt")).toBe(false)
+
+ // But .gitignore should be in the diff
+ expect(diffs.some((x) => x.file === ".gitignore")).toBe(true)
+
+ // b.txt should be in the diff (not gitignored)
+ expect(diffs.some((x) => x.file === "b.txt")).toBe(true)
+ },
+ })
+})
+
test("git info exclude changes", async () => {
await using tmp = await bootstrap()
await Instance.provide({