Skip to content

Commit 3b93e8d

Browse files
committed
fix(app): added/deleted file status now correctly calculated
1 parent 23631a9 commit 3b93e8d

5 files changed

Lines changed: 115 additions & 4 deletions

File tree

packages/app/src/components/file-tree.tsx

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,10 +130,57 @@ export default function FileTree(props: {
130130
const nodes = file.tree.children(props.path)
131131
const current = filter()
132132
if (!current) return nodes
133-
return nodes.filter((node) => {
133+
134+
const parent = (path: string) => {
135+
const idx = path.lastIndexOf("/")
136+
if (idx === -1) return ""
137+
return path.slice(0, idx)
138+
}
139+
140+
const leaf = (path: string) => {
141+
const idx = path.lastIndexOf("/")
142+
return idx === -1 ? path : path.slice(idx + 1)
143+
}
144+
145+
const out = nodes.filter((node) => {
134146
if (node.type === "file") return current.files.has(node.path)
135147
return current.dirs.has(node.path)
136148
})
149+
150+
const seen = new Set(out.map((node) => node.path))
151+
152+
for (const dir of current.dirs) {
153+
if (parent(dir) !== props.path) continue
154+
if (seen.has(dir)) continue
155+
out.push({
156+
name: leaf(dir),
157+
path: dir,
158+
absolute: dir,
159+
type: "directory",
160+
ignored: false,
161+
})
162+
seen.add(dir)
163+
}
164+
165+
for (const item of current.files) {
166+
if (parent(item) !== props.path) continue
167+
if (seen.has(item)) continue
168+
out.push({
169+
name: leaf(item),
170+
path: item,
171+
absolute: item,
172+
type: "file",
173+
ignored: false,
174+
})
175+
seen.add(item)
176+
}
177+
178+
return out.toSorted((a, b) => {
179+
if (a.type !== b.type) {
180+
return a.type === "directory" ? -1 : 1
181+
}
182+
return a.name.localeCompare(b.name)
183+
})
137184
})
138185

139186
const Node = (

packages/app/src/pages/session.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -500,9 +500,7 @@ export default function Page() {
500500
const out = new Map<string, "add" | "del" | "mix">()
501501
for (const diff of diffs()) {
502502
const file = normalize(diff.file)
503-
const add = diff.additions > 0
504-
const del = diff.deletions > 0
505-
const kind = add && del ? "mix" : add ? "add" : del ? "del" : "mix"
503+
const kind = diff.status === "added" ? "add" : diff.status === "deleted" ? "del" : "mix"
506504

507505
out.set(file, kind)
508506

packages/opencode/src/snapshot/index.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ export namespace Snapshot {
188188
after: z.string(),
189189
additions: z.number(),
190190
deletions: z.number(),
191+
status: z.enum(["added", "deleted", "modified"]).optional(),
191192
})
192193
.meta({
193194
ref: "FileDiff",
@@ -196,6 +197,23 @@ export namespace Snapshot {
196197
export async function diffFull(from: string, to: string): Promise<FileDiff[]> {
197198
const git = gitdir()
198199
const result: FileDiff[] = []
200+
const status = new Map<string, "added" | "deleted" | "modified">()
201+
202+
const statuses =
203+
await $`git -c core.autocrlf=false -c core.quotepath=false --git-dir ${git} --work-tree ${Instance.worktree} diff --no-ext-diff --name-status --no-renames ${from} ${to} -- .`
204+
.quiet()
205+
.cwd(Instance.directory)
206+
.nothrow()
207+
.text()
208+
209+
for (const line of statuses.trim().split("\n")) {
210+
if (!line) continue
211+
const [code, file] = line.split("\t")
212+
if (!code || !file) continue
213+
const kind = code.startsWith("A") ? "added" : code.startsWith("D") ? "deleted" : "modified"
214+
status.set(file, kind)
215+
}
216+
199217
for await (const line of $`git -c core.autocrlf=false -c core.quotepath=false --git-dir ${git} --work-tree ${Instance.worktree} diff --no-ext-diff --no-renames --numstat ${from} ${to} -- .`
200218
.quiet()
201219
.cwd(Instance.directory)
@@ -224,6 +242,7 @@ export namespace Snapshot {
224242
after,
225243
additions: Number.isFinite(added) ? added : 0,
226244
deletions: Number.isFinite(deleted) ? deleted : 0,
245+
status: status.get(file) ?? "modified",
227246
})
228247
}
229248
return result

packages/opencode/test/snapshot/snapshot.test.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -749,6 +749,52 @@ test("revert preserves file that existed in snapshot when deleted then recreated
749749
})
750750
})
751751

752+
test("diffFull sets status based on git change type", async () => {
753+
await using tmp = await bootstrap()
754+
await Instance.provide({
755+
directory: tmp.path,
756+
fn: async () => {
757+
await Bun.write(`${tmp.path}/grow.txt`, "one\n")
758+
await Bun.write(`${tmp.path}/trim.txt`, "line1\nline2\n")
759+
await Bun.write(`${tmp.path}/delete.txt`, "gone")
760+
761+
const before = await Snapshot.track()
762+
expect(before).toBeTruthy()
763+
764+
await Bun.write(`${tmp.path}/grow.txt`, "one\ntwo\n")
765+
await Bun.write(`${tmp.path}/trim.txt`, "line1\n")
766+
await $`rm ${tmp.path}/delete.txt`.quiet()
767+
await Bun.write(`${tmp.path}/added.txt`, "new")
768+
769+
const after = await Snapshot.track()
770+
expect(after).toBeTruthy()
771+
772+
const diffs = await Snapshot.diffFull(before!, after!)
773+
expect(diffs.length).toBe(4)
774+
775+
const added = diffs.find((d) => d.file === "added.txt")
776+
expect(added).toBeDefined()
777+
expect(added!.status).toBe("added")
778+
779+
const deleted = diffs.find((d) => d.file === "delete.txt")
780+
expect(deleted).toBeDefined()
781+
expect(deleted!.status).toBe("deleted")
782+
783+
const grow = diffs.find((d) => d.file === "grow.txt")
784+
expect(grow).toBeDefined()
785+
expect(grow!.status).toBe("modified")
786+
expect(grow!.additions).toBeGreaterThan(0)
787+
expect(grow!.deletions).toBe(0)
788+
789+
const trim = diffs.find((d) => d.file === "trim.txt")
790+
expect(trim).toBeDefined()
791+
expect(trim!.status).toBe("modified")
792+
expect(trim!.additions).toBe(0)
793+
expect(trim!.deletions).toBeGreaterThan(0)
794+
},
795+
})
796+
})
797+
752798
test("diffFull with new file additions", async () => {
753799
await using tmp = await bootstrap()
754800
await Instance.provide({

packages/sdk/js/src/v2/gen/types.gen.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ export type FileDiff = {
9696
after: string
9797
additions: number
9898
deletions: number
99+
status?: "added" | "deleted" | "modified"
99100
}
100101

101102
export type UserMessage = {

0 commit comments

Comments
 (0)