fix(vcs): avoid unbounded diff memory usage (#25581)

This commit is contained in:
Shoubhit Dash
2026-05-03 17:49:46 +05:30
committed by GitHub
parent 8299fb3e2b
commit d1f597b5b5
4 changed files with 338 additions and 71 deletions

View File

@@ -114,6 +114,53 @@ describe("Git", () => {
})
})
test("patch() returns capped native patch output", async () => {
await using tmp = await tmpdir({ git: true })
await fs.writeFile(path.join(tmp.path, weird), "before\n", "utf-8")
await fs.writeFile(path.join(tmp.path, "other.txt"), "old\n", "utf-8")
await $`git add .`.cwd(tmp.path).quiet()
await $`git commit --no-gpg-sign -m "add file"`.cwd(tmp.path).quiet()
await fs.writeFile(path.join(tmp.path, weird), "after\n", "utf-8")
await fs.writeFile(path.join(tmp.path, "other.txt"), "new\n", "utf-8")
await withGit(async (rt) => {
const [patch, all, capped] = await Promise.all([
rt.runPromise(Git.Service.use((git) => git.patch(tmp.path, "HEAD", weird, { context: 2_147_483_647 }))),
rt.runPromise(Git.Service.use((git) => git.patchAll(tmp.path, "HEAD", { context: 2_147_483_647 }))),
rt.runPromise(Git.Service.use((git) => git.patch(tmp.path, "HEAD", weird, { maxOutputBytes: 1 }))),
])
expect(patch.truncated).toBe(false)
expect(patch.text).toContain("diff --git")
expect(patch.text).toContain("-before")
expect(patch.text).toContain("+after")
expect(all.truncated).toBe(false)
expect(all.text).toContain("diff --git")
expect(all.text).toContain("other.txt")
expect(all.text).toContain("+new")
expect(capped.truncated).toBe(true)
expect(capped.text).toBe("")
})
})
test("patchUntracked() and statUntracked() handle added files", async () => {
await using tmp = await tmpdir({ git: true })
await fs.writeFile(path.join(tmp.path, weird), "one\ntwo\n", "utf-8")
await withGit(async (rt) => {
const [patch, stat] = await Promise.all([
rt.runPromise(Git.Service.use((git) => git.patchUntracked(tmp.path, weird, { context: 2_147_483_647 }))),
rt.runPromise(Git.Service.use((git) => git.statUntracked(tmp.path, weird))),
])
expect(patch.truncated).toBe(false)
expect(patch.text).toContain("diff --git")
expect(patch.text).toContain("+one")
expect(patch.text).toContain("+two")
expect(stat).toEqual(expect.objectContaining({ file: weird, additions: 2, deletions: 0 }))
})
})
test("show() returns empty text for binary blobs", async () => {
await using tmp = await tmpdir({ git: true })
await fs.writeFile(path.join(tmp.path, "bin.dat"), new Uint8Array([0, 1, 2, 3]))

View File

@@ -234,6 +234,7 @@ describe("Vcs diff", () => {
}),
]),
)
expect(diff.find((item) => item.file === "file.txt")?.patch).toContain("diff --git")
})
})
@@ -259,6 +260,34 @@ describe("Vcs diff", () => {
})
})
test("diff('git') keeps batched patches aligned for type changes", async () => {
if (process.platform === "win32") return
await using tmp = await tmpdir({ git: true })
await fs.writeFile(path.join(tmp.path, "a.txt"), "old\n", "utf-8")
await fs.writeFile(path.join(tmp.path, "b.txt"), "old\n", "utf-8")
await $`git add .`.cwd(tmp.path).quiet()
await $`git commit --no-gpg-sign -m "add files"`.cwd(tmp.path).quiet()
await fs.unlink(path.join(tmp.path, "a.txt"))
await fs.symlink("target", path.join(tmp.path, "a.txt"))
await fs.writeFile(path.join(tmp.path, "b.txt"), "new\n", "utf-8")
await withVcsOnly(tmp.path, async () => {
const diff = await AppRuntime.runPromise(
Effect.gen(function* () {
const vcs = yield* Vcs.Service
return yield* vcs.diff("git")
}),
)
const a = diff.find((item) => item.file === "a.txt")
const b = diff.find((item) => item.file === "b.txt")
expect(a?.patch).toContain("deleted file mode")
expect(a?.patch).toContain("new file mode")
expect(b?.patch).toContain("+new")
})
})
test("diff('branch') returns changes against default branch", async () => {
await using tmp = await tmpdir({ git: true })
await $`git branch -M main`.cwd(tmp.path).quiet()