forked from heavy-duty/ceremony
154 lines
8 KiB
Bash
154 lines
8 KiB
Bash
|
|
#!/usr/bin/env bash
|
|||
|
|
# lib/decide.sh — the merge door's decision, pure and exhaustively tested
|
|||
|
|
# (issue #8; design lineage: box#96 / rig#47 / cast#111).
|
|||
|
|
#
|
|||
|
|
# The merge-door job runs on EVERY push to main, and the `release` label
|
|||
|
|
# carries TWO legitimate meanings (per each repo's LABELS.md: "release flow
|
|||
|
|
# and version/packaging work"): the ceremony PR that ships a version, and
|
|||
|
|
# ordinary work ON the release machinery — the PR that adds the ceremony to
|
|||
|
|
# a repo included. The version transition tells them apart. Every ambiguous
|
|||
|
|
# middle state is a half-ceremony and must die loudly, creating nothing;
|
|||
|
|
# every legitimate non-ceremony state must be a green NOTICE no-op, not a
|
|||
|
|
# red run on main per infra PR.
|
|||
|
|
#
|
|||
|
|
# Pure: no git, no gh, no network. The caller (the release workflow, #9)
|
|||
|
|
# establishes four facts and passes them as environment variables:
|
|||
|
|
#
|
|||
|
|
# VER the version at the pushed head
|
|||
|
|
# BASE_VER the version at the base (the head's first parent /
|
|||
|
|
# event.before — the caller's job, including the
|
|||
|
|
# all-zeros-event.before fallback)
|
|||
|
|
# RELEASED "yes"|"no" — does a release for VER already exist?
|
|||
|
|
# LABELED "yes"|"no" — is a merged, release-labeled PR behind this
|
|||
|
|
# commit?
|
|||
|
|
#
|
|||
|
|
# Output: `ceremony=yes` or `ceremony=no` on stdout (the workflow appends
|
|||
|
|
# it to $GITHUB_OUTPUT), notices to stdout, refusals to stderr, exit 1 on
|
|||
|
|
# refusal.
|
|||
|
|
#
|
|||
|
|
# The decision table (this IS the spec — issue #8):
|
|||
|
|
#
|
|||
|
|
# | # | VER | vs BASE_VER | RELEASED | LABELED | result |
|
|||
|
|
# |---|--------|-------------|----------|---------|---------------------------|
|
|||
|
|
# | 1 | `-dev` | unchanged | — | — | `ceremony=no`, NOTICE: |
|
|||
|
|
# | | | | | | work under the label / |
|
|||
|
|
# | | | | | | ordinary merge — nothing |
|
|||
|
|
# | | | | | | to publish |
|
|||
|
|
# | 2 | `-dev` | changed | — | — | `ceremony=no`, NOTICE: |
|
|||
|
|
# | | | | | | still a dev tree — the |
|
|||
|
|
# | | | | | | post-release bump or a |
|
|||
|
|
# | | | | | | renumber; "a dev tree is |
|
|||
|
|
# | | | | | | by definition not a |
|
|||
|
|
# | | | | | | release" |
|
|||
|
|
# | 3 | bare | unchanged | yes | — | `ceremony=no`, NOTICE: |
|
|||
|
|
# | | | | | | post-release window |
|
|||
|
|
# | | | | | | (ceremony landed, `-dev` |
|
|||
|
|
# | | | | | | bump hasn't) — nothing to |
|
|||
|
|
# | | | | | | publish |
|
|||
|
|
# | 4 | bare | unchanged | no | — | REFUSE (exit 1): "the |
|
|||
|
|
# | | | | | | label says ship but this |
|
|||
|
|
# | | | | | | PR did not mint the |
|
|||
|
|
# | | | | | | version. Refusing to |
|
|||
|
|
# | | | | | | guess — creating |
|
|||
|
|
# | | | | | | nothing." |
|
|||
|
|
# | 5 | bare | changed | — | no | REFUSE (exit 1): "version |
|
|||
|
|
# | | | | | | transitioned but no |
|
|||
|
|
# | | | | | | merged, release-labeled |
|
|||
|
|
# | | | | | | PR is behind this commit |
|
|||
|
|
# | | | | | | — a release is a labeled |
|
|||
|
|
# | | | | | | ceremony PR, not a bare |
|
|||
|
|
# | | | | | | push — creating nothing." |
|
|||
|
|
# | 6 | bare | changed | — | yes | `ceremony=yes` |
|
|||
|
|
#
|
|||
|
|
# Ordering matters and matches the sources: the -dev cases never consult
|
|||
|
|
# RELEASED or LABELED; the bare-unchanged cases never consult LABELED;
|
|||
|
|
# LABELED is only read after a bare transition is established. RELEASED and
|
|||
|
|
# LABELED may be empty in the states that don't use them — the workflow is
|
|||
|
|
# free to skip API calls it doesn't need — but a state that DOES need one
|
|||
|
|
# refuses when it is empty: a fact-gathering bug upstream must never fall
|
|||
|
|
# through to "no".
|
|||
|
|
#
|
|||
|
|
# State 4 doubles as the known first-release edge (cast#111): a repo whose
|
|||
|
|
# first version never carried -dev ships its first release by the tag door.
|
|||
|
|
# Consumers that bootstrap at X.Y.Z-dev (the docs/CONSUMERS.md guide says
|
|||
|
|
# to) never hit it.
|
|||
|
|
set -euo pipefail
|
|||
|
|
|
|||
|
|
# shellcheck source=lib/version.sh
|
|||
|
|
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/version.sh"
|
|||
|
|
|
|||
|
|
refuse() {
|
|||
|
|
printf '%s\n' "$@" >&2
|
|||
|
|
exit 1
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
notice() {
|
|||
|
|
printf 'NOTICE: %s\n' "$1"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Before the table: a missing fact is a fact-gathering bug upstream and must
|
|||
|
|
# not fall through to "no"; a malformed fact is the same bug wearing a
|
|||
|
|
# different hat.
|
|||
|
|
if [ -z "${VER:-}" ]; then
|
|||
|
|
refuse "VER is empty — the caller failed to establish the version at the pushed head. Refusing to decide — creating nothing."
|
|||
|
|
fi
|
|||
|
|
if [ -z "${BASE_VER:-}" ]; then
|
|||
|
|
refuse "BASE_VER is empty — the caller failed to establish the version at the base. Refusing to decide — creating nothing."
|
|||
|
|
fi
|
|||
|
|
case "${RELEASED:-}" in
|
|||
|
|
yes | no | '') ;;
|
|||
|
|
*) refuse "RELEASED='${RELEASED}' — expected yes, no, or empty. Refusing to decide — creating nothing." ;;
|
|||
|
|
esac
|
|||
|
|
case "${LABELED:-}" in
|
|||
|
|
yes | no | '') ;;
|
|||
|
|
*) refuse "LABELED='${LABELED}' — expected yes, no, or empty. Refusing to decide — creating nothing." ;;
|
|||
|
|
esac
|
|||
|
|
|
|||
|
|
# Rows 1–2: a -dev tree decides on VER and BASE_VER alone. Only -dev is
|
|||
|
|
# special-cased (version_is_dev): an rc is a pre-release, and an rc
|
|||
|
|
# transition with a label is a shippable ceremony.
|
|||
|
|
if version_is_dev "$VER"; then
|
|||
|
|
if [ "$BASE_VER" = "$VER" ]; then
|
|||
|
|
notice "the version '$VER' is -dev and unchanged by this PR — release-flow work under the release label, not a ceremony. Nothing to publish."
|
|||
|
|
else
|
|||
|
|
notice "the version changed ('$BASE_VER' -> '$VER') and still ends -dev — a dev tree is by definition not a release. This is work (the post-release bump, a renumber); nothing to publish."
|
|||
|
|
fi
|
|||
|
|
echo "ceremony=no"
|
|||
|
|
exit 0
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
# Rows 3–4: bare and unchanged — RELEASED tells the post-release window
|
|||
|
|
# apart from a label with no minted version. LABELED is not consulted.
|
|||
|
|
if [ "$BASE_VER" = "$VER" ]; then
|
|||
|
|
case "${RELEASED:-}" in
|
|||
|
|
yes)
|
|||
|
|
notice "the version '$VER' is already released and unchanged by this PR — release-flow work merged in the post-release window (before the -dev bump). Nothing to publish."
|
|||
|
|
echo "ceremony=no"
|
|||
|
|
exit 0
|
|||
|
|
;;
|
|||
|
|
no)
|
|||
|
|
refuse \
|
|||
|
|
"the version '$VER' is bare, unchanged by this PR, and never released — the label says ship but this PR did not mint the version. Refusing to guess — creating nothing." \
|
|||
|
|
"(If this PR was mislabeled, drop the label; if it was meant to release, it forgot the bump. A first release whose version never carried -dev ships by the tag door — the known first-release edge.)"
|
|||
|
|
;;
|
|||
|
|
*)
|
|||
|
|
refuse "the version '$VER' is bare and unchanged, but RELEASED is empty — this state is decided by whether '$VER' is already released, and the caller did not establish that fact. Refusing to guess — creating nothing."
|
|||
|
|
;;
|
|||
|
|
esac
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
# Rows 5–6: a bare transition — now the LABEL, the operator's declared
|
|||
|
|
# intent. No merged, release-labeled PR behind this commit = a transition
|
|||
|
|
# nobody declared: refuse.
|
|||
|
|
case "${LABELED:-}" in
|
|||
|
|
yes)
|
|||
|
|
echo "ceremony=yes"
|
|||
|
|
;;
|
|||
|
|
no)
|
|||
|
|
refuse "the version transitioned ('$BASE_VER' -> '$VER') but no merged, release-labeled PR is behind this commit — a release is a labeled ceremony PR, not a bare push — creating nothing."
|
|||
|
|
;;
|
|||
|
|
*)
|
|||
|
|
refuse "the version transitioned ('$BASE_VER' -> '$VER') but LABELED is empty — a transition ships only behind a merged, release-labeled PR, and the caller did not establish that fact. Refusing to guess — creating nothing."
|
|||
|
|
;;
|
|||
|
|
esac
|