Skip to content
142 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Merge branch 'worktree-agent-ac5b181a013e54348'1// The restore drill against a chain of real bundles: a full one and an
2// incremental one, cut the way the runner's backup mode cuts them
3// (crates/runner/src/backup.rs), from a local copy of the bucket.
4
5import assert from "node:assert/strict";
6import { execFileSync, spawnSync } from "node:child_process";
7import { createHash } from "node:crypto";
8import { mkdirSync, mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
9import { tmpdir } from "node:os";
10import { join } from "node:path";
11import { test } from "node:test";
12import { fileURLToPath } from "node:url";
13
14import { compareRefs, headBranch, parseRefs, readManifest, restore } from "./backup-restore-drill.mjs";
15
16const DRILL = fileURLToPath(new URL("./backup-restore-drill.mjs", import.meta.url));
17const git = (cwd, ...args) => execFileSync("git", args, { cwd, encoding: "utf8" }).trim();
18
19function commit(dir, file, text) {
20 writeFileSync(join(dir, file), text);
21 git(dir, "add", "--all");
Ops tests commit unsigned in their scratch repositories, whatever the developer's signing setup22 git(dir, "-c", "user.name=t", "-c", "user.email=t@example.com", "-c", "commit.gpgsign=false", "commit", "--quiet", "-m", text);
Merge branch 'worktree-agent-ac5b181a013e54348'23}
24
25function refsOf(dir) {
26 const refs = parseRefs(git(dir, "for-each-ref", "--format=%(objectname) %(refname)"));
27 refs.HEAD = git(dir, "rev-parse", "HEAD");
28 return refs;
29}
30
31/** A bundle of the mirror, leaving out `prerequisites`, as the runner cuts one. */
32function cut(mirror, prerequisites, out) {
33 for (const hash of prerequisites) git(mirror, "update-ref", `refs/g1t-backup-prerequisites/${hash}`, hash);
34 const not = prerequisites.length ? ["--not", "--glob=refs/g1t-backup-prerequisites/*"] : [];
35 git(mirror, "bundle", "create", "--quiet", out, "--exclude=refs/g1t-backup-prerequisites/*", "--all", ...not);
36 for (const hash of prerequisites) git(mirror, "update-ref", "-d", `refs/g1t-backup-prerequisites/${hash}`);
37}
38
39function entry(id, kind, key, file, refs, prerequisites) {
40 return {
41 id,
42 kind,
43 key,
44 created_at: "2026-10-06T02:53:00.000Z",
45 refs_version: 1,
46 refs,
47 prerequisites,
48 size: statSync(file).size,
49 sha256: createHash("sha256").update(readFileSync(file)).digest("hex"),
50 };
51}
52
53test("refs, HEAD and differences are read as git writes them", () => {
54 const a = "c71546fcd893ef8b0f57388b65e620d759705dda";
55 const b = "4807077b296e6edbf410d55e72749d3e1170c291";
56 assert.deepEqual(parseRefs(`${a}\tHEAD\n${a}\trefs/heads/main\n${b}\trefs/tags/v1\n${a}\trefs/tags/v1^{}\n`), {
57 HEAD: a,
58 "refs/heads/main": a,
59 "refs/tags/v1": b,
60 });
61 assert.equal(headBranch({ HEAD: a, "refs/heads/dev": a, "refs/heads/main": a }), "refs/heads/main");
62 assert.equal(headBranch({ HEAD: a, "refs/heads/dev": a }), "refs/heads/dev");
63 assert.deepEqual(compareRefs({ x: a, y: b }, { x: a, z: b }), [
64 { ref: "y", want: b, have: null },
65 { ref: "z", want: null, have: b },
66 ]);
67 assert.throws(() => readManifest(JSON.stringify({ version: 2, chain: [] })), /version 2/);
68 assert.throws(() => readManifest(JSON.stringify({ version: 1, chain: [{ kind: "incremental" }] })), /full/);
69});
70
71test("a chain restores every ref, and the drill fails once the live repository differs", () => {
72 const root = mkdtempSync(join(tmpdir(), "g1t-drill-test-"));
73 try {
74 const origin = join(root, "origin");
75 mkdirSync(origin);
76 git(origin, "init", "--quiet", "--initial-branch=main");
77 commit(origin, "a.txt", "one");
The three open dependency updates land together (#29, #33, #35): katex resolves to 0.19.0 everywhere, mermaid's nested copy included; query-string moves to 9.5.1, which takes decode-uri-component to 0.5.0; and the uuid that xcode locks moves from 7.0.3 to 11.1.1, which still loads under xcode's require. The ops tests' annotated tags carry their own git identity, so they run where git has none configured.78 git(origin, "-c", "user.name=t", "-c", "user.email=t@example.com", "tag", "-a", "v1", "-m", "v1");
Merge branch 'worktree-agent-ac5b181a013e54348'79 const mirror = join(root, "mirror.git");
80 const bucket = join(root, "bucket");
81 const dir = join(bucket, "backups", "repo_1");
82 mkdirSync(dir, { recursive: true });
83
84 git(root, "clone", "--mirror", "--quiet", origin, mirror);
85 const fullRefs = refsOf(mirror);
86 cut(mirror, [], join(dir, "1-full.bundle"));
87 const full = entry("1", "full", "backups/repo_1/1-full.bundle", join(dir, "1-full.bundle"), fullRefs, []);
88
89 commit(origin, "b.txt", "two");
90 git(origin, "branch", "feature");
91 git(origin, "tag", "-d", "v1");
92 rmSync(mirror, { recursive: true, force: true });
93 git(root, "clone", "--mirror", "--quiet", origin, mirror);
94 const prerequisites = [...new Set(Object.values(fullRefs))].sort();
95 cut(mirror, prerequisites, join(dir, "2-incr.bundle"));
96 const incr = entry("2", "incremental", "backups/repo_1/2-incr.bundle", join(dir, "2-incr.bundle"), refsOf(mirror), prerequisites);
97
98 const manifest = { version: 1, repo_id: "repo_1", store_key: "acme--rocket", path: null, updated_at: "", chain: [full, incr], previous: [] };
99 writeFileSync(join(dir, "manifest.json"), JSON.stringify(manifest, null, 2));
100
101 const run = () =>
102 spawnSync(process.execPath, [DRILL, "--repo-id", "repo_1", "--bundles", bucket, "--live", origin], { encoding: "utf8" });
103 const passed = run();
104 assert.equal(passed.status, 0, passed.stdout + passed.stderr);
105 assert.match(passed.stdout, /every ref matches the live repository/);
106
107 commit(origin, "c.txt", "three");
108 const failed = run();
109 assert.equal(failed.status, 1, failed.stdout + failed.stderr);
110 assert.match(failed.stdout, /refs differ from the live repository/);
111
112 // A bundle that is not what the manifest says is refused.
113 writeFileSync(join(dir, "2-incr.bundle"), "not a bundle");
114 const broken = run();
115 assert.equal(broken.status, 2, broken.stdout + broken.stderr);
116 assert.match(broken.stderr, /bytes, the manifest says|SHA-256/);
117 } finally {
118 rmSync(root, { recursive: true, force: true });
119 }
120});
121
122test("restore keeps only the refs the last entry names", async () => {
123 const root = mkdtempSync(join(tmpdir(), "g1t-drill-restore-"));
124 try {
125 const origin = join(root, "origin");
126 mkdirSync(origin);
127 git(origin, "init", "--quiet", "--initial-branch=main");
128 commit(origin, "a.txt", "one");
129 git(origin, "branch", "gone");
130 const mirror = join(root, "mirror.git");
131 git(root, "clone", "--mirror", "--quiet", origin, mirror);
132 const bundle = join(root, "1-full.bundle");
133 cut(mirror, [], bundle);
134 const refs = refsOf(mirror);
135 delete refs["refs/heads/gone"];
136 const manifest = { version: 1, chain: [entry("1", "full", "k", bundle, refs, [])] };
137 const restored = await restore(manifest, async () => bundle, join(root, "restored.git"), root);
138 assert.deepEqual(compareRefs(refs, restored), []);
139 } finally {
140 rmSync(root, { recursive: true, force: true });
141 }
142});

This file's history is long; its oldest lines are credited to the oldest commit read.