mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-03 07:18:03 +01:00
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>
This commit is contained in:
1 parent
48a9d92a2f
commit
6aac45aa74
4 files changed
+37
-23
No files matched your search
@@ -6,14 +6,14 @@
|
||||
#
|
||||
# Usage: history-read.sh <ref> <path> [--absent <text>]
|
||||
#
|
||||
# Existence comes from the ref's tree (`git ls-tree --full-tree`, local data only). A path
|
||||
# the tree does not list is absent: prints <text> (default: nothing), exits 0.
|
||||
# A listed path is read with `git show`, tried 3 times in a partial clone
|
||||
# (only a fetch can recover) and once otherwise, waiting
|
||||
# HISTORY_READ_BACKOFF_SECONDS (default 2) times the attempt between tries.
|
||||
# Anything else (a ref that does not resolve, a fetch that keeps failing) exits
|
||||
# 1 with HISTORY_READ_FAILED naming the ref, the path and the git version, and
|
||||
# never reads as absent.
|
||||
# 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, so this prints <text> (default: nothing) and
|
||||
# exits 0. A listed path 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. Anything else (a ref that does
|
||||
# not resolve, a fetch that keeps failing) exits 1 with HISTORY_READ_FAILED
|
||||
# naming the ref, the path and the git version, and never reads as absent.
|
||||
# docs/troubleshooting.md#history-read-failed
|
||||
set -euo pipefail
|
||||
|
||||
@@ -30,6 +30,7 @@ fail() {
|
||||
exit 1
|
||||
}
|
||||
|
||||
if git show "${ref}:${file}" >"$out" 2>"$err"; then cat "$out"; exit 0; fi
|
||||
listed=$(git ls-tree --full-tree "$ref" -- "$file" 2>"$err") || fail "git ls-tree failed: $(head -n 1 "$err")"
|
||||
if [ -z "$listed" ]; then
|
||||
printf '%s' "$absent"
|
||||
@@ -39,9 +40,9 @@ fi
|
||||
tries=1
|
||||
if git config --get-regexp '^(remote\..+\.promisor|extensions\.partialclone)$' | grep -qvi ' false$'; then tries=3; fi
|
||||
try=1
|
||||
until git show "${ref}:${file}" >"$out" 2>"$err"; do
|
||||
[ "$try" -lt "$tries" ] || fail "git show failed ${tries} time(s): $(head -n 1 "$err")"
|
||||
while [ "$try" -lt "$tries" ]; do
|
||||
sleep "$(( try * ${HISTORY_READ_BACKOFF_SECONDS:-2} ))"
|
||||
if git show "${ref}:${file}" >"$out" 2>"$err"; then cat "$out"; exit 0; fi
|
||||
try=$(( try + 1 ))
|
||||
done
|
||||
cat "$out"
|
||||
fail "git show failed ${tries} time(s): $(head -n 1 "$err")"
|
||||
@@ -9,6 +9,7 @@
|
||||
*/
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { headingVersion, parseChangelog } from './changelog-check';
|
||||
import { readHistoryFile } from './history-read';
|
||||
import type { Bump } from './version-source';
|
||||
|
||||
export const FRAGMENT_README = 'README.md';
|
||||
@@ -75,8 +76,8 @@ export function readFragments(cwd: string, treeish: string | null, dir: string,
|
||||
return out;
|
||||
};
|
||||
const excluded = new Map((exclude ? list(exclude) : []).map(e => [e.path, e.sha]));
|
||||
const shown = exclude && changelog ? git(cwd, ['show', `${exclude}:${changelog}`]) : null;
|
||||
const released = shown?.ok ? parseChangelog(shown.out.replace(/\r\n/g, '\n')).sections.filter(s => !s.unreleased).map(s => s.lines) : [];
|
||||
const shown = exclude && changelog ? readHistoryFile(cwd, exclude, changelog) : null;
|
||||
const released = shown !== null ? parseChangelog(shown.replace(/\r\n/g, '\n')).sections.filter(s => !s.unreleased).map(s => s.lines) : [];
|
||||
const isReleased = (body: string) => {
|
||||
const want = body.split('\n');
|
||||
return released.some(lines => lines.some((_, i) => want.every((w, k) => lines[i + k] === w)));
|
||||
|
||||
+14
-10
@@ -2,12 +2,14 @@
|
||||
* 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: existence comes from the ref's tree
|
||||
* (`git ls-tree`, local data only); a listed path is read with `git show`,
|
||||
* tried 3 times in a partial clone (only a fetch can recover) and once
|
||||
* 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).
|
||||
* .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';
|
||||
|
||||
@@ -25,15 +27,17 @@ export function readHistoryFile(repoRoot: string, ref: string, file: string): st
|
||||
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++) {
|
||||
const shown = git(['show', `${ref}:${file}`]);
|
||||
if (shown.status === 0) return shown.stdout;
|
||||
if (attempt === tries) throw fail(`git show failed ${tries === 1 ? 'once (not a partial clone, so no retry)' : `${tries} times`}: ${firstLine(shown)}`);
|
||||
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)}`);
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import { spawnSync } from 'node:child_process';
|
||||
import * as fs from 'node:fs';
|
||||
import * as os from 'node:os';
|
||||
import * as path from 'node:path';
|
||||
import { readFragments } from '../lib/changelog-fragments';
|
||||
import { HistoryReadError, readHistoryFile } from '../lib/history-read';
|
||||
import { classifyTrainHead } from '../lib/version-gate-train';
|
||||
|
||||
@@ -157,6 +158,13 @@ describe('history-read.sh and lib/history-read.ts on a blobless clone', () => {
|
||||
absent.cut();
|
||||
expect(classifyTrainHead({ repoRoot: absent.clone, baseRef: 'main', head: absent.one })).toEqual({ kind: 'off' });
|
||||
});
|
||||
|
||||
test('the fragment reader fails loudly when the base CHANGELOG cannot be fetched, and treats an absent one as nothing released', () => {
|
||||
const fx = fixture('fragments', { ...ONE, 'CHANGELOG.md': '# Changelog\n\n## [1.0.0.0]\n\n- one\n', 'changelog.d/pr.md': '- the PR entry\n' }, { 'CHANGELOG.md': '# Changelog\n\n## [1.0.0.1]\n\n- two\n\n## [1.0.0.0]\n\n- one\n' });
|
||||
fx.cut();
|
||||
expect(() => readFragments(fx.clone, fx.one, 'changelog.d', fx.two, 'CHANGELOG.md', 'skip')).toThrow(HistoryReadError);
|
||||
expect(readFragments(fx.clone, fx.one, 'changelog.d', fx.two, 'NO-CHANGELOG.md', 'skip').errors).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
type Step = { name?: string; uses?: string; run?: string; with?: Record<string, unknown> };
|
||||
|
||||
Reference in new issue
Block a user