summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorShoubhit Dash <[email protected]>2026-02-20 18:32:25 +0530
committerGitHub <[email protected]>2026-02-20 07:02:25 -0600
commitac0b37a7b7a8dfc55a682b94cff0020ad28cca66 (patch)
tree928dcfed042758c9ac378591c26172f1ad986a96
parent7e1051af0784693d7fc37ae31d6f513d47e0d24b (diff)
downloadopencode-ac0b37a7b7a8dfc55a682b94cff0020ad28cca66.tar.gz
opencode-ac0b37a7b7a8dfc55a682b94cff0020ad28cca66.zip
fix(snapshot): respect info exclude in snapshot staging (#13495)
-rw-r--r--packages/opencode/src/snapshot/index.ts40
-rw-r--r--packages/opencode/test/snapshot/snapshot.test.ts62
2 files changed, 99 insertions, 3 deletions
diff --git a/packages/opencode/src/snapshot/index.ts b/packages/opencode/src/snapshot/index.ts
index a1c2b5781..83cc467e4 100644
--- a/packages/opencode/src/snapshot/index.ts
+++ b/packages/opencode/src/snapshot/index.ts
@@ -66,7 +66,7 @@ export namespace Snapshot {
await $`git --git-dir ${git} config core.autocrlf false`.quiet().nothrow()
log.info("initialized")
}
- await $`git --git-dir ${git} --work-tree ${Instance.worktree} add .`.quiet().cwd(Instance.directory).nothrow()
+ await add(git)
const hash = await $`git --git-dir ${git} --work-tree ${Instance.worktree} write-tree`
.quiet()
.cwd(Instance.directory)
@@ -84,7 +84,7 @@ export namespace Snapshot {
export async function patch(hash: string): Promise<Patch> {
const git = gitdir()
- await $`git --git-dir ${git} --work-tree ${Instance.worktree} add .`.quiet().cwd(Instance.directory).nothrow()
+ await add(git)
const result =
await $`git -c core.autocrlf=false -c core.quotepath=false --git-dir ${git} --work-tree ${Instance.worktree} diff --no-ext-diff --name-only ${hash} -- .`
.quiet()
@@ -162,7 +162,7 @@ export namespace Snapshot {
export async function diff(hash: string) {
const git = gitdir()
- await $`git --git-dir ${git} --work-tree ${Instance.worktree} add .`.quiet().cwd(Instance.directory).nothrow()
+ await add(git)
const result =
await $`git -c core.autocrlf=false -c core.quotepath=false --git-dir ${git} --work-tree ${Instance.worktree} diff --no-ext-diff ${hash} -- .`
.quiet()
@@ -253,4 +253,38 @@ export namespace Snapshot {
const project = Instance.project
return path.join(Global.Path.data, "snapshot", project.id)
}
+
+ async function add(git: string) {
+ await syncExclude(git)
+ await $`git --git-dir ${git} --work-tree ${Instance.worktree} add .`.quiet().cwd(Instance.directory).nothrow()
+ }
+
+ async function syncExclude(git: string) {
+ const file = await excludes()
+ const target = path.join(git, "info", "exclude")
+ await fs.mkdir(path.join(git, "info"), { recursive: true })
+ if (!file) {
+ await Bun.write(target, "")
+ return
+ }
+ const text = await Bun.file(file)
+ .text()
+ .catch(() => "")
+ await Bun.write(target, text)
+ }
+
+ async function excludes() {
+ const file = await $`git rev-parse --path-format=absolute --git-path info/exclude`
+ .quiet()
+ .cwd(Instance.worktree)
+ .nothrow()
+ .text()
+ if (!file.trim()) return
+ const exists = await fs
+ .stat(file.trim())
+ .then(() => true)
+ .catch(() => false)
+ if (!exists) return
+ return file.trim()
+ }
}
diff --git a/packages/opencode/test/snapshot/snapshot.test.ts b/packages/opencode/test/snapshot/snapshot.test.ts
index b54cb8b8a..9a0622c4a 100644
--- a/packages/opencode/test/snapshot/snapshot.test.ts
+++ b/packages/opencode/test/snapshot/snapshot.test.ts
@@ -508,6 +508,68 @@ test("gitignore changes", async () => {
})
})
+test("git info exclude changes", async () => {
+ await using tmp = await bootstrap()
+ await Instance.provide({
+ directory: tmp.path,
+ fn: async () => {
+ const before = await Snapshot.track()
+ expect(before).toBeTruthy()
+
+ const file = `${tmp.path}/.git/info/exclude`
+ const text = await Bun.file(file).text()
+ await Bun.write(file, `${text.trimEnd()}\nignored.txt\n`)
+ await Bun.write(`${tmp.path}/ignored.txt`, "ignored content")
+ await Bun.write(`${tmp.path}/normal.txt`, "normal content")
+
+ const patch = await Snapshot.patch(before!)
+ expect(patch.files).toContain(`${tmp.path}/normal.txt`)
+ expect(patch.files).not.toContain(`${tmp.path}/ignored.txt`)
+
+ const after = await Snapshot.track()
+ const diffs = await Snapshot.diffFull(before!, after!)
+ expect(diffs.some((x) => x.file === "normal.txt")).toBe(true)
+ expect(diffs.some((x) => x.file === "ignored.txt")).toBe(false)
+ },
+ })
+})
+
+test("git info exclude keeps global excludes", async () => {
+ await using tmp = await bootstrap()
+ await Instance.provide({
+ directory: tmp.path,
+ fn: async () => {
+ const global = `${tmp.path}/global.ignore`
+ const config = `${tmp.path}/global.gitconfig`
+ await Bun.write(global, "global.tmp\n")
+ await Bun.write(config, `[core]\n\texcludesFile = ${global}\n`)
+
+ const prev = process.env.GIT_CONFIG_GLOBAL
+ process.env.GIT_CONFIG_GLOBAL = config
+ try {
+ const before = await Snapshot.track()
+ expect(before).toBeTruthy()
+
+ const file = `${tmp.path}/.git/info/exclude`
+ const text = await Bun.file(file).text()
+ await Bun.write(file, `${text.trimEnd()}\ninfo.tmp\n`)
+
+ await Bun.write(`${tmp.path}/global.tmp`, "global content")
+ await Bun.write(`${tmp.path}/info.tmp`, "info content")
+ await Bun.write(`${tmp.path}/normal.txt`, "normal content")
+
+ const patch = await Snapshot.patch(before!)
+ expect(patch.files).toContain(`${tmp.path}/normal.txt`)
+ expect(patch.files).not.toContain(`${tmp.path}/global.tmp`)
+ expect(patch.files).not.toContain(`${tmp.path}/info.tmp`)
+ } finally {
+ if (prev) process.env.GIT_CONFIG_GLOBAL = prev
+ else delete process.env.GIT_CONFIG_GLOBAL
+ }
+ },
+ })
+})
+
test("concurrent file operations during patch", async () => {
await using tmp = await bootstrap()
await Instance.provide({