mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-01 22:45:02 +01:00
* ci: blobless clones on the PR and train path, fail-loud history reads (GSTA-203 PR 5) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * changelog: GSTA-203 PR 5 fragment Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: tree-only merge-base diff in the Version Gate; ls-tree --full-tree in both history readers Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * ci: keep the route and backstop policy reads inline (git and jq only); docs follow Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: base CHANGELOG read in readFragments fails loudly; history readers try git show first Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * docs: HISTORY_READ_FAILED from gstack-restamp and ship-queue land Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --------- Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
44 lines
2.5 KiB
TypeScript
44 lines
2.5 KiB
TypeScript
/**
|
|
* Read one file at a commit when the checkout may be a blobless clone
|
|
* (`filter: blob:none`), where git fetches an old file's contents on demand
|
|
* and that fetch can fail. The TypeScript twin of
|
|
* .github/scripts/history-read.sh. A read that `git show` answers is the
|
|
* file. When it fails, existence comes from the ref's tree (`git ls-tree
|
|
* --full-tree`, local data only): a path the tree does not list is absent, and
|
|
* a listed one is retried, 3 tries in all in a partial clone (only a fetch can
|
|
* recover) and none otherwise, waiting HISTORY_READ_BACKOFF_SECONDS (default 2)
|
|
* times the attempt between tries. Every other failure throws
|
|
* HISTORY_READ_FAILED instead of reading as absent
|
|
* (docs/troubleshooting.md#history-read-failed).
|
|
*/
|
|
import { spawnSync } from 'node:child_process';
|
|
|
|
export const HISTORY_READ_TRIES = 3;
|
|
|
|
export class HistoryReadError extends Error {
|
|
readonly code = 'HISTORY_READ_FAILED';
|
|
}
|
|
|
|
/** The file's text at `ref`, or null when the ref's tree does not list it. */
|
|
export function readHistoryFile(repoRoot: string, ref: string, file: string): string | null {
|
|
const git = (args: string[]) => spawnSync('git', args, { cwd: repoRoot, encoding: 'utf8', timeout: 60_000, maxBuffer: 64 * 1024 * 1024 });
|
|
const fail = (cause: string) => {
|
|
const version = git(['--version']).stdout?.trim() || 'git version unknown';
|
|
return new HistoryReadError(`cannot read ${file} at ${ref}. ${cause} (${version}). Fix: see docs/troubleshooting.md#history-read-failed (HISTORY_READ_FAILED)`);
|
|
};
|
|
const firstLine = (r: ReturnType<typeof git>) => (r.stderr ?? '').split('\n')[0] || r.error?.message || `exit ${r.status}`;
|
|
let shown = git(['show', `${ref}:${file}`]);
|
|
if (shown.status === 0) return shown.stdout;
|
|
const listed = git(['ls-tree', '--full-tree', ref, '--', file]);
|
|
if (listed.status !== 0) throw fail(`git ls-tree failed: ${firstLine(listed)}`);
|
|
if (!listed.stdout.trim()) return null;
|
|
const promisors = (git(['config', '--get-regexp', '^(remote\\..+\\.promisor|extensions\\.partialclone)$']).stdout ?? '').split('\n');
|
|
const tries = promisors.some(line => line && !/ false$/i.test(line)) ? HISTORY_READ_TRIES : 1;
|
|
for (let attempt = 1; attempt < tries; attempt++) {
|
|
Bun.sleepSync(attempt * 1000 * Number(process.env.HISTORY_READ_BACKOFF_SECONDS ?? 2));
|
|
shown = git(['show', `${ref}:${file}`]);
|
|
if (shown.status === 0) return shown.stdout;
|
|
}
|
|
throw fail(`git show failed ${tries === 1 ? 'once (not a partial clone, so no retry)' : `${tries} times`}: ${firstLine(shown)}`);
|
|
}
|