Selaa lähdekoodia

fix(snapshot): complete gitignore respect for previously tracked files (#22172)

Dax 5 päivää sitten
vanhempi
sitoutus
264418c0cd

+ 27 - 0
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 }))

+ 35 - 0
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({