fix(app): added/deleted file status now correctly calculated
This commit is contained in:
@@ -130,10 +130,57 @@ export default function FileTree(props: {
|
|||||||
const nodes = file.tree.children(props.path)
|
const nodes = file.tree.children(props.path)
|
||||||
const current = filter()
|
const current = filter()
|
||||||
if (!current) return nodes
|
if (!current) return nodes
|
||||||
return nodes.filter((node) => {
|
|
||||||
|
const parent = (path: string) => {
|
||||||
|
const idx = path.lastIndexOf("/")
|
||||||
|
if (idx === -1) return ""
|
||||||
|
return path.slice(0, idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
const leaf = (path: string) => {
|
||||||
|
const idx = path.lastIndexOf("/")
|
||||||
|
return idx === -1 ? path : path.slice(idx + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const out = nodes.filter((node) => {
|
||||||
if (node.type === "file") return current.files.has(node.path)
|
if (node.type === "file") return current.files.has(node.path)
|
||||||
return current.dirs.has(node.path)
|
return current.dirs.has(node.path)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const seen = new Set(out.map((node) => node.path))
|
||||||
|
|
||||||
|
for (const dir of current.dirs) {
|
||||||
|
if (parent(dir) !== props.path) continue
|
||||||
|
if (seen.has(dir)) continue
|
||||||
|
out.push({
|
||||||
|
name: leaf(dir),
|
||||||
|
path: dir,
|
||||||
|
absolute: dir,
|
||||||
|
type: "directory",
|
||||||
|
ignored: false,
|
||||||
|
})
|
||||||
|
seen.add(dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const item of current.files) {
|
||||||
|
if (parent(item) !== props.path) continue
|
||||||
|
if (seen.has(item)) continue
|
||||||
|
out.push({
|
||||||
|
name: leaf(item),
|
||||||
|
path: item,
|
||||||
|
absolute: item,
|
||||||
|
type: "file",
|
||||||
|
ignored: false,
|
||||||
|
})
|
||||||
|
seen.add(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
return out.toSorted((a, b) => {
|
||||||
|
if (a.type !== b.type) {
|
||||||
|
return a.type === "directory" ? -1 : 1
|
||||||
|
}
|
||||||
|
return a.name.localeCompare(b.name)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const Node = (
|
const Node = (
|
||||||
|
|||||||
@@ -500,9 +500,7 @@ export default function Page() {
|
|||||||
const out = new Map<string, "add" | "del" | "mix">()
|
const out = new Map<string, "add" | "del" | "mix">()
|
||||||
for (const diff of diffs()) {
|
for (const diff of diffs()) {
|
||||||
const file = normalize(diff.file)
|
const file = normalize(diff.file)
|
||||||
const add = diff.additions > 0
|
const kind = diff.status === "added" ? "add" : diff.status === "deleted" ? "del" : "mix"
|
||||||
const del = diff.deletions > 0
|
|
||||||
const kind = add && del ? "mix" : add ? "add" : del ? "del" : "mix"
|
|
||||||
|
|
||||||
out.set(file, kind)
|
out.set(file, kind)
|
||||||
|
|
||||||
|
|||||||
@@ -188,6 +188,7 @@ export namespace Snapshot {
|
|||||||
after: z.string(),
|
after: z.string(),
|
||||||
additions: z.number(),
|
additions: z.number(),
|
||||||
deletions: z.number(),
|
deletions: z.number(),
|
||||||
|
status: z.enum(["added", "deleted", "modified"]).optional(),
|
||||||
})
|
})
|
||||||
.meta({
|
.meta({
|
||||||
ref: "FileDiff",
|
ref: "FileDiff",
|
||||||
@@ -196,6 +197,23 @@ export namespace Snapshot {
|
|||||||
export async function diffFull(from: string, to: string): Promise<FileDiff[]> {
|
export async function diffFull(from: string, to: string): Promise<FileDiff[]> {
|
||||||
const git = gitdir()
|
const git = gitdir()
|
||||||
const result: FileDiff[] = []
|
const result: FileDiff[] = []
|
||||||
|
const status = new Map<string, "added" | "deleted" | "modified">()
|
||||||
|
|
||||||
|
const statuses =
|
||||||
|
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} -- .`
|
||||||
|
.quiet()
|
||||||
|
.cwd(Instance.directory)
|
||||||
|
.nothrow()
|
||||||
|
.text()
|
||||||
|
|
||||||
|
for (const line of statuses.trim().split("\n")) {
|
||||||
|
if (!line) continue
|
||||||
|
const [code, file] = line.split("\t")
|
||||||
|
if (!code || !file) continue
|
||||||
|
const kind = code.startsWith("A") ? "added" : code.startsWith("D") ? "deleted" : "modified"
|
||||||
|
status.set(file, kind)
|
||||||
|
}
|
||||||
|
|
||||||
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} -- .`
|
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} -- .`
|
||||||
.quiet()
|
.quiet()
|
||||||
.cwd(Instance.directory)
|
.cwd(Instance.directory)
|
||||||
@@ -224,6 +242,7 @@ export namespace Snapshot {
|
|||||||
after,
|
after,
|
||||||
additions: Number.isFinite(added) ? added : 0,
|
additions: Number.isFinite(added) ? added : 0,
|
||||||
deletions: Number.isFinite(deleted) ? deleted : 0,
|
deletions: Number.isFinite(deleted) ? deleted : 0,
|
||||||
|
status: status.get(file) ?? "modified",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -749,6 +749,52 @@ test("revert preserves file that existed in snapshot when deleted then recreated
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("diffFull sets status based on git change type", async () => {
|
||||||
|
await using tmp = await bootstrap()
|
||||||
|
await Instance.provide({
|
||||||
|
directory: tmp.path,
|
||||||
|
fn: async () => {
|
||||||
|
await Bun.write(`${tmp.path}/grow.txt`, "one\n")
|
||||||
|
await Bun.write(`${tmp.path}/trim.txt`, "line1\nline2\n")
|
||||||
|
await Bun.write(`${tmp.path}/delete.txt`, "gone")
|
||||||
|
|
||||||
|
const before = await Snapshot.track()
|
||||||
|
expect(before).toBeTruthy()
|
||||||
|
|
||||||
|
await Bun.write(`${tmp.path}/grow.txt`, "one\ntwo\n")
|
||||||
|
await Bun.write(`${tmp.path}/trim.txt`, "line1\n")
|
||||||
|
await $`rm ${tmp.path}/delete.txt`.quiet()
|
||||||
|
await Bun.write(`${tmp.path}/added.txt`, "new")
|
||||||
|
|
||||||
|
const after = await Snapshot.track()
|
||||||
|
expect(after).toBeTruthy()
|
||||||
|
|
||||||
|
const diffs = await Snapshot.diffFull(before!, after!)
|
||||||
|
expect(diffs.length).toBe(4)
|
||||||
|
|
||||||
|
const added = diffs.find((d) => d.file === "added.txt")
|
||||||
|
expect(added).toBeDefined()
|
||||||
|
expect(added!.status).toBe("added")
|
||||||
|
|
||||||
|
const deleted = diffs.find((d) => d.file === "delete.txt")
|
||||||
|
expect(deleted).toBeDefined()
|
||||||
|
expect(deleted!.status).toBe("deleted")
|
||||||
|
|
||||||
|
const grow = diffs.find((d) => d.file === "grow.txt")
|
||||||
|
expect(grow).toBeDefined()
|
||||||
|
expect(grow!.status).toBe("modified")
|
||||||
|
expect(grow!.additions).toBeGreaterThan(0)
|
||||||
|
expect(grow!.deletions).toBe(0)
|
||||||
|
|
||||||
|
const trim = diffs.find((d) => d.file === "trim.txt")
|
||||||
|
expect(trim).toBeDefined()
|
||||||
|
expect(trim!.status).toBe("modified")
|
||||||
|
expect(trim!.additions).toBe(0)
|
||||||
|
expect(trim!.deletions).toBeGreaterThan(0)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
test("diffFull with new file additions", async () => {
|
test("diffFull with new file additions", async () => {
|
||||||
await using tmp = await bootstrap()
|
await using tmp = await bootstrap()
|
||||||
await Instance.provide({
|
await Instance.provide({
|
||||||
|
|||||||
@@ -96,6 +96,7 @@ export type FileDiff = {
|
|||||||
after: string
|
after: string
|
||||||
additions: number
|
additions: number
|
||||||
deletions: number
|
deletions: number
|
||||||
|
status?: "added" | "deleted" | "modified"
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserMessage = {
|
export type UserMessage = {
|
||||||
|
|||||||
Reference in New Issue
Block a user