diff --git a/tools/ci/merge-to-master.sh b/tools/ci/merge-to-master.sh index 842cc10a6..9a8df6c74 100755 --- a/tools/ci/merge-to-master.sh +++ b/tools/ci/merge-to-master.sh @@ -18,8 +18,12 @@ # unless --fixes-master, none pushes the branch, pending waits then goes set -euo pipefail ROOT=$(git rev-parse --show-toplevel); cd "$ROOT" -BRANCH="$(git rev-parse --abbrev-ref HEAD)"; TRIES=6; CI_WAIT_MIN="${CI_WAIT_MIN:-25}"; FIXES_MASTER=0; SELF_TEST=0 -while [ $# -gt 0 ]; do case "$1" in --tries) TRIES="$2"; shift 2 ;; --ci-wait) CI_WAIT_MIN="$2"; shift 2 ;; --fixes-master) FIXES_MASTER=1; shift ;; --self-test) SELF_TEST=1; shift ;; -*) echo "unknown option $1" >&2; exit 2 ;; *) BRANCH="$1"; shift ;; esac; done +BRANCH="$(git rev-parse --abbrev-ref HEAD)"; TRIES=6; CI_WAIT_MIN="${CI_WAIT_MIN:-25}"; FIXES_MASTER=0; SELF_TEST=0; REMOTE=origin +while [ $# -gt 0 ]; do case "$1" in --tries) TRIES="$2"; shift 2 ;; --ci-wait) CI_WAIT_MIN="$2"; shift 2 ;; --fixes-master) FIXES_MASTER=1; shift ;; --remote) REMOTE="$2"; shift 2 ;; --self-test) SELF_TEST=1; shift ;; -*) echo "unknown option $1" >&2; exit 2 ;; *) BRANCH="$1"; shift ;; esac; done +# --remote : land on another remote's master (a box mirror, build@:/srv/igneum.git, while GitHub is unreachable; main's +# ruling of 7 October 2026, 19:5x UK). CI runs on GitHub only, so the CI rule binds the origin remote; on a mirror the box gate +# stamp is the verdict and the merge message says so. IGNEUM_MASTER_EXCEPTION, when set, is printed by the hook with the push. +remote_is_github() { case "$(git remote get-url "$REMOTE" 2>/dev/null)" in *github.com*) return 0 ;; *) return 1 ;; esac; } CI_STATE="${CI_STATE_CMD:-node tools/ci/ci-state.mjs}" # the self-test swaps in a fake clock() { TZ=Europe/London date '+%H:%M %Z'; } @@ -125,8 +129,11 @@ success 4 u push run out=$( cd "$d/src" && IGNEUM_MIRRORS="$d/mirror.git" mirror_master "$fork" ) [ "$(git -C "$d/mirror.git" rev-parse master)" = "$tip" ] || { echo "self-test failed: a non-fast-forward push moved the mirror's master"; fails=1; } case "$out" in *"did not take master"*) ;; *) echo "self-test failed: the refused mirror push was not reported: $out"; fails=1 ;; esac + # the remote decides the rule: origin (GitHub) binds the CI gate; a mirror remote does not + REMOTE=origin; remote_is_github || { echo "self-test failed: origin was not read as GitHub (is origin's URL github.com?)"; fails=1; } + ( cd "$d/src" && git remote add mirror "$d/mirror.git" ) 2>/dev/null; REMOTE=mirror; ( cd "$d/src" && remote_is_github ) && { echo "self-test failed: a bare-path mirror remote was read as GitHub"; fails=1; }; REMOTE=origin rm -rf "$d" - [ "$fails" = 0 ] && echo "self-test passed: a landed master is fast-forwarded to every mirror and a refused mirror push is a line, not a failure; a green branch run merges; a red one is refused; an unknown read is waited through and refused only at the deadline; a queued run is waited for with the clock; an unrun branch is pushed once and then waited for; a red master refuses every merge but the declared fix" + [ "$fails" = 0 ] && echo "self-test passed: the CI rule binds a GitHub remote only; a landed master is fast-forwarded to every mirror and a refused mirror push is a line, not a failure; a green branch run merges; a red one is refused; an unknown read is waited through and refused only at the deadline; a queued run is waited for with the clock; an unrun branch is pushed once and then waited for; a red master refuses every merge but the declared fix" exit $fails fi [ -z "$(git status --porcelain --untracked-files=no)" ] || { echo "merge-to-master: the tree has uncommitted tracked changes; commit first" >&2; exit 1; } @@ -138,18 +145,24 @@ if [ ! -f "$G/igneum-gate-green/$SHA" ]; then fi AUTHOR=(-c user.name=igneum-labs -c user.email=337424239+[removed]) # the CI rule: the branch's own run on this exact commit must be green (pushed for a run when there is none, waited for when queued), and master must not be red -ci_gate "$SHA" "$BRANCH" git push -q origin "$SHA:refs/heads/$BRANCH" || exit 1 -master_gate || exit 1 +if remote_is_github; then + ci_gate "$SHA" "$BRANCH" git push -q "$REMOTE" "$SHA:refs/heads/$BRANCH" || exit 1 + master_gate || exit 1 + VERDICT="gate: green on ${SHA:0:8}, recorded by tools/ci/pre-push.sh; CI green on ${SHA:0:8}; the full gate runs in CI on this merge" +else + echo "merge-to-master: the remote $REMOTE is not GitHub (a mirror); the box gate stamp on ${SHA:0:8} is the verdict${IGNEUM_MASTER_EXCEPTION:+ (exception: $IGNEUM_MASTER_EXCEPTION)}" + VERDICT="gate: green on ${SHA:0:8}, recorded by tools/ci/pre-push.sh; landed on the $REMOTE mirror${IGNEUM_MASTER_EXCEPTION:+ under the exception declared by main: $IGNEUM_MASTER_EXCEPTION}" +fi for i in $(seq 1 "$TRIES"); do - git fetch -q origin master; TIP=$(git rev-parse origin/master) - if git merge-base --is-ancestor "$SHA" "$TIP"; then echo "merge-to-master: ${SHA:0:8} is already on origin/master $(git log -1 --format=%h origin/master)"; exit 0; fi + git fetch -q "$REMOTE" master; TIP=$(git rev-parse "$REMOTE/master") + if git merge-base --is-ancestor "$SHA" "$TIP"; then echo "merge-to-master: ${SHA:0:8} is already on $REMOTE/master $(git log -1 --format=%h "$REMOTE/master")"; exit 0; fi W=$(mktemp -d "${TMPDIR:-/tmp}/merge-to-master.XXXXXX"); rmdir "$W" git worktree add -q --detach "$W" "$TIP" - if ( cd "$W" && git "${AUTHOR[@]}" merge -q --no-ff -m "Merge $BRANCH ${SHA:0:8} into master (gate: green on ${SHA:0:8}, recorded by tools/ci/pre-push.sh; the full gate runs in CI on this merge)" "$SHA" ); then - if ( cd "$W" && git push -q origin HEAD:master ); then # the hook asks ci-state about ${SHA:0:8} once more on this push - git worktree remove --force "$W"; git fetch -q origin master - echo "merge-to-master: pushed on try $i: origin/master $(git log -1 --format='%h %ci' origin/master) $(TZ=Europe/London date '+%H:%M %Z')" - mirror_master "$(git rev-parse origin/master)"; exit 0 + if ( cd "$W" && git "${AUTHOR[@]}" merge -q --no-ff -m "Merge $BRANCH ${SHA:0:8} into master ($VERDICT)" "$SHA" ); then + if ( cd "$W" && git push -q "$REMOTE" HEAD:master ); then # on a GitHub remote the hook asks ci-state about ${SHA:0:8} once more + git worktree remove --force "$W"; git fetch -q "$REMOTE" master + echo "merge-to-master: pushed on try $i: $REMOTE/master $(git log -1 --format='%h %ci' "$REMOTE/master") $(TZ=Europe/London date '+%H:%M %Z')" + remote_is_github && mirror_master "$(git rev-parse "$REMOTE/master")"; exit 0 fi echo "merge-to-master: try $i: the push was rejected (master moved or the hook was red); again" else