Merge pull request #136 from claude-bot-andresmgsl/build/134-facts-root-commit
fix: facts.sh reads a parentless head as base_ver=(none), not exit 128
This commit is contained in:
commit
7b97554f19
4 changed files with 120 additions and 15 deletions
1
changelog.d/134.md
Normal file
1
changelog.d/134.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
- `lib/facts.sh` — a repository's first push to `main` (a root commit with no first parent) now reads `base_ver=(none)` and lets decide's table govern, instead of dying at exit 128 before establishing a fact; the no-base path skips the base fetch and `git show`, and an unresolvable head still fails loudly (#134).
|
||||
41
lib/facts.sh
41
lib/facts.sh
|
|
@ -44,31 +44,50 @@ ver="$(version_read "$VERSION_SOURCE")"
|
|||
# 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
|
||||
# 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:-}"
|
||||
case "$base_sha" in
|
||||
*[!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
|
||||
|
||||
# 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 fetch still cannot produce it, the git show below is the loud failure.
|
||||
git cat-file -e "$base_sha" 2>/dev/null \
|
||||
|| git fetch --depth=1 origin "$base_sha" >&2 \
|
||||
|| true
|
||||
# Skipped entirely when there is no base: with an empty rev the fetch is
|
||||
# meaningless and `git show ":$src"` would read the INDEX — reporting the
|
||||
# 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)"
|
||||
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")"
|
||||
else
|
||||
# The base tree has no version source at all: the merge that ADDS the
|
||||
# version machinery (a consumer's adoption PR, a greenfield repo's first
|
||||
# caller). "(none)" is not a version, so decide sees a changed version
|
||||
# and the table still governs: a -dev head is work (row 2, the guided
|
||||
# bootstrap path), a bare head still demands the merged release label
|
||||
# (rows 5–6). Nothing releases silently either way.
|
||||
# No base tree (a root commit — the repository's first push, the 0.2.0
|
||||
# drills' wall, #134), or a base tree with no version source in it: the
|
||||
# merge that ADDS the version machinery (a consumer's adoption PR, a
|
||||
# greenfield repo's first caller). "(none)" is not a version, so decide
|
||||
# sees a changed version and the table still governs: a -dev head is work
|
||||
# (row 2, the guided bootstrap path), a bare head still demands the
|
||||
# merged release label (rows 5–6). Nothing releases silently either way.
|
||||
base_ver="(none)"
|
||||
fi
|
||||
|
||||
|
|
|
|||
|
|
@ -146,6 +146,42 @@ adoptb_head="$(commit adoption-bare VERSION 0.1.0)"
|
|||
check "absent-at-base with a bare head still asks for the label" 0 "labeled=yes" \
|
||||
facts_in adoption-bare VERSION_SOURCE=file MERGE_SHA="$adoptb_head" EVENT_BEFORE="$adoptb_base" GH_STUB=labeled-yes
|
||||
|
||||
# --- a root commit: no base tree at all (the repository's first push) --------
|
||||
|
||||
# The first push to main IS a branch-create push (event.before all-zeros)
|
||||
# whose head has no first parent — there is no base, and the honest fact is
|
||||
# "(none)", not an exit-128 death at rev-parse. Both 0.2.0 drills hit the
|
||||
# death independently (#134). The -dev row consults no API (stub default),
|
||||
# which also asserts the no-base path runs no base fetch/show at all.
|
||||
repo greenfield
|
||||
green_head="$(commit greenfield VERSION 0.1.0-dev)"
|
||||
|
||||
check "root commit, all-zeros event.before: base_ver=(none)" 0 "base_ver=(none)" \
|
||||
facts_in greenfield VERSION_SOURCE=file MERGE_SHA="$green_head" EVENT_BEFORE="$ZEROS"
|
||||
check "root commit, empty event.before: base_ver=(none)" 0 "base_ver=(none)" \
|
||||
facts_in greenfield VERSION_SOURCE=file MERGE_SHA="$green_head" EVENT_BEFORE=
|
||||
|
||||
repo greenfield-bare
|
||||
greenb_head="$(commit greenfield-bare VERSION 0.1.0)"
|
||||
check "bare root commit still establishes labeled, so decide can refuse" 0 "labeled=no" \
|
||||
facts_in greenfield-bare VERSION_SOURCE=file MERGE_SHA="$greenb_head" EVENT_BEFORE="$ZEROS" GH_STUB=labeled-no
|
||||
|
||||
# The D2 pin: "no first parent" is detected, never inferred from a failed
|
||||
# command — an unresolvable MERGE_SHA is a loud death, not "(none)". A
|
||||
# `|| true` around the fallback would pass every case above and fail here.
|
||||
BAD_SHA="1111111111111111111111111111111111111111"
|
||||
check "an unresolvable MERGE_SHA still dies loudly" 128 "bad object" \
|
||||
facts_in greenfield VERSION_SOURCE=file MERGE_SHA="$BAD_SHA" EVENT_BEFORE="$ZEROS"
|
||||
bad_out="$(facts_in greenfield VERSION_SOURCE=file MERGE_SHA="$BAD_SHA" EVENT_BEFORE="$ZEROS" 2>&1)"
|
||||
if printf '%s' "$bad_out" | grep -qF "base_ver=(none)"; then
|
||||
echo "FAIL: an unresolvable MERGE_SHA must not be reported as base_ver=(none)"
|
||||
printf '%s\n' "$bad_out" | sed 's/^/ /'
|
||||
FAIL=$((FAIL + 1))
|
||||
else
|
||||
echo "ok: an unresolvable MERGE_SHA is not reported as base_ver=(none)"
|
||||
PASS=$((PASS + 1))
|
||||
fi
|
||||
|
||||
# --- the package-json backend ------------------------------------------------
|
||||
|
||||
repo pkg
|
||||
|
|
|
|||
|
|
@ -69,12 +69,14 @@ git -C "$TMP/repo" add VERSION CHANGELOG.md
|
|||
git -C "$TMP/repo" commit -qm "release: 0.7.0"
|
||||
MERGE_SHA="$(git -C "$TMP/repo" rev-parse HEAD)"
|
||||
|
||||
# chain <merge_sha> <event_before> — facts, then decide fed from facts'
|
||||
# output lines, then the notes extraction, printing each stage's result.
|
||||
# chain <merge_sha> <event_before> [repo_dir] [stub_dir] — facts, then
|
||||
# decide fed from facts' output lines, then the notes extraction, printing
|
||||
# each stage's result. The optional dirs default to the main fixture; the
|
||||
# greenfield cases below (#134) bring their own repo and stub.
|
||||
chain() {
|
||||
(
|
||||
cd "$TMP/repo" || exit 1
|
||||
facts_out="$(env PATH="$TMP/stub:$PATH" GITHUB_REPOSITORY=fixture/fixture \
|
||||
cd "${3:-$TMP/repo}" || exit 1
|
||||
facts_out="$(env PATH="${4:-$TMP/stub}:$PATH" GITHUB_REPOSITORY=fixture/fixture \
|
||||
GH_TOKEN=stub VERSION_SOURCE=file MERGE_SHA="$1" EVENT_BEFORE="$2" \
|
||||
bash "$FACTS")" || exit 1
|
||||
printf '%s\n' "$facts_out"
|
||||
|
|
@ -150,4 +152,51 @@ check "the post-release bump decides ceremony=no" 0 "ceremony=no" \
|
|||
check "an ordinary -dev merge decides ceremony=no" 0 "ceremony=no" \
|
||||
chain "$WORK2_SHA" "$BUMP_SHA"
|
||||
|
||||
# --- the repository's first push to main: a root commit, no base (#134) ------
|
||||
|
||||
# event.before is all-zeros and the head has no first parent. facts reads
|
||||
# base_ver=(none) instead of dying at rev-parse, and decide's table governs
|
||||
# from there: the guided bootstrap (-dev first commit, what CONSUMERS.md
|
||||
# tells a new repo to write) is a green NOTICE no-op — the doctrine's
|
||||
# promise held at the exact moment a consumer adopts the ceremony.
|
||||
ZEROS="0000000000000000000000000000000000000000"
|
||||
|
||||
git init -q "$TMP/greenfield"
|
||||
git -C "$TMP/greenfield" config user.email fixture@example.invalid
|
||||
git -C "$TMP/greenfield" config user.name fixture
|
||||
printf '0.1.0-dev\n' >"$TMP/greenfield/VERSION"
|
||||
git -C "$TMP/greenfield" add VERSION
|
||||
git -C "$TMP/greenfield" commit -qm "root: adopt the ceremony at 0.1.0-dev"
|
||||
GREEN_SHA="$(git -C "$TMP/greenfield" rev-parse HEAD)"
|
||||
|
||||
check "a greenfield -dev root commit decides ceremony=no, not a red run" 0 "ceremony=no" \
|
||||
chain "$GREEN_SHA" "$ZEROS" "$TMP/greenfield"
|
||||
check "the greenfield no-op is a NOTICE" 0 "NOTICE:" \
|
||||
chain "$GREEN_SHA" "$ZEROS" "$TMP/greenfield"
|
||||
|
||||
# A bare first commit with no merged release-labeled PR must still refuse —
|
||||
# the reason the crash could not be `|| true`-ed away: a greenfield adoption
|
||||
# must never become a silent release. This stub answers the labeled query
|
||||
# with false.
|
||||
mkdir -p "$TMP/stub-unlabeled"
|
||||
cat >"$TMP/stub-unlabeled/gh" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
if [ "$1" = api ]; then echo false; exit 0; fi
|
||||
echo "gh stub: unexpected call: gh $*" >&2
|
||||
exit 97
|
||||
EOF
|
||||
chmod +x "$TMP/stub-unlabeled/gh"
|
||||
|
||||
git init -q "$TMP/greenfield-bare"
|
||||
git -C "$TMP/greenfield-bare" config user.email fixture@example.invalid
|
||||
git -C "$TMP/greenfield-bare" config user.name fixture
|
||||
printf '0.1.0\n' >"$TMP/greenfield-bare/VERSION"
|
||||
git -C "$TMP/greenfield-bare" add VERSION
|
||||
git -C "$TMP/greenfield-bare" commit -qm "root: bare 0.1.0, nobody declared a release"
|
||||
GREENB_SHA="$(git -C "$TMP/greenfield-bare" rev-parse HEAD)"
|
||||
|
||||
check "a bare unlabeled root commit still refuses, creating nothing" 1 \
|
||||
"no merged, release-labeled PR" \
|
||||
chain "$GREENB_SHA" "$ZEROS" "$TMP/greenfield-bare" "$TMP/stub-unlabeled"
|
||||
|
||||
summary
|
||||
|
|
|
|||
Loading…
Reference in a new issue