hillclimb 0.9.5 → 0.9.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.js +421 -59
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -15183,13 +15183,14 @@ import os9 from "os";
|
|
|
15183
15183
|
import path19 from "path";
|
|
15184
15184
|
|
|
15185
15185
|
// src/git-traces/git-ops.ts
|
|
15186
|
-
import { execFileSync as execFileSync2, spawn as spawn2, spawnSync } from "child_process";
|
|
15186
|
+
import { execFileSync as execFileSync2, spawn as spawn2, spawnSync as spawnSync2 } from "child_process";
|
|
15187
15187
|
import fs18 from "fs";
|
|
15188
15188
|
import os7 from "os";
|
|
15189
15189
|
import path17 from "path";
|
|
15190
15190
|
import { createGzip } from "zlib";
|
|
15191
15191
|
|
|
15192
15192
|
// src/git-traces/object-store-probe.ts
|
|
15193
|
+
import { spawnSync } from "child_process";
|
|
15193
15194
|
import fs17 from "fs";
|
|
15194
15195
|
import path16 from "path";
|
|
15195
15196
|
var probedRepos = /* @__PURE__ */ new Set();
|
|
@@ -15197,7 +15198,16 @@ var SAMPLE_FANOUT_DIRS = 8;
|
|
|
15197
15198
|
var SAMPLE_READS = 20;
|
|
15198
15199
|
var SAMPLE_READ_BYTES = 4096;
|
|
15199
15200
|
var MAX_NAMED_OBJECTS = 3;
|
|
15200
|
-
|
|
15201
|
+
var MAX_LOCATE_BLOBS = 2e4;
|
|
15202
|
+
var MAX_STALLED_PATH_CHARS = 120;
|
|
15203
|
+
var OBJECT_SHA = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
|
|
15204
|
+
var gitLimits = {
|
|
15205
|
+
versionMs: 2e3,
|
|
15206
|
+
listMs: 1e4,
|
|
15207
|
+
readMs: 15e3,
|
|
15208
|
+
headMs: 5e3
|
|
15209
|
+
};
|
|
15210
|
+
function probeObjectStore(repoRoot, trigger, stderr, args = []) {
|
|
15201
15211
|
if (probedRepos.has(repoRoot)) return;
|
|
15202
15212
|
probedRepos.add(repoRoot);
|
|
15203
15213
|
const started = Date.now();
|
|
@@ -15226,13 +15236,19 @@ function probeObjectStore(repoRoot, trigger, stderr) {
|
|
|
15226
15236
|
`alternates=${fs17.existsSync(path16.join(objectsDir, "info", "alternates")) ? "yes" : "no"}`
|
|
15227
15237
|
);
|
|
15228
15238
|
fields.push(...timeSampleReads(sample));
|
|
15229
|
-
|
|
15239
|
+
const named = namedObjects(stderr);
|
|
15240
|
+
for (const sha of named) {
|
|
15230
15241
|
const loose = path16.join(objectsDir, sha.slice(0, 2), sha.slice(2));
|
|
15231
15242
|
const stats = fs17.statSync(loose, { throwIfNoEntry: false });
|
|
15232
15243
|
fields.push(
|
|
15233
15244
|
stats ? `named=${sha.slice(0, 8)}(size=${stats.size},blocks=${stats.blocks})` : `named=${sha.slice(0, 8)}(no loose file)`
|
|
15234
15245
|
);
|
|
15235
15246
|
}
|
|
15247
|
+
fields.push(...gitVersion(repoRoot));
|
|
15248
|
+
locateGitRead(fields, repoRoot, objectsDir, failedTreeish(args), [
|
|
15249
|
+
...sample.map(sampleSha).filter((sha) => !!sha),
|
|
15250
|
+
...named
|
|
15251
|
+
]);
|
|
15236
15252
|
} catch (err) {
|
|
15237
15253
|
fields.push(
|
|
15238
15254
|
`probeError=${err instanceof Error ? err.message : String(err)}`
|
|
@@ -15285,6 +15301,7 @@ function timeSampleReads(sample) {
|
|
|
15285
15301
|
const readStarted = performance.now();
|
|
15286
15302
|
try {
|
|
15287
15303
|
const stats = fs17.statSync(file);
|
|
15304
|
+
if (!stats.isFile()) continue;
|
|
15288
15305
|
if (stats.size > 0 && stats.blocks === 0) dataless++;
|
|
15289
15306
|
const fd = fs17.openSync(file, "r");
|
|
15290
15307
|
try {
|
|
@@ -15314,6 +15331,121 @@ function namedObjects(stderr) {
|
|
|
15314
15331
|
const shas = new Set(stderr?.match(/\b[0-9a-f]{40}\b/g) ?? []);
|
|
15315
15332
|
return [...shas].slice(0, MAX_NAMED_OBJECTS);
|
|
15316
15333
|
}
|
|
15334
|
+
function sampleSha(file) {
|
|
15335
|
+
const sha = path16.basename(path16.dirname(file)) + path16.basename(file);
|
|
15336
|
+
return OBJECT_SHA.test(sha) ? sha : null;
|
|
15337
|
+
}
|
|
15338
|
+
function failedTreeish(args) {
|
|
15339
|
+
for (const arg of args.slice(1).reverse()) {
|
|
15340
|
+
if (OBJECT_SHA.test(arg) || arg.startsWith("refs/")) return arg;
|
|
15341
|
+
}
|
|
15342
|
+
return null;
|
|
15343
|
+
}
|
|
15344
|
+
function probeGit(repoRoot, args, timeout, input) {
|
|
15345
|
+
return spawnSync("git", args, {
|
|
15346
|
+
cwd: repoRoot,
|
|
15347
|
+
input,
|
|
15348
|
+
timeout,
|
|
15349
|
+
killSignal: "SIGKILL",
|
|
15350
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
15351
|
+
windowsHide: true,
|
|
15352
|
+
// Never prompt or lazily fetch from inside a diagnostic.
|
|
15353
|
+
env: { ...process.env, GIT_TERMINAL_PROMPT: "0", GIT_NO_LAZY_FETCH: "1" }
|
|
15354
|
+
});
|
|
15355
|
+
}
|
|
15356
|
+
function gitVersion(repoRoot) {
|
|
15357
|
+
const started = performance.now();
|
|
15358
|
+
const result = probeGit(repoRoot, ["--version"], gitLimits.versionMs);
|
|
15359
|
+
const version2 = result.stdout?.toString().trim().replace(/^git version /, "");
|
|
15360
|
+
return [
|
|
15361
|
+
`gitVersion=${result.status === 0 && version2 ? version2 : `unavailable(${result.error ? result.error.code : result.status})`}`,
|
|
15362
|
+
`gitSpawnMs=${(performance.now() - started).toFixed(0)}`
|
|
15363
|
+
];
|
|
15364
|
+
}
|
|
15365
|
+
function locateGitRead(fields, repoRoot, objectsDir, treeish, fallback) {
|
|
15366
|
+
let targets = null;
|
|
15367
|
+
if (treeish) {
|
|
15368
|
+
const listed = probeGit(
|
|
15369
|
+
repoRoot,
|
|
15370
|
+
["ls-tree", "-r", "-z", treeish],
|
|
15371
|
+
gitLimits.listMs
|
|
15372
|
+
);
|
|
15373
|
+
if (listed.status === 0 && listed.stdout) {
|
|
15374
|
+
targets = treeBlobs(listed.stdout.toString());
|
|
15375
|
+
fields.push(
|
|
15376
|
+
`gitReadTarget=tree:${OBJECT_SHA.test(treeish) ? treeish.slice(0, 8) : treeish}`,
|
|
15377
|
+
`treeBlobs=${targets.length}`
|
|
15378
|
+
);
|
|
15379
|
+
targets = targets.slice(0, MAX_LOCATE_BLOBS);
|
|
15380
|
+
} else {
|
|
15381
|
+
fields.push("gitReadTarget=sample", `lsTreeError=${failureText(listed)}`);
|
|
15382
|
+
}
|
|
15383
|
+
} else {
|
|
15384
|
+
fields.push("gitReadTarget=sample");
|
|
15385
|
+
}
|
|
15386
|
+
targets ??= [...new Set(fallback)].map((sha) => ({ sha }));
|
|
15387
|
+
if (targets.length === 0) return;
|
|
15388
|
+
const started = performance.now();
|
|
15389
|
+
const read = probeGit(
|
|
15390
|
+
repoRoot,
|
|
15391
|
+
["cat-file", "--batch-check=%(objectname) %(objecttype) %(objectsize)"],
|
|
15392
|
+
gitLimits.readMs,
|
|
15393
|
+
`${targets.map((target) => target.sha).join("\n")}
|
|
15394
|
+
`
|
|
15395
|
+
);
|
|
15396
|
+
const answered = (read.stdout?.toString() ?? "").split("\n").slice(0, -1);
|
|
15397
|
+
fields.push(
|
|
15398
|
+
`gitRead=${answered.length}/${targets.length}`,
|
|
15399
|
+
`gitReadMs=${(performance.now() - started).toFixed(1)}`,
|
|
15400
|
+
`gitMissing=${answered.filter((line) => line.endsWith(" missing")).length}`
|
|
15401
|
+
);
|
|
15402
|
+
const stalled = targets[answered.length];
|
|
15403
|
+
if (!stalled) return;
|
|
15404
|
+
fields.push(
|
|
15405
|
+
read.error?.code === "ETIMEDOUT" ? `gitReadTimeout=${gitLimits.readMs}` : `gitReadError=${failureText(read)}`,
|
|
15406
|
+
`gitStalledOn=${stalled.sha.slice(0, 8)}`
|
|
15407
|
+
);
|
|
15408
|
+
if (stalled.path)
|
|
15409
|
+
fields.push(
|
|
15410
|
+
`stalledPath=${JSON.stringify(stalled.path.slice(0, MAX_STALLED_PATH_CHARS))}`
|
|
15411
|
+
);
|
|
15412
|
+
const loose = path16.join(
|
|
15413
|
+
objectsDir,
|
|
15414
|
+
stalled.sha.slice(0, 2),
|
|
15415
|
+
stalled.sha.slice(2)
|
|
15416
|
+
);
|
|
15417
|
+
const stats = fs17.statSync(loose, { throwIfNoEntry: false });
|
|
15418
|
+
fields.push(
|
|
15419
|
+
stats ? `stalledLoose=size=${stats.size},blocks=${stats.blocks}` : "stalledLoose=no"
|
|
15420
|
+
);
|
|
15421
|
+
if (stats && process.platform !== "win32") {
|
|
15422
|
+
const headStarted = performance.now();
|
|
15423
|
+
const head = spawnSync("head", ["-c", "4096", loose], {
|
|
15424
|
+
timeout: gitLimits.headMs,
|
|
15425
|
+
killSignal: "SIGKILL"
|
|
15426
|
+
});
|
|
15427
|
+
fields.push(
|
|
15428
|
+
head.error?.code === "ETIMEDOUT" ? `headTimeout=${gitLimits.headMs}` : `headMs=${(performance.now() - headStarted).toFixed(0)}`
|
|
15429
|
+
);
|
|
15430
|
+
}
|
|
15431
|
+
}
|
|
15432
|
+
function treeBlobs(listing) {
|
|
15433
|
+
const blobs = /* @__PURE__ */ new Map();
|
|
15434
|
+
for (const entry of listing.split("\0")) {
|
|
15435
|
+
const tab = entry.indexOf(" ");
|
|
15436
|
+
if (tab < 0) continue;
|
|
15437
|
+
const [, type, sha] = entry.slice(0, tab).split(" ");
|
|
15438
|
+
if (type === "blob" && sha && !blobs.has(sha))
|
|
15439
|
+
blobs.set(sha, entry.slice(tab + 1));
|
|
15440
|
+
}
|
|
15441
|
+
return [...blobs].map(([sha, blobPath]) => ({ sha, path: blobPath }));
|
|
15442
|
+
}
|
|
15443
|
+
function failureText(result) {
|
|
15444
|
+
const error = result.error;
|
|
15445
|
+
if (error) return error.code ?? error.message;
|
|
15446
|
+
const stderr = result.stderr?.toString().trim().split("\n")[0];
|
|
15447
|
+
return stderr || `exit=${result.status}`;
|
|
15448
|
+
}
|
|
15317
15449
|
|
|
15318
15450
|
// src/git-traces/git-ops.ts
|
|
15319
15451
|
var DEFAULT_GIT_COMMAND_TIMEOUT_MS = 12e4;
|
|
@@ -15514,7 +15646,7 @@ function isBinaryBuffer(buffer) {
|
|
|
15514
15646
|
}
|
|
15515
15647
|
function readTreeBlobHead(repoRoot, sha) {
|
|
15516
15648
|
try {
|
|
15517
|
-
const { stdout } =
|
|
15649
|
+
const { stdout } = spawnSync2("git", ["cat-file", "blob", sha], {
|
|
15518
15650
|
cwd: repoRoot,
|
|
15519
15651
|
timeout: GIT_COMMAND_TIMEOUT_MS,
|
|
15520
15652
|
maxBuffer: BINARY_SNIFF_BYTES,
|
|
@@ -15634,6 +15766,7 @@ function isGitTimeoutError(err) {
|
|
|
15634
15766
|
}
|
|
15635
15767
|
function gitBuffer(repoRoot, args, options = {}) {
|
|
15636
15768
|
const timeout = options.timeoutMs ?? EXEC_OPTS.timeout;
|
|
15769
|
+
const detached = options.killProcessGroup && process.platform !== "win32";
|
|
15637
15770
|
const started = Date.now();
|
|
15638
15771
|
try {
|
|
15639
15772
|
return execFileSync2("git", args, {
|
|
@@ -15642,15 +15775,25 @@ function gitBuffer(repoRoot, args, options = {}) {
|
|
|
15642
15775
|
...options.input !== void 0 ? { input: options.input } : {},
|
|
15643
15776
|
stdio: ["pipe", "pipe", "pipe"],
|
|
15644
15777
|
...EXEC_OPTS,
|
|
15645
|
-
timeout
|
|
15778
|
+
timeout,
|
|
15779
|
+
// Not in the sync typings, but spawnSync honors it (setsid).
|
|
15780
|
+
...detached ? { detached: true } : {}
|
|
15646
15781
|
});
|
|
15647
15782
|
} catch (err) {
|
|
15783
|
+
const pid = err.pid;
|
|
15784
|
+
if (detached && pid) {
|
|
15785
|
+
try {
|
|
15786
|
+
process.kill(-pid, "SIGKILL");
|
|
15787
|
+
} catch {
|
|
15788
|
+
}
|
|
15789
|
+
}
|
|
15648
15790
|
const wrapped = createGitError(args, err, timeout, Date.now() - started);
|
|
15649
|
-
if (wrapped.isGitTimeout || wrapped.isGitMissingObject) {
|
|
15791
|
+
if (wrapped.isGitTimeout || wrapped.isGitMissingObject && !options.expectMissingObjects) {
|
|
15650
15792
|
probeObjectStore(
|
|
15651
15793
|
repoRoot,
|
|
15652
15794
|
wrapped.isGitTimeout ? "timeout" : "missing-object",
|
|
15653
|
-
wrapped.gitStderr
|
|
15795
|
+
wrapped.gitStderr,
|
|
15796
|
+
args
|
|
15654
15797
|
);
|
|
15655
15798
|
}
|
|
15656
15799
|
throw wrapped;
|
|
@@ -15783,7 +15926,7 @@ function captureWorkingCommitShaOnIndex(repoRoot, indexEnv) {
|
|
|
15783
15926
|
function hasUnbornHead(repoRoot) {
|
|
15784
15927
|
const ref = git(repoRoot, ["symbolic-ref", "--quiet", "HEAD"]);
|
|
15785
15928
|
const args = ["show-ref", "--verify", "--quiet", ref];
|
|
15786
|
-
const result =
|
|
15929
|
+
const result = spawnSync2("git", args, { cwd: repoRoot, ...EXEC_OPTS });
|
|
15787
15930
|
if (result.error) throw createGitError(args, result.error);
|
|
15788
15931
|
return result.status === 1;
|
|
15789
15932
|
}
|
|
@@ -15802,20 +15945,99 @@ function deleteRef(repoRoot, refName) {
|
|
|
15802
15945
|
function captureSnapshotSha(repoRoot) {
|
|
15803
15946
|
return captureWorkingCommitSha(repoRoot);
|
|
15804
15947
|
}
|
|
15805
|
-
|
|
15806
|
-
|
|
15948
|
+
var OBJECT_SHA2 = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
|
|
15949
|
+
function gitExpectingMissing(repoRoot, args) {
|
|
15950
|
+
return gitBuffer(repoRoot, args, { expectMissingObjects: true }).toString("utf-8").trim();
|
|
15951
|
+
}
|
|
15952
|
+
function commitTreeSha(repoRoot, commit) {
|
|
15953
|
+
const tree = gitExpectingMissing(repoRoot, [
|
|
15954
|
+
"rev-list",
|
|
15955
|
+
"--no-walk",
|
|
15956
|
+
"--format=%T",
|
|
15957
|
+
commit,
|
|
15958
|
+
"--"
|
|
15959
|
+
]).split("\n").at(-1);
|
|
15960
|
+
return tree && OBJECT_SHA2.test(tree) ? tree : null;
|
|
15961
|
+
}
|
|
15962
|
+
function listUnsharedObjects(repoRoot, treeSha, baseTrees) {
|
|
15963
|
+
const out = gitExpectingMissing(repoRoot, [
|
|
15964
|
+
"rev-list",
|
|
15965
|
+
"--objects",
|
|
15966
|
+
treeSha,
|
|
15967
|
+
...baseTrees.map((base) => `^${base}`),
|
|
15968
|
+
"--"
|
|
15969
|
+
]);
|
|
15970
|
+
const shas = /* @__PURE__ */ new Set();
|
|
15971
|
+
for (const line of out.split("\n")) {
|
|
15972
|
+
const sha = line.split(" ", 1)[0];
|
|
15973
|
+
if (OBJECT_SHA2.test(sha)) shas.add(sha);
|
|
15974
|
+
}
|
|
15975
|
+
return [...shas];
|
|
15976
|
+
}
|
|
15977
|
+
function walkBaseTree(repoRoot, tree) {
|
|
15978
|
+
gitExpectingMissing(repoRoot, ["rev-list", "--objects", `^${tree}`, "--"]);
|
|
15979
|
+
}
|
|
15980
|
+
function blobSizeTotals(repoRoot, shas) {
|
|
15981
|
+
const totals = { bytes: 0, blobs: 0, unreadable: 0 };
|
|
15982
|
+
for (const object of batchCheckObjects(repoRoot, shas)) {
|
|
15983
|
+
if (object.type === "missing") totals.unreadable++;
|
|
15984
|
+
else if (object.type === "blob" && object.size !== null) {
|
|
15985
|
+
totals.bytes += object.size;
|
|
15986
|
+
totals.blobs++;
|
|
15987
|
+
}
|
|
15988
|
+
}
|
|
15989
|
+
return totals;
|
|
15990
|
+
}
|
|
15991
|
+
var gitVersionCache;
|
|
15992
|
+
function gitVersion2(repoRoot) {
|
|
15993
|
+
if (gitVersionCache !== void 0) return gitVersionCache;
|
|
15994
|
+
try {
|
|
15995
|
+
const match = /git version (\d+)\.(\d+)(?:\.(\d+))?/.exec(
|
|
15996
|
+
git(repoRoot, ["--version"])
|
|
15997
|
+
);
|
|
15998
|
+
gitVersionCache = match ? [Number(match[1]), Number(match[2]), Number(match[3] ?? 0)] : null;
|
|
15999
|
+
} catch {
|
|
16000
|
+
gitVersionCache = null;
|
|
16001
|
+
}
|
|
16002
|
+
return gitVersionCache;
|
|
16003
|
+
}
|
|
16004
|
+
function gitExcludesNegativeTrees(repoRoot) {
|
|
16005
|
+
const version2 = gitVersion2(repoRoot);
|
|
16006
|
+
if (!version2) return true;
|
|
16007
|
+
const [major, minor, patch] = version2;
|
|
16008
|
+
return major > 1 || minor > 8 || minor === 8 && patch >= 5;
|
|
16009
|
+
}
|
|
16010
|
+
function treeBlobSizes(repoRoot, treeSha) {
|
|
16011
|
+
const sizes = /* @__PURE__ */ new Map();
|
|
16012
|
+
for (const entry of lsTreeEntries(repoRoot, ["-l"], treeSha)) {
|
|
16013
|
+
if (entry.type === "blob" && !sizes.has(entry.sha))
|
|
16014
|
+
sizes.set(
|
|
16015
|
+
entry.sha,
|
|
16016
|
+
/^\d+$/.test(entry.size) ? Number(entry.size) : null
|
|
16017
|
+
);
|
|
16018
|
+
}
|
|
16019
|
+
return sizes;
|
|
16020
|
+
}
|
|
16021
|
+
function treeBlobShas(repoRoot, treeSha) {
|
|
16022
|
+
const shas = /* @__PURE__ */ new Set();
|
|
16023
|
+
for (const entry of lsTreeEntries(repoRoot, [], treeSha)) {
|
|
16024
|
+
if (entry.type === "blob") shas.add(entry.sha);
|
|
16025
|
+
}
|
|
16026
|
+
return shas;
|
|
16027
|
+
}
|
|
16028
|
+
function lsTreeEntries(repoRoot, flags, treeSha) {
|
|
16029
|
+
const entries = [];
|
|
15807
16030
|
for (const entry of gitBuffer(repoRoot, [
|
|
15808
16031
|
"ls-tree",
|
|
15809
16032
|
"-r",
|
|
15810
|
-
|
|
16033
|
+
...flags,
|
|
15811
16034
|
"-z",
|
|
15812
|
-
|
|
16035
|
+
treeSha
|
|
15813
16036
|
]).toString("utf-8").split("\0")) {
|
|
15814
|
-
const [, type, sha, size] = entry.slice(0, entry.indexOf(" ")).trim().split(/\s+/);
|
|
15815
|
-
if (type
|
|
15816
|
-
blobs.set(sha, Number(size));
|
|
16037
|
+
const [, type, sha, size = ""] = entry.slice(0, entry.indexOf(" ")).trim().split(/\s+/);
|
|
16038
|
+
if (type && sha) entries.push({ type, sha, size });
|
|
15817
16039
|
}
|
|
15818
|
-
return
|
|
16040
|
+
return entries;
|
|
15819
16041
|
}
|
|
15820
16042
|
function formatSize(bytes) {
|
|
15821
16043
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
@@ -15887,20 +16109,28 @@ function parseDiffTreeRawZ(output) {
|
|
|
15887
16109
|
}
|
|
15888
16110
|
function lookupBlobSizes(repoRoot, shas) {
|
|
15889
16111
|
const sizes = /* @__PURE__ */ new Map();
|
|
15890
|
-
|
|
16112
|
+
for (const object of batchCheckObjects(repoRoot, shas)) {
|
|
16113
|
+
if (object.type === "blob" && object.size !== null)
|
|
16114
|
+
sizes.set(object.sha, object.size);
|
|
16115
|
+
}
|
|
16116
|
+
return sizes;
|
|
16117
|
+
}
|
|
16118
|
+
function batchCheckObjects(repoRoot, shas) {
|
|
16119
|
+
if (shas.length === 0) return [];
|
|
15891
16120
|
const out = gitBuffer(
|
|
15892
16121
|
repoRoot,
|
|
15893
16122
|
["cat-file", "--batch-check=%(objectname) %(objecttype) %(objectsize)"],
|
|
15894
16123
|
{ input: `${shas.join("\n")}
|
|
15895
16124
|
` }
|
|
15896
16125
|
).toString("utf-8");
|
|
16126
|
+
const objects = [];
|
|
15897
16127
|
for (const line of out.split("\n")) {
|
|
15898
16128
|
const [sha, type, sizeRaw] = line.split(" ");
|
|
15899
|
-
if (
|
|
16129
|
+
if (!sha || !type) continue;
|
|
15900
16130
|
const size = Number.parseInt(sizeRaw, 10);
|
|
15901
|
-
|
|
16131
|
+
objects.push({ sha, type, size: Number.isFinite(size) ? size : null });
|
|
15902
16132
|
}
|
|
15903
|
-
return
|
|
16133
|
+
return objects;
|
|
15904
16134
|
}
|
|
15905
16135
|
function sortOmittedByPath(files) {
|
|
15906
16136
|
return [...files].sort(
|
|
@@ -16276,7 +16506,8 @@ function createBundleFromTree(repoRoot, treeSha, sessionId, label) {
|
|
|
16276
16506
|
repoRoot,
|
|
16277
16507
|
"bundle-create",
|
|
16278
16508
|
() => gitBuffer(repoRoot, ["bundle", "create", tmpFile, orphanRef], {
|
|
16279
|
-
timeoutMs: resolveGitCommandTimeoutMs(DEFAULT_BUNDLE_TIMEOUT_MS)
|
|
16509
|
+
timeoutMs: resolveGitCommandTimeoutMs(DEFAULT_BUNDLE_TIMEOUT_MS),
|
|
16510
|
+
killProcessGroup: true
|
|
16280
16511
|
})
|
|
16281
16512
|
);
|
|
16282
16513
|
const bundle = fs18.readFileSync(tmpFile);
|
|
@@ -16335,7 +16566,7 @@ function gitGzipStream(repoRoot, args, maxGzBytes) {
|
|
|
16335
16566
|
GIT_COMMAND_TIMEOUT_MS,
|
|
16336
16567
|
Date.now() - started
|
|
16337
16568
|
);
|
|
16338
|
-
probeObjectStore(repoRoot, "timeout", wrapped.gitStderr);
|
|
16569
|
+
probeObjectStore(repoRoot, "timeout", wrapped.gitStderr, args);
|
|
16339
16570
|
reject(wrapped);
|
|
16340
16571
|
} else if (overflowed) {
|
|
16341
16572
|
reject(
|
|
@@ -17035,6 +17266,13 @@ function replayBackoffMs(attempts) {
|
|
|
17035
17266
|
REPLAY_BACKOFF_MAX_MS
|
|
17036
17267
|
);
|
|
17037
17268
|
}
|
|
17269
|
+
function bundleBackoffRemainingMs(timeout, now) {
|
|
17270
|
+
if (!timeout) return 0;
|
|
17271
|
+
return Math.max(
|
|
17272
|
+
0,
|
|
17273
|
+
timeout.lastAttemptAt + replayBackoffMs(timeout.attempts) - now
|
|
17274
|
+
);
|
|
17275
|
+
}
|
|
17038
17276
|
function replayEligibility(oldestCapturedAt, replay, now) {
|
|
17039
17277
|
if (now - oldestCapturedAt < STRANDED_MIN_AGE_MS)
|
|
17040
17278
|
return { eligible: false, reason: "young" };
|
|
@@ -17216,7 +17454,7 @@ function queueDiskBytes(directory) {
|
|
|
17216
17454
|
return diskBytes;
|
|
17217
17455
|
}
|
|
17218
17456
|
function chargedBytes(record) {
|
|
17219
|
-
return record.retainedBytes ?? record.treeBytes;
|
|
17457
|
+
return record.retainedBytes ?? record.treeBytes ?? 0;
|
|
17220
17458
|
}
|
|
17221
17459
|
function pendingBytes(records) {
|
|
17222
17460
|
const first = records[0];
|
|
@@ -17228,42 +17466,109 @@ function pendingBytes(records) {
|
|
|
17228
17466
|
0
|
|
17229
17467
|
);
|
|
17230
17468
|
}
|
|
17469
|
+
function warnMissingBase(repoRoot, base) {
|
|
17470
|
+
appendLog(
|
|
17471
|
+
"warn",
|
|
17472
|
+
`git-traces: capture base is missing for retention accounting; charging unshared content (repo=${repoRoot}, base=${base})`
|
|
17473
|
+
);
|
|
17474
|
+
}
|
|
17231
17475
|
function measureTreeRetention(repoRoot, treeSha, bases, context) {
|
|
17232
17476
|
const finish = startCaptureStage("tree-inventory", context);
|
|
17233
17477
|
let status = "failed";
|
|
17234
17478
|
let details;
|
|
17235
17479
|
try {
|
|
17236
|
-
const
|
|
17237
|
-
|
|
17238
|
-
|
|
17239
|
-
|
|
17240
|
-
bases.filter((base2) => !!base2)
|
|
17241
|
-
)) {
|
|
17480
|
+
const trees = [
|
|
17481
|
+
...new Set(bases.trees.filter((base) => !!base))
|
|
17482
|
+
];
|
|
17483
|
+
if (!trees.includes(treeSha) && bases.headSha) {
|
|
17242
17484
|
try {
|
|
17243
|
-
const
|
|
17244
|
-
|
|
17245
|
-
|
|
17485
|
+
const headTree = commitTreeSha(repoRoot, bases.headSha);
|
|
17486
|
+
if (headTree && !trees.includes(headTree)) trees.push(headTree);
|
|
17487
|
+
if (!headTree) warnMissingBase(repoRoot, bases.headSha);
|
|
17246
17488
|
} catch (err) {
|
|
17247
17489
|
if (!isMissingObjectError(err)) throw err;
|
|
17248
|
-
|
|
17249
|
-
"warn",
|
|
17250
|
-
`git-traces: capture base is missing for retention accounting; charging unshared content (repo=${repoRoot}, base=${base})`
|
|
17251
|
-
);
|
|
17490
|
+
warnMissingBase(repoRoot, bases.headSha);
|
|
17252
17491
|
}
|
|
17253
17492
|
}
|
|
17254
|
-
|
|
17255
|
-
|
|
17256
|
-
|
|
17257
|
-
|
|
17258
|
-
if (!shared.has(sha)) retainedBytes += size;
|
|
17493
|
+
if (trees.includes(treeSha)) {
|
|
17494
|
+
status = "ok";
|
|
17495
|
+
details = "retainedBytes=0, sameAsBase=yes";
|
|
17496
|
+
return 0;
|
|
17259
17497
|
}
|
|
17498
|
+
let totals;
|
|
17499
|
+
if (gitExcludesNegativeTrees(repoRoot)) {
|
|
17500
|
+
const { objects, usable } = unsharedObjects(repoRoot, treeSha, trees);
|
|
17501
|
+
totals = { ...blobSizeTotals(repoRoot, objects), used: usable };
|
|
17502
|
+
} else {
|
|
17503
|
+
totals = legacyRetention(repoRoot, treeSha, trees);
|
|
17504
|
+
}
|
|
17505
|
+
const { used } = totals;
|
|
17260
17506
|
status = "ok";
|
|
17261
|
-
details = `
|
|
17262
|
-
|
|
17507
|
+
details = `retainedBytes=${totals.bytes}, retainedBlobs=${totals.blobs}, bases=${used}`;
|
|
17508
|
+
if (totals.unreadable > 0)
|
|
17509
|
+
details += `, unreadableBlobs=${totals.unreadable}`;
|
|
17510
|
+
if (used < trees.length) details += `, droppedBases=${trees.length - used}`;
|
|
17511
|
+
return totals.bytes;
|
|
17263
17512
|
} finally {
|
|
17264
17513
|
finish(status, details);
|
|
17265
17514
|
}
|
|
17266
17515
|
}
|
|
17516
|
+
function legacyRetention(repoRoot, treeSha, bases) {
|
|
17517
|
+
appendLog(
|
|
17518
|
+
"info",
|
|
17519
|
+
`git-traces: git before 1.8.5 ignores negative trees; measuring retention from ls-tree (repo=${repoRoot})`
|
|
17520
|
+
);
|
|
17521
|
+
const shared = /* @__PURE__ */ new Set();
|
|
17522
|
+
let used = 0;
|
|
17523
|
+
for (const base of bases) {
|
|
17524
|
+
try {
|
|
17525
|
+
for (const sha of treeBlobShas(repoRoot, base)) shared.add(sha);
|
|
17526
|
+
used++;
|
|
17527
|
+
} catch (err) {
|
|
17528
|
+
if (!isMissingObjectError(err)) throw err;
|
|
17529
|
+
warnMissingBase(repoRoot, base);
|
|
17530
|
+
}
|
|
17531
|
+
}
|
|
17532
|
+
const totals = { bytes: 0, blobs: 0, unreadable: 0, used };
|
|
17533
|
+
for (const [sha, size] of treeBlobSizes(repoRoot, treeSha)) {
|
|
17534
|
+
if (shared.has(sha)) continue;
|
|
17535
|
+
if (size === null) totals.unreadable++;
|
|
17536
|
+
else {
|
|
17537
|
+
totals.bytes += size;
|
|
17538
|
+
totals.blobs++;
|
|
17539
|
+
}
|
|
17540
|
+
}
|
|
17541
|
+
return totals;
|
|
17542
|
+
}
|
|
17543
|
+
function unsharedObjects(repoRoot, treeSha, bases) {
|
|
17544
|
+
try {
|
|
17545
|
+
return {
|
|
17546
|
+
objects: listUnsharedObjects(repoRoot, treeSha, bases),
|
|
17547
|
+
usable: bases.length
|
|
17548
|
+
};
|
|
17549
|
+
} catch (err) {
|
|
17550
|
+
if (!isMissingObjectError(err) || bases.length === 0) throw err;
|
|
17551
|
+
const usable = bases.filter((base) => {
|
|
17552
|
+
try {
|
|
17553
|
+
walkBaseTree(repoRoot, base);
|
|
17554
|
+
return true;
|
|
17555
|
+
} catch (baseErr) {
|
|
17556
|
+
if (!isMissingObjectError(baseErr)) throw baseErr;
|
|
17557
|
+
warnMissingBase(repoRoot, base);
|
|
17558
|
+
return false;
|
|
17559
|
+
}
|
|
17560
|
+
});
|
|
17561
|
+
if (usable.length === bases.length)
|
|
17562
|
+
return {
|
|
17563
|
+
objects: listUnsharedObjects(repoRoot, treeSha, bases),
|
|
17564
|
+
usable: bases.length
|
|
17565
|
+
};
|
|
17566
|
+
return {
|
|
17567
|
+
objects: listUnsharedObjects(repoRoot, treeSha, usable),
|
|
17568
|
+
usable: usable.length
|
|
17569
|
+
};
|
|
17570
|
+
}
|
|
17571
|
+
}
|
|
17267
17572
|
async function withCaptureQueueLock(repoRoot, tool, sessionId, work) {
|
|
17268
17573
|
const lockTool = `${tool}:capture-queue-v1`;
|
|
17269
17574
|
await acquireLock3(repoRoot, lockTool, sessionId);
|
|
@@ -17287,21 +17592,26 @@ function storePendingCapture(params) {
|
|
|
17287
17592
|
params.tool,
|
|
17288
17593
|
params.sessionId
|
|
17289
17594
|
);
|
|
17290
|
-
const
|
|
17595
|
+
const retainedBytes = measureTreeRetention(
|
|
17291
17596
|
params.repoRoot,
|
|
17292
17597
|
input.treeSha,
|
|
17293
|
-
|
|
17294
|
-
|
|
17295
|
-
|
|
17296
|
-
|
|
17297
|
-
|
|
17298
|
-
|
|
17598
|
+
{
|
|
17599
|
+
trees: [
|
|
17600
|
+
existing.at(-1)?.treeSha,
|
|
17601
|
+
params.seed.lastSnapshotTreeSha,
|
|
17602
|
+
params.seed.baselineTreeSha
|
|
17603
|
+
],
|
|
17604
|
+
headSha: input.headSha
|
|
17605
|
+
},
|
|
17299
17606
|
`repo=${params.repoRoot}, tool=${params.tool}, session=${params.sessionId}, recordedAt=${params.recordedAt}`
|
|
17300
17607
|
);
|
|
17301
17608
|
const record = {
|
|
17302
17609
|
...identity,
|
|
17303
17610
|
...input,
|
|
17304
|
-
|
|
17611
|
+
retainedBytes,
|
|
17612
|
+
// 0.8.12 sums treeBytes with no fallback; a missing value would
|
|
17613
|
+
// disable its byte limit for the whole queue.
|
|
17614
|
+
treeBytes: retainedBytes,
|
|
17305
17615
|
version: 1,
|
|
17306
17616
|
id: crypto7.randomUUID(),
|
|
17307
17617
|
sequence: Math.max(Date.now(), (existing.at(-1)?.sequence ?? 0) + 1),
|
|
@@ -17310,10 +17620,10 @@ function storePendingCapture(params) {
|
|
|
17310
17620
|
const pendingBefore = queueDiskBytes(
|
|
17311
17621
|
queueDirectory(params.repoRoot, params.tool, params.sessionId)
|
|
17312
17622
|
) + existing.reduce((sum, item) => sum + chargedBytes(item), 0);
|
|
17313
|
-
const incomingBytes =
|
|
17623
|
+
const incomingBytes = retainedBytes + Buffer.byteLength(JSON.stringify(record));
|
|
17314
17624
|
if (existing.length >= MAX_CAPTURES || pendingBefore + incomingBytes > MAX_PENDING_BYTES2) {
|
|
17315
17625
|
throw new PendingCaptureLimitError(
|
|
17316
|
-
`Pending Git capture limit reached (count=${existing.length}, maxCount=${MAX_CAPTURES}, pendingBytes=${pendingBefore}, incomingBytes=${incomingBytes},
|
|
17626
|
+
`Pending Git capture limit reached (count=${existing.length}, maxCount=${MAX_CAPTURES}, pendingBytes=${pendingBefore}, incomingBytes=${incomingBytes}, retainedBytes=${retainedBytes}, maxBytes=${MAX_PENDING_BYTES2}); existing captures retained`
|
|
17317
17627
|
);
|
|
17318
17628
|
}
|
|
17319
17629
|
const directory = captureDirectory(record);
|
|
@@ -17355,7 +17665,7 @@ function storePendingCapture(params) {
|
|
|
17355
17665
|
durableDetails = `capture=${record.id}, durableCapturedAt=${durableCapturedAt}`;
|
|
17356
17666
|
appendLog(
|
|
17357
17667
|
"info",
|
|
17358
|
-
`git-traces: capture queued (repo=${record.repoRoot}, session=${record.sessionId}, capture=${record.id}, recordedAt=${record.recordedAt}, captureStartedAt=${record.captureStartedAt}, capturedAt=${record.capturedAt}, sourceCapturedAt=${record.capturedAt}, durableCapturedAt=${durableCapturedAt}, pending=${existing.length + 1}, oldestPendingAgeMs=${Date.now() - (existing[0]?.capturedAt ?? record.capturedAt)},
|
|
17668
|
+
`git-traces: capture queued (repo=${record.repoRoot}, session=${record.sessionId}, capture=${record.id}, recordedAt=${record.recordedAt}, captureStartedAt=${record.captureStartedAt}, capturedAt=${record.capturedAt}, sourceCapturedAt=${record.capturedAt}, durableCapturedAt=${durableCapturedAt}, pending=${existing.length + 1}, oldestPendingAgeMs=${Date.now() - (existing[0]?.capturedAt ?? record.capturedAt)}, retainedBytes=${record.retainedBytes}, pendingBytes=${pendingBefore + incomingBytes})`
|
|
17359
17669
|
);
|
|
17360
17670
|
return record;
|
|
17361
17671
|
} finally {
|
|
@@ -17546,7 +17856,7 @@ async function uploadArtifact(client, contributionId, filename, mimeType, buffer
|
|
|
17546
17856
|
}
|
|
17547
17857
|
|
|
17548
17858
|
// package.json
|
|
17549
|
-
var version = "0.9.
|
|
17859
|
+
var version = "0.9.6";
|
|
17550
17860
|
|
|
17551
17861
|
// src/version.ts
|
|
17552
17862
|
var CLI_VERSION = version;
|
|
@@ -19488,6 +19798,53 @@ function freezeEpochBaseline(params) {
|
|
|
19488
19798
|
return null;
|
|
19489
19799
|
}
|
|
19490
19800
|
}
|
|
19801
|
+
function createBundleWithBackoff(pending, params) {
|
|
19802
|
+
const { repoRoot, baselineTreeSha } = params;
|
|
19803
|
+
const previous = pending?.record.bundleTimeout?.input === baselineTreeSha ? pending.record.bundleTimeout : void 0;
|
|
19804
|
+
if (pending && previous) {
|
|
19805
|
+
const retryInMs = bundleBackoffRemainingMs(previous, Date.now());
|
|
19806
|
+
if (retryInMs > 0) {
|
|
19807
|
+
appendLog(
|
|
19808
|
+
"warn",
|
|
19809
|
+
`git-traces: baseline bundle deferred after timeout (repo=${repoRoot}, session=${pending.record.sessionId}, capture=${pending.record.id}, attempts=${previous.attempts}, retryInMs=${retryInMs})`
|
|
19810
|
+
);
|
|
19811
|
+
return null;
|
|
19812
|
+
}
|
|
19813
|
+
}
|
|
19814
|
+
try {
|
|
19815
|
+
const bundle = createBundleFromTree(
|
|
19816
|
+
repoRoot,
|
|
19817
|
+
baselineTreeSha,
|
|
19818
|
+
params.bundleKey,
|
|
19819
|
+
params.label
|
|
19820
|
+
);
|
|
19821
|
+
if (pending?.record.bundleTimeout) {
|
|
19822
|
+
delete pending.record.bundleTimeout;
|
|
19823
|
+
saveBundleBackoff(pending);
|
|
19824
|
+
}
|
|
19825
|
+
return bundle;
|
|
19826
|
+
} catch (err) {
|
|
19827
|
+
if (pending && isGitTimeoutError(err)) {
|
|
19828
|
+
pending.record.bundleTimeout = {
|
|
19829
|
+
input: baselineTreeSha,
|
|
19830
|
+
attempts: (previous?.attempts ?? 0) + 1,
|
|
19831
|
+
lastAttemptAt: Date.now()
|
|
19832
|
+
};
|
|
19833
|
+
saveBundleBackoff(pending);
|
|
19834
|
+
}
|
|
19835
|
+
throw err;
|
|
19836
|
+
}
|
|
19837
|
+
}
|
|
19838
|
+
function saveBundleBackoff(pending) {
|
|
19839
|
+
try {
|
|
19840
|
+
pending.save();
|
|
19841
|
+
} catch (err) {
|
|
19842
|
+
appendLog(
|
|
19843
|
+
"warn",
|
|
19844
|
+
`git-traces: could not save baseline bundle backoff (capture=${pending.record.id}): ${formatError(err)}`
|
|
19845
|
+
);
|
|
19846
|
+
}
|
|
19847
|
+
}
|
|
19491
19848
|
function buildFrozenEpochBaselineArtifacts(params) {
|
|
19492
19849
|
const { repoRoot, sessionId, epoch, baselineTreeSha, baselineMetadata } = params;
|
|
19493
19850
|
const prefix = epochPrefix2(epoch);
|
|
@@ -19497,12 +19854,13 @@ function buildFrozenEpochBaselineArtifacts(params) {
|
|
|
19497
19854
|
pending,
|
|
19498
19855
|
`${prefix}-baseline.bundle`,
|
|
19499
19856
|
baselineTreeSha
|
|
19500
|
-
)) ??
|
|
19857
|
+
)) ?? createBundleWithBackoff(pending, {
|
|
19501
19858
|
repoRoot,
|
|
19502
19859
|
baselineTreeSha,
|
|
19503
|
-
`${sessionId}-${prefix}`,
|
|
19504
|
-
`baseline ${prefix}`
|
|
19505
|
-
);
|
|
19860
|
+
bundleKey: `${sessionId}-${prefix}`,
|
|
19861
|
+
label: `baseline ${prefix}`
|
|
19862
|
+
});
|
|
19863
|
+
if (!bundleBuffer) return null;
|
|
19506
19864
|
const metadataBuffer = Buffer.from(
|
|
19507
19865
|
JSON.stringify(baselineMetadata, null, 2)
|
|
19508
19866
|
);
|
|
@@ -21407,6 +21765,10 @@ async function sweepStrandedCaptures(params) {
|
|
|
21407
21765
|
tally[state.reason]++;
|
|
21408
21766
|
continue;
|
|
21409
21767
|
}
|
|
21768
|
+
if (bundleBackoffRemainingMs(head.bundleTimeout, now) > 0) {
|
|
21769
|
+
tally.backoff++;
|
|
21770
|
+
continue;
|
|
21771
|
+
}
|
|
21410
21772
|
eligible.push({
|
|
21411
21773
|
queue,
|
|
21412
21774
|
oldestCapturedAt,
|