1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
|
import { afterAll, beforeAll, describe, expect, it } from "bun:test";
import { mkdir, rm, writeFile } from "fs/promises";
import { join } from "path";
import { tmpdir } from "os";
import { initBareRepo, getRepoPath } from "../git/repository";
import { autoRepair } from "../lib/autorepair";
const GIT_REPOS_PATH = join(tmpdir(), "gluecron-autorepair-tree-" + Date.now());
const OWNER = "acme";
const REPO = "widgets";
let barePath: string;
async function git(args: string[], cwd?: string): Promise<{ stdout: string; exitCode: number }> {
const proc = Bun.spawn(["git", ...args], { cwd, stdout: "pipe", stderr: "pipe" });
const stdout = (await new Response(proc.stdout).text()).trim();
const exitCode = await proc.exited;
return { stdout, exitCode };
}
beforeAll(async () => {
await mkdir(GIT_REPOS_PATH, { recursive: true });
process.env.GIT_REPOS_PATH = GIT_REPOS_PATH;
barePath = await initBareRepo(OWNER, REPO);
expect(barePath).toBe(getRepoPath(OWNER, REPO));
const workDir = join(GIT_REPOS_PATH, "_seed-work");
await mkdir(join(workDir, "src", "lib"), { recursive: true });
await mkdir(join(workDir, "src", "routes"), { recursive: true });
await mkdir(join(workDir, "docs"), { recursive: true });
await writeFile(join(workDir, ".gitignore"), "node_modules/\n");
await writeFile(join(workDir, "package.json"), JSON.stringify({ name: REPO }, null, 2) + "\n");
await writeFile(join(workDir, "src", "lib", "foo.ts"), "export const foo = 1; \n");
await writeFile(join(workDir, "src", "routes", "bar.ts"), "export const bar = 2;\n");
await writeFile(join(workDir, "docs", "notes.md"), "# Notes\n\nNothing to see here.\n");
await writeFile(join(workDir, "README.md"), "# widgets\n");
const gitArgs = (...args: string[]) => [`--git-dir=${barePath}`, `--work-tree=${workDir}`, ...args];
await git(gitArgs("add", "-A"), workDir);
const commit = await git(
gitArgs("-c", "user.email=test@test.com", "-c", "user.name=Test", "commit", "-m", "initial"),
workDir
);
expect(commit.exitCode).toBe(0);
await rm(workDir, { recursive: true, force: true });
}, 30000);
afterAll(async () => {
delete process.env.GIT_REPOS_PATH;
await rm(GIT_REPOS_PATH, { recursive: true, force: true }).catch(() => {});
});
describe("autoRepair — tree integrity after a repair", () => {
it("keeps every nested file after committing a repair (2026-07-16 regression)", async () => {
const before = await git(["ls-tree", "-r", "--name-only", "main"], barePath);
expect(before.exitCode).toBe(0);
const beforeFiles = before.stdout.split("\n").filter(Boolean).sort();
expect(beforeFiles).toContain("src/lib/foo.ts");
expect(beforeFiles).toContain("src/routes/bar.ts");
expect(beforeFiles).toContain("docs/notes.md");
const result = await autoRepair(OWNER, REPO, "main");
expect(result.repaired).toBe(true);
expect(result.commitSha).not.toBeNull();
expect(result.repairs.length).toBeGreaterThanOrEqual(2);
const after = await git(["ls-tree", "-r", "--name-only", "main"], barePath);
expect(after.exitCode).toBe(0);
const afterFiles = after.stdout.split("\n").filter(Boolean).sort();
for (const f of beforeFiles) {
expect(afterFiles).toContain(f);
}
const bar = await git(["show", "main:src/routes/bar.ts"], barePath);
expect(bar.stdout).toBe("export const bar = 2;");
const notes = await git(["show", "main:docs/notes.md"], barePath);
expect(notes.stdout).toBe("# Notes\n\nNothing to see here.");
const foo = await git(["show", "main:src/lib/foo.ts"], barePath);
expect(foo.stdout).toBe("export const foo = 1;");
const gitignore = await git(["show", "main:.gitignore"], barePath);
expect(gitignore.stdout).toContain(".env");
}, 30000);
it("makes no commit at all if a git subprocess fails (fail-closed)", async () => {
const result = await autoRepair(OWNER, REPO, "no-such-branch");
expect(result.repaired).toBe(false);
expect(result.commitSha).toBeNull();
}, 15000);
});
|