fix: facts.sh reads a parentless head as base_ver=(none) (#134)

A repository's first push to main is a branch-create push whose head is a
root commit: event.before is all-zeros and MERGE_SHA^1 does not exist, so
the fallback died at exit 128 before establishing a fact. The parent count
is now read via rev-list --parents (a fact, not an inferred failure), the
no-base path skips the belt-and-braces fetch and the base git show, and an
unresolvable MERGE_SHA still fails loudly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
claude-bot-andresmgsl 2026-07-24 12:14:07 +00:00
parent a602fd0a70
commit 7477d3ec95

View file

@ -44,31 +44,50 @@ ver="$(version_read "$VERSION_SOURCE")"
# event.before is all-zeros on a branch-create push, and absent outside push # event.before is all-zeros on a branch-create push, and absent outside push
# events; the pushed head's first parent is main the instant before, either # events; the pushed head's first parent is main the instant before, either
# way (#1 constraint 10; cast's `*[!0]*` test — "contains a non-zero char"). # way (#1 constraint 10; cast's `*[!0]*` test — "contains a non-zero char").
# One branch-create has no instant before: the repository's FIRST push to
# main, whose head is a root commit — both 0.2.0 drills died here at exit
# 128, the first release-flow event either scratch consumer ever saw (#134).
# The parent count is read as a fact (`rev-list --parents` prints the head
# alone for a root commit) rather than inferred from a failed rev-parse, so
# an unresolvable MERGE_SHA still dies loudly instead of masquerading as
# "(none)".
base_sha="${EVENT_BEFORE:-}" base_sha="${EVENT_BEFORE:-}"
case "$base_sha" in case "$base_sha" in
*[!0]*) ;; *[!0]*) ;;
*) base_sha="$(git rev-parse "$MERGE_SHA^1")" ;; *)
parents="$(git rev-list --parents -n 1 "$MERGE_SHA")"
case "$parents" in
*" "*) base_sha="$(git rev-parse "$MERGE_SHA^1")" ;;
*) base_sha="" ;; # a root commit: no base tree exists at all
esac
;;
esac esac
# Belt-and-braces (cast's precedent): the workflow's fetch-depth: 2 resolves # Belt-and-braces (cast's precedent): the workflow's fetch-depth: 2 resolves
# the first parent, but event.before can predate it when pushes raced. If # the first parent, but event.before can predate it when pushes raced. If
# the fetch still cannot produce it, the git show below is the loud failure. # the fetch still cannot produce it, the git show below is the loud failure.
git cat-file -e "$base_sha" 2>/dev/null \ # Skipped entirely when there is no base: with an empty rev the fetch is
|| git fetch --depth=1 origin "$base_sha" >&2 \ # meaningless and `git show ":$src"` would read the INDEX — reporting the
|| true # head's own version as the base, a wrong fact worse than any crash (#134).
if [ -n "$base_sha" ]; then
git cat-file -e "$base_sha" 2>/dev/null \
|| git fetch --depth=1 origin "$base_sha" >&2 \
|| true
fi
base_dir="$(mktemp -d)" base_dir="$(mktemp -d)"
trap 'rm -rf "$base_dir"' EXIT trap 'rm -rf "$base_dir"' EXIT
if git show "$base_sha:$src" >"$base_dir/$src" 2>/dev/null; then if [ -n "$base_sha" ] && git show "$base_sha:$src" >"$base_dir/$src" 2>/dev/null; then
base_ver="$(version_read "$VERSION_SOURCE" "$base_dir")" base_ver="$(version_read "$VERSION_SOURCE" "$base_dir")"
else else
# The base tree has no version source at all: the merge that ADDS the # No base tree (a root commit — the repository's first push, the 0.2.0
# version machinery (a consumer's adoption PR, a greenfield repo's first # drills' wall, #134), or a base tree with no version source in it: the
# caller). "(none)" is not a version, so decide sees a changed version # merge that ADDS the version machinery (a consumer's adoption PR, a
# and the table still governs: a -dev head is work (row 2, the guided # greenfield repo's first caller). "(none)" is not a version, so decide
# bootstrap path), a bare head still demands the merged release label # sees a changed version and the table still governs: a -dev head is work
# (rows 56). Nothing releases silently either way. # (row 2, the guided bootstrap path), a bare head still demands the
# merged release label (rows 56). Nothing releases silently either way.
base_ver="(none)" base_ver="(none)"
fi fi