Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions packages/opencode/src/snapshot/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,13 +180,15 @@ 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",
path.join(state.worktree, ".git"),
"--work-tree",
state.worktree,
"check-ignore",
"--no-index",
"--",
...all,
]
Expand Down Expand Up @@ -303,6 +305,7 @@ export namespace Snapshot {
"--work-tree",
state.worktree,
"check-ignore",
"--no-index",
"--",
...files,
]
Expand Down Expand Up @@ -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 }))
Expand Down
35 changes: 35 additions & 0 deletions packages/opencode/test/snapshot/snapshot.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
Loading