Skip to content
201 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 main into Artifacts Phase 21#!/usr/bin/env node
2// Pushes through the repos service with real git and checks that git sends
3// a pack without outside bases: the receive-pack advertisement says
4// `no-thin` (src/git_http.rs `with_no_thin`), git runs pack-objects without
5// `--thin`, and the service says the pack came whole (`thin;desc=no` in
6// Server-Timing) and read no bases from the store. Then the same into an
7// empty repository, whose advertisement is a `capabilities^{}` line.
8//
9// Runs everything on this machine, as clone-check.mjs does: the self-hosted
10// git store, and `wrangler dev` with dev/repos.jsonc, dev/artifacts.jsonc
11// and dev/stubs.jsonc (whose identity stub knows `dev`, the pusher). Build
12// first:
13//
14// cd services/repos && node ../../scripts/build-rust-worker.mjs
15// node dev/push-check.mjs
16//
17// GITSTORE_PORT and REPOS_PORT move it off 8799 and 8791. Needs node, git
18// (with git-http-backend) and the repository's npm packages.
19
20import { spawn, spawnSync } from "node:child_process";
21import { mkdtempSync, rmSync, writeFileSync, readFileSync } from "node:fs";
22import { tmpdir } from "node:os";
23import { dirname, join, resolve } from "node:path";
24import { fileURLToPath } from "node:url";
25import { createRequire } from "node:module";
26
27const here = dirname(fileURLToPath(import.meta.url));
28const service = resolve(here, "..");
29const root = resolve(service, "../..");
30const work = mkdtempSync(join(tmpdir(), "g1t-push-check-"));
31const persist = join(work, "state");
32const SECRET = "dev-gitstore-secret-0123";
33const STORE_PORT = process.env.GITSTORE_PORT ?? "8799";
34const REPOS_PORT = process.env.REPOS_PORT ?? "8791";
35const STORE = `http://localhost:${STORE_PORT}`;
36const REPOS = `http://localhost:${REPOS_PORT}`;
37// The pusher the identity stub knows (dev/stubs.js).
38const PUSHER = "dev:dev-push-secret";
39const ARTIFACTS_CONFIG = join(work, "artifacts.json");
40writeFileSync(
41 ARTIFACTS_CONFIG,
42 JSON.stringify({
43 name: "g1t-artifacts",
44 main: join(root, "deploy/self-host/workers/artifacts/index.js"),
45 compatibility_date: "2026-09-26",
46 vars: { GITSTORE_URL: STORE, GITSTORE_SECRET: SECRET },
47 }),
48);
49const children = [];
50const WRANGLER = join(dirname(createRequire(join(service, "package.json")).resolve("wrangler/package.json")), "bin/wrangler.js");
51
52function run(command, args, options = {}) {
53 const done = spawnSync(command, args, { encoding: "utf8", ...options });
54 if (done.status !== 0 && !options.allowFail) {
55 throw new Error(`${command} ${args.join(" ")} failed:\n${done.stdout}\n${done.stderr}`);
56 }
57 return done;
58}
59
60const git = (args, cwd = work, env = {}) => run("git", args, { cwd, env: { ...process.env, ...env } });
61const identity = ["-c", "user.name=dev", "-c", "user.email=dev@example.com"];
62
63function start(command, args, options) {
64 const child = spawn(command, args, { ...options });
65 children.push(child);
66 return child;
67}
68
69async function waitFor(url, what) {
70 for (let i = 0; i < 120; i++) {
71 try {
72 const response = await fetch(url);
73 if (response.status < 500) return;
74 } catch {}
75 await new Promise((resolve) => setTimeout(resolve, 500));
76 }
77 throw new Error(`${what} did not start`);
78}
79
80const sql = (command) =>
81 run("node", [WRANGLER, "d1", "execute", "g1t-repos", "--local", "--persist-to", persist, "-c", "dev/repos.jsonc", "--command", command], {
82 cwd: service,
83 env: { ...process.env, CI: "1" },
84 });
85
86const checks = [];
87function check(what, ok, detail = "") {
88 checks.push({ what, ok });
89 console.log(`${ok ? "ok " : "FAIL"} ${what}${detail ? ` (${detail})` : ""}`);
90}
91
92/** Pushes `dir`'s main through the service, with git's traces. */
93function push(label, dir, name) {
94 const trace = join(work, `${label}.trace`);
95 const done = git(["push", `http://${PUSHER}@localhost:${REPOS_PORT}/acme/${name}.git`, "main"], dir, {
96 GIT_TRACE: trace,
97 GIT_TRACE_PACKET: trace,
98 GIT_TRACE_CURL: trace,
99 GIT_TRACE_CURL_NO_DATA: "1",
100 });
101 const lines = readFileSync(trace, "utf8").split("\n");
102 // The first ref line, with the capabilities after its NUL (`\0` in the trace).
103 const advertised = lines.find((line) => /packet:.*< [0-9a-f]{40} \S+\\0/.test(line)) ?? "";
104 if (!advertised) console.log(lines.filter((line) => /report-status/.test(line)).slice(0, 3).join("\n"));
105 const packing = lines.find((line) => /run_command: git pack-objects/.test(line)) ?? "";
106 const timing = lines.filter((line) => /server-timing:/i.test(line) && /recv;dur=/.test(line)).join(" ");
107 check(`${label}: the advertisement says no-thin`, /\bno-thin\b/.test(advertised), `...${advertised.slice(-48)}`);
108 check(`${label}: git packs without --thin`, packing !== "" && !/--thin\b/.test(packing), packing.replace(/.*run_command: /, ""));
109 check(`${label}: the service says the pack came whole`, /thin;desc=no/.test(timing), (/thin;desc=\w+/.exec(timing) ?? ["no thin note"])[0]);
110 check(`${label}: no base was read`, /read;dur=\d+/.test(timing) && !/thin;desc=yes/.test(timing), (/read;dur=\d+/.exec(timing) ?? [""])[0]);
111 return done;
112}
113
114try {
115 start("node", [join(root, "deploy/self-host/gitstore/server.mjs")], {
116 env: { ...process.env, GITSTORE_ROOT: join(work, "git"), GITSTORE_SECRET: SECRET, GITSTORE_PORT: STORE_PORT, GITSTORE_URL: STORE },
117 stdio: "inherit",
118 });
119 await waitFor(`${STORE}/healthz`, "the git store");
120 const api = (path, body) =>
121 fetch(`${STORE}/api/repos${path}`, {
122 method: "POST",
123 headers: { "x-gitstore-secret": SECRET, "content-type": "application/json" },
124 body: JSON.stringify(body),
125 }).then((response) => response.json());
126 // acme/rocket: a large file, many versions of it, so a push changing one
127 // line of it would be a delta on the version the store has.
128 await api("", { name: "acme--rocket", defaultBranch: "main" });
129 await api("", { name: "acme--empty", defaultBranch: "main" });
130 const token = (await api("/acme--rocket/tokens", { scope: "write" })).plaintext;
131 const seed = join(work, "seed");
132 git(["init", "-q", "-b", "main", seed]);
133 const lines = Array.from({ length: 4000 }, (_, at) => `line ${at} of a file that changes a little at a time\n`);
134 for (let i = 1; i <= 3; i++) {
135 lines[i * 100] = `changed in commit ${i}\n`;
136 writeFileSync(join(seed, "big.txt"), lines.join(""));
137 git(["add", "."], seed);
138 git([...identity, "commit", "-q", "-m", `commit ${i}`], seed);
139 }
140 git(["-c", `http.extraHeader=Authorization: Bearer ${token}`, "push", "-q", `${STORE}/git/acme--rocket.git`, "main"], seed);
141 // The control: the store's own advertisement, without g1t in front,
142 // gets a thin pack for the same kind of change.
143 {
144 lines[3000] = "changed straight in the store\n";
145 writeFileSync(join(seed, "big.txt"), lines.join(""));
146 git([...identity, "commit", "-q", "-am", "straight"], seed);
147 const trace = join(work, "control.trace");
148 git(["-c", `http.extraHeader=Authorization: Bearer ${token}`, "push", "-q", `${STORE}/git/acme--rocket.git`, "main"], seed, { GIT_TRACE: trace });
149 const packing = readFileSync(trace, "utf8").split("\n").find((line) => /run_command: git pack-objects/.test(line)) ?? "";
150 check("control: straight to the store, git packs with --thin", /--thin\b/.test(packing), packing.replace(/.*run_command: /, ""));
151 }
152
153 run("node", [WRANGLER, "d1", "migrations", "apply", "g1t-repos", "--local", "--persist-to", persist, "-c", "dev/repos.jsonc"], {
154 cwd: service,
155 env: { ...process.env, CI: "1" },
156 });
157 sql(
158 "INSERT INTO repos (id, namespace, name, is_private, owner_id, default_branch, refs_version) VALUES " +
159 "('rep_rocket', 'acme', 'rocket', 0, 'usr_dev', 'main', 1), ('rep_empty', 'acme', 'empty', 0, 'usr_dev', 'main', 1)",
160 );
161 start(
162 "node",
163 [WRANGLER, "dev", "-c", "dev/repos.jsonc", "-c", ARTIFACTS_CONFIG, "-c", "dev/stubs.jsonc", "--local", "--persist-to", persist, "--port", REPOS_PORT],
164 { cwd: service, env: { ...process.env, CI: "1" }, stdio: ["ignore", "inherit", "inherit"] },
165 );
166 await waitFor(`${REPOS}/acme/rocket.git/info/refs?service=git-upload-pack`, "wrangler dev");
167
168 // One line of the big file changed: thin, the pack would be a small
169 // delta on the store's copy; whole, it carries the file.
170 const clone = join(work, "clone");
171 git(["clone", "-q", `${REPOS}/acme/rocket.git`, clone]);
172 lines[2000] = "changed by the push\n";
173 writeFileSync(join(clone, "big.txt"), lines.join(""));
174 git([...identity, "commit", "-q", "-am", "one line"], clone);
175 push("existing repository", clone, "rocket");
176 const pushed = git(["ls-remote", `${STORE}/git/acme--rocket.git`, "refs/heads/main"], work, {
177 GIT_CONFIG_COUNT: "1",
178 GIT_CONFIG_KEY_0: "http.extraHeader",
179 GIT_CONFIG_VALUE_0: `Authorization: Bearer ${token}`,
180 }).stdout.split(/\s/)[0];
181 const local = git(["rev-parse", "HEAD"], clone).stdout.trim();
182 check("existing repository: the store has the pushed commit", pushed === local, `${pushed} / ${local}`);
183
184 // An empty repository advertises `capabilities^{}`.
185 push("empty repository", clone, "empty");
186} catch (error) {
187 console.error(error);
188 checks.push({ what: "ran", ok: false });
189} finally {
190 for (const child of children) {
191 if (process.platform === "win32") spawnSync("taskkill", ["/pid", String(child.pid), "/t", "/f"], { stdio: "ignore" });
192 else child.kill();
193 }
194 try {
195 rmSync(work, { recursive: true, force: true });
196 } catch {}
197}
198
199const failed = checks.filter((c) => !c.ok);
200console.log(failed.length ? `\n${failed.length} of ${checks.length} checks failed.` : `\nAll ${checks.length} checks passed.`);
201process.exit(failed.length ? 1 : 0);

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