mirror of
https://github.com/anomalyco/opencode.git
synced 2026-04-17 03:14:50 +00:00
Compare commits
1 Commits
dev
...
fix/snapsh
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f88ab8f8d |
@@ -177,8 +177,39 @@ export namespace Snapshot {
|
||||
const all = Array.from(new Set([...tracked, ...untracked]))
|
||||
if (!all.length) return
|
||||
|
||||
// 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,
|
||||
]
|
||||
const check = yield* git(checkArgs, { cwd: state.directory })
|
||||
const ignored =
|
||||
check.code === 0 ? new Set(check.text.trim().split("\n").filter(Boolean)) : new Set<string>()
|
||||
const filtered = all.filter((item) => !ignored.has(item))
|
||||
|
||||
// Remove newly-ignored files from snapshot index to prevent re-adding
|
||||
if (ignored.size > 0) {
|
||||
const ignoredFiles = Array.from(ignored)
|
||||
log.info("removing gitignored files from snapshot", { count: ignoredFiles.length })
|
||||
yield* git([...cfg, ...args(["rm", "--cached", "-f", "--", ...ignoredFiles])], {
|
||||
cwd: state.directory,
|
||||
})
|
||||
}
|
||||
|
||||
if (!filtered.length) return
|
||||
|
||||
const large = (yield* Effect.all(
|
||||
all.map((item) =>
|
||||
filtered.map((item) =>
|
||||
fs
|
||||
.stat(path.join(state.directory, item))
|
||||
.pipe(Effect.catch(() => Effect.void))
|
||||
@@ -259,14 +290,39 @@ export namespace Snapshot {
|
||||
log.warn("failed to get diff", { hash, exitCode: result.code })
|
||||
return { hash, files: [] }
|
||||
}
|
||||
const files = result.text
|
||||
.trim()
|
||||
.split("\n")
|
||||
.map((x) => x.trim())
|
||||
.filter(Boolean)
|
||||
|
||||
// Filter out files that are now gitignored
|
||||
if (files.length > 0) {
|
||||
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 = files.filter((item) => !ignored.has(item))
|
||||
return {
|
||||
hash,
|
||||
files: filtered.map((x) => path.join(state.worktree, x).replaceAll("\\", "/")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
hash,
|
||||
files: result.text
|
||||
.trim()
|
||||
.split("\n")
|
||||
.map((x) => x.trim())
|
||||
.filter(Boolean)
|
||||
.map((x) => path.join(state.worktree, x).replaceAll("\\", "/")),
|
||||
files: files.map((x) => path.join(state.worktree, x).replaceAll("\\", "/")),
|
||||
}
|
||||
}),
|
||||
)
|
||||
@@ -616,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 }))
|
||||
|
||||
@@ -511,6 +511,49 @@ test("circular symlinks", async () => {
|
||||
})
|
||||
})
|
||||
|
||||
test("source project gitignore is respected - ignored files are not snapshotted", async () => {
|
||||
await using tmp = await tmpdir({
|
||||
git: true,
|
||||
init: async (dir) => {
|
||||
// Create gitignore BEFORE any tracking
|
||||
await Filesystem.write(`${dir}/.gitignore`, "*.ignored\nbuild/\nnode_modules/\n")
|
||||
await Filesystem.write(`${dir}/tracked.txt`, "tracked content")
|
||||
await Filesystem.write(`${dir}/ignored.ignored`, "ignored content")
|
||||
await $`mkdir -p ${dir}/build`.quiet()
|
||||
await Filesystem.write(`${dir}/build/output.js`, "build output")
|
||||
await Filesystem.write(`${dir}/normal.js`, "normal js")
|
||||
await $`git add .`.cwd(dir).quiet()
|
||||
await $`git commit -m init`.cwd(dir).quiet()
|
||||
},
|
||||
})
|
||||
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
const before = await Snapshot.track()
|
||||
expect(before).toBeTruthy()
|
||||
|
||||
// Modify tracked files and create new ones - some ignored, some not
|
||||
await Filesystem.write(`${tmp.path}/tracked.txt`, "modified tracked")
|
||||
await Filesystem.write(`${tmp.path}/new.ignored`, "new ignored")
|
||||
await Filesystem.write(`${tmp.path}/new-tracked.txt`, "new tracked")
|
||||
await Filesystem.write(`${tmp.path}/build/new-build.js`, "new build file")
|
||||
|
||||
const patch = await Snapshot.patch(before!)
|
||||
|
||||
// Modified and new tracked files should be in snapshot
|
||||
expect(patch.files).toContain(fwd(tmp.path, "new-tracked.txt"))
|
||||
expect(patch.files).toContain(fwd(tmp.path, "tracked.txt"))
|
||||
|
||||
// Ignored files should NOT be in snapshot
|
||||
expect(patch.files).not.toContain(fwd(tmp.path, "new.ignored"))
|
||||
expect(patch.files).not.toContain(fwd(tmp.path, "ignored.ignored"))
|
||||
expect(patch.files).not.toContain(fwd(tmp.path, "build/output.js"))
|
||||
expect(patch.files).not.toContain(fwd(tmp.path, "build/new-build.js"))
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("gitignore changes", async () => {
|
||||
await using tmp = await bootstrap()
|
||||
await Instance.provide({
|
||||
@@ -535,6 +578,67 @@ test("gitignore changes", async () => {
|
||||
})
|
||||
})
|
||||
|
||||
test("files tracked in snapshot but now gitignored are filtered out", 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")
|
||||
|
||||
const patch = await Snapshot.patch(before!)
|
||||
|
||||
// a.txt is now gitignored and should NOT appear in patch
|
||||
expect(patch.files).not.toContain(fwd(tmp.path, "a.txt"))
|
||||
|
||||
// .gitignore itself should appear (it's a new file)
|
||||
expect(patch.files).toContain(fwd(tmp.path, ".gitignore"))
|
||||
|
||||
// b.txt should appear (not gitignored)
|
||||
expect(patch.files).toContain(fwd(tmp.path, "b.txt"))
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("gitignore updated between track calls filters from diff", async () => {
|
||||
await using tmp = await bootstrap()
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
// First track - a.txt is tracked in snapshot
|
||||
const before = await Snapshot.track()
|
||||
expect(before).toBeTruthy()
|
||||
|
||||
// Modify a.txt
|
||||
await Filesystem.write(`${tmp.path}/a.txt`, "modified content")
|
||||
|
||||
// Now add a.txt to gitignore
|
||||
await Filesystem.write(`${tmp.path}/.gitignore`, "a.txt\n")
|
||||
|
||||
// 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)
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("git info exclude changes", async () => {
|
||||
await using tmp = await bootstrap()
|
||||
await Instance.provide({
|
||||
|
||||
Reference in New Issue
Block a user