108 lines
4.2 KiB
JavaScript
108 lines
4.2 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import crypto from "node:crypto";
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import { execFileSync } from "node:child_process";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
|
const driftPath = path.join(root, "tests/golden/corrective/rehydration/M12-manifest-sha-drift.json");
|
|
const outputArgumentIndex = process.argv.indexOf("--out");
|
|
const outputRoot = path.resolve(root, outputArgumentIndex >= 0 ? process.argv[outputArgumentIndex + 1] : "tests/golden/corrective/rehydration/M12-git-history-snapshot");
|
|
const filesRoot = path.join(outputRoot, "files");
|
|
const drift = JSON.parse(fs.readFileSync(driftPath, "utf8"));
|
|
const sha256 = (bytes) => crypto.createHash("sha256").update(bytes).digest("hex");
|
|
const run = (args) => execFileSync("git", args, { cwd: root, encoding: "utf8", maxBuffer: 32 * 1024 * 1024 });
|
|
const uniqueEntries = new Map();
|
|
for (const entry of drift.entries) {
|
|
const key = `${entry.path}\0${entry.originalSha256}`;
|
|
if (!uniqueEntries.has(key)) uniqueEntries.set(key, entry);
|
|
}
|
|
|
|
function historicalBlob(pathname, expectedSha) {
|
|
let objectListing = "";
|
|
try {
|
|
objectListing = run(["rev-list", "--objects", "--all", "--", pathname]);
|
|
} catch {
|
|
return null;
|
|
}
|
|
const candidates = [...new Set(objectListing.split("\n").map((line) => line.split(" ")[0]).filter((id) => /^[0-9a-f]{40}$/u.test(id)))];
|
|
for (const blob of candidates) {
|
|
let type;
|
|
try {
|
|
type = run(["cat-file", "-t", blob]).trim();
|
|
} catch {
|
|
continue;
|
|
}
|
|
if (type !== "blob") continue;
|
|
const bytes = execFileSync("git", ["cat-file", "blob", blob], { cwd: root, maxBuffer: 64 * 1024 * 1024 });
|
|
if (sha256(bytes) !== expectedSha) continue;
|
|
let commits = [];
|
|
try {
|
|
commits = run(["log", "--all", "--format=%H", "--", pathname]).trim().split("\n").filter(Boolean);
|
|
} catch {
|
|
commits = [];
|
|
}
|
|
const commit = commits.find((candidate) => {
|
|
try {
|
|
return run(["rev-parse", `${candidate}:${pathname}`]).trim() === blob;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}) ?? null;
|
|
return { blob, commit, bytes };
|
|
}
|
|
return null;
|
|
}
|
|
|
|
const matched = [];
|
|
const unresolved = [];
|
|
const pathPairCounts = new Map();
|
|
for (const entry of uniqueEntries.values()) pathPairCounts.set(entry.path, (pathPairCounts.get(entry.path) ?? 0) + 1);
|
|
for (const entry of uniqueEntries.values()) {
|
|
const pathname = entry.path;
|
|
const result = historicalBlob(pathname, entry.originalSha256);
|
|
if (!result) {
|
|
unresolved.push({ path: pathname, originalSha256: entry.originalSha256, reason: "exact SHA-256 content was not found in Git history" });
|
|
continue;
|
|
}
|
|
const suffix = (pathPairCounts.get(pathname) ?? 0) > 1 ? `.${entry.originalSha256.slice(0, 16)}` : "";
|
|
const snapshotPath = path.join("files", `${pathname}${suffix}`);
|
|
const destination = path.join(outputRoot, snapshotPath);
|
|
fs.mkdirSync(path.dirname(destination), { recursive: true });
|
|
fs.writeFileSync(destination, result.bytes);
|
|
matched.push({
|
|
path: pathname,
|
|
snapshotPath,
|
|
sha256: entry.originalSha256,
|
|
bytes: result.bytes.length,
|
|
gitBlob: result.blob,
|
|
gitCommit: result.commit,
|
|
});
|
|
}
|
|
|
|
const repository = run(["remote", "get-url", "origin"]).trim();
|
|
const head = run(["rev-parse", "HEAD"]).trim();
|
|
const manifest = {
|
|
schemaVersion: 1,
|
|
mode: "IMMUTABLE_REPOSITORY",
|
|
authorized: true,
|
|
source: {
|
|
kind: "GIT_HISTORY",
|
|
repository,
|
|
head,
|
|
selection: "exact original SHA-256 content match; current worktree is never read",
|
|
},
|
|
coverage: {
|
|
requestedPaths: new Set([...uniqueEntries.values()].map((entry) => entry.path)).size,
|
|
requestedPairs: uniqueEntries.size,
|
|
matchedPaths: matched.length,
|
|
unmatchedPaths: unresolved.length,
|
|
unresolved,
|
|
},
|
|
files: matched.sort((a, b) => a.path.localeCompare(b.path)),
|
|
};
|
|
fs.mkdirSync(outputRoot, { recursive: true });
|
|
fs.writeFileSync(path.join(outputRoot, "snapshot-manifest.json"), `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
|
|
process.stdout.write(`m12-git-snapshot-ok requested=${uniqueEntries.size} matched=${matched.length} unmatched=${unresolved.length} output=${path.relative(root, outputRoot).replaceAll(path.sep, "/")}\n`);
|