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 | ||
| 5 | import assert from "node:assert/strict"; | |
| 6 | import { execFileSync, spawnSync } from "node:child_process"; | |
| 7 | import { createHash } from "node:crypto"; | |
| 8 | import { mkdirSync, mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs"; | |
| 9 | import { tmpdir } from "node:os"; | |
| 10 | import { join } from "node:path"; | |
| 11 | import { test } from "node:test"; | |
| 12 | import { fileURLToPath } from "node:url"; | |
| 13 | ||
| 14 | import { compareRefs, headBranch, parseRefs, readManifest, restore } from "./backup-restore-drill.mjs"; | |
| 15 | ||
| 16 | const DRILL = fileURLToPath(new URL("./backup-restore-drill.mjs", import.meta.url)); | |
| 17 | const git = (cwd, ...args) => execFileSync("git", args, { cwd, encoding: "utf8" }).trim(); | |
| 18 | ||
| 19 | function 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 setup | 22 | 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 | ||
| 25 | function 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. */ | |
| 32 | function 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 | ||
| 39 | function 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 | ||
| 53 | test("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 | ||
| 71 | test("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 | ||
| 122 | test("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.