Some checks failed
CI / self-guards (pull_request) Successful in 10s
CI / action-exercise (pull_request) Successful in 8s
CI / release-exercise (pull_request) Successful in 14s
CI / docs-sync-exercise (pull_request) Successful in 8s
Refs guard / refs-not-closing (pull_request) Successful in 8s
labels / labels (pull_request) Successful in 10s
CI / test (pull_request) Failing after 43s
81 lines
3.5 KiB
Bash
Executable file
81 lines
3.5 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# lib/preflight.sh — the merge door's resume decision, pure and exhaustively
|
|
# tested (issue #273).
|
|
#
|
|
# A merge-door run creates the tag before the artifact hook and release. A
|
|
# failed hook or publish therefore leaves a tag but no release. Re-running the
|
|
# same merge commit must resume after that irreversible step; a published
|
|
# release or a tag naming another commit must still refuse.
|
|
#
|
|
# Pure: no repository or forge reads. The workflow establishes four facts:
|
|
#
|
|
# VER the version being released
|
|
# MERGE_SHA the commit this door would tag
|
|
# TAG_SHAS object names returned for the direct and peeled tag refs,
|
|
# newline-separated; empty means the tag is absent
|
|
# RELEASED yes|no — whether a published release for VER exists
|
|
#
|
|
# Output: resume=yes or resume=no on stdout, notices to stdout, refusals to
|
|
# stderr, return 1 on refusal.
|
|
#
|
|
# The decision table (this IS the spec — issue #273):
|
|
#
|
|
# | # | RELEASED | TAG_SHAS contains MERGE_SHA | result |
|
|
# |---|----------|------------------------------|---------------------------|
|
|
# | 1 | yes | either | REFUSE: already released |
|
|
# | 2 | no | empty | resume=no: ordinary run |
|
|
# | 3 | no | yes | resume=yes + resume NOTICE |
|
|
# | 4 | no | non-empty, no | REFUSE: tag is elsewhere |
|
|
|
|
release_preflight() {
|
|
local tag_sha sha
|
|
|
|
if [ -z "${VER:-}" ]; then
|
|
printf '%s\n' "VER is empty — the caller failed to establish the release version. Refusing to decide — creating nothing." >&2
|
|
return 1
|
|
fi
|
|
if [ -z "${MERGE_SHA:-}" ]; then
|
|
printf '%s\n' "MERGE_SHA is empty — the caller failed to establish the merge commit. Refusing to decide — creating nothing." >&2
|
|
return 1
|
|
fi
|
|
if [ -z "${RELEASED:-}" ]; then
|
|
printf '%s\n' "RELEASED is empty — the caller failed to establish whether release '$VER' exists. Refusing to decide — creating nothing." >&2
|
|
return 1
|
|
fi
|
|
case "$RELEASED" in
|
|
yes | no) ;;
|
|
*)
|
|
printf '%s\n' "RELEASED='$RELEASED' — expected yes or no. Refusing to decide — creating nothing." >&2
|
|
return 1
|
|
;;
|
|
esac
|
|
|
|
# Row 1 comes first: deleting a tag under a standing release never makes
|
|
# that release safe to recreate.
|
|
if [ "$RELEASED" = yes ]; then
|
|
printf '%s\n' "release '$VER' already exists — this release already happened; refusing to re-release, creating nothing." >&2
|
|
return 1
|
|
fi
|
|
|
|
# Row 2: an absent tag is the ordinary first run.
|
|
if [ -z "${TAG_SHAS:-}" ]; then
|
|
printf '%s\n' 'resume=no'
|
|
return 0
|
|
fi
|
|
|
|
# Row 3: compare each object name as a whole line. For an annotated tag the
|
|
# direct ref names the tag object and the peeled ref names MERGE_SHA.
|
|
while IFS= read -r sha; do
|
|
if [ "$sha" = "$MERGE_SHA" ]; then
|
|
printf '%s\n' "NOTICE: tag '$VER' already stands at this merge commit and no release exists — a previous run of this door tagged and then failed to publish. Resuming: the tag is not recreated; the artifact hook and the publish run."
|
|
printf '%s\n' 'resume=yes'
|
|
return 0
|
|
fi
|
|
done <<<"$TAG_SHAS"
|
|
|
|
# Row 4: the first object name is enough to diagnose the conflicting tag;
|
|
# MERGE_SHA is printed beside it so the operator sees both sides.
|
|
tag_sha="${TAG_SHAS%%$'\n'*}"
|
|
printf '%s\n' "tag '$VER' already exists at $tag_sha but this run would tag $MERGE_SHA — a manual tag won the race, or it names a different commit; refusing to re-release, creating nothing. Delete that tag, or re-tag the merge commit." >&2
|
|
return 1
|
|
}
|