diff --git a/tools/ci/merge-to-master.sh b/tools/ci/merge-to-master.sh index 1e7bd890..842cc10a 100755 --- a/tools/ci/merge-to-master.sh +++ b/tools/ci/merge-to-master.sh @@ -55,6 +55,19 @@ master_gate() { esac } +# After a landing, master goes to the box mirrors too (7 October 2026, 18:25 UTC: build-2's /srv/igneum.git master sat at 15:27's +# a4bca198 while GitHub's carried cf7d6ccb, and three branches were cut from the stale tip; the mirrors only ever received what a +# build happened to push). One push per mirror in IGNEUM_MIRRORS (default: both box files), fast-forward only, best effort: a mirror +# that is down prints a line and never fails the landing. +mirror_master() { # + local sha="$1" m host key="${BS_KEY:-$HOME/.ssh/igneum_ed25519}" list="${IGNEUM_MIRRORS:-}" + if [ -z "$list" ]; then for f in "$HOME/.config/igneum/build-server" "$HOME/.config/igneum/build-server-2"; do [ -s "$f" ] && list="$list $(head -1 "$f" | tr -d '[:space:]'):/srv/igneum.git"; done; fi + for m in $list; do + if GIT_SSH_COMMAND="ssh -i $key -o BatchMode=yes -o ConnectTimeout=10" git push -q "$m" "$sha:refs/heads/master" 2>/dev/null; then echo "merge-to-master: mirror $m master -> ${sha:0:8}" + else echo "merge-to-master: mirror $m did not take master ${sha:0:8} (down, or not a fast-forward); the next landing tries again"; fi + done +} + if [ "$SELF_TEST" = 1 ]; then fails=0; d=$(mktemp -d); fake="$d/ci-state.sh" # the fake answers from $d/answer- (one line per call, consumed top to bottom; the last line repeats) and $d/answer-master @@ -102,8 +115,18 @@ success 4 u push run FIXES_MASTER=0 master_gate >/dev/null || { echo "self-test failed: an unknown master read that turned green refused a merge"; fails=1; } printf 'unknown - - gh: exit 1\n' > "$d/answer-master"; rm -f "$d/i-master" FIXES_MASTER=0 master_gate >/dev/null 2>&1 && { echo "self-test failed: a lasting unknown master read let a merge through"; fails=1; } + # the mirror push: a bare repository behind master is fast-forwarded; one that is not a fast-forward is left alone with a line + mkdir -p "$d/src" && ( cd "$d/src" && git init -q -b master . && git -c user.name=t -c user.email=t@t commit -q --allow-empty -m one && git -c user.name=t -c user.email=t@t commit -q --allow-empty -m two ) 2>/dev/null + git clone -q --bare "$d/src" "$d/mirror.git" && ( cd "$d/mirror.git" && git update-ref refs/heads/master "$(git rev-parse master~1)" ) + tip=$(git -C "$d/src" rev-parse master) + out=$( cd "$d/src" && IGNEUM_MIRRORS="$d/mirror.git" mirror_master "$tip" ) + [ "$(git -C "$d/mirror.git" rev-parse master)" = "$tip" ] || { echo "self-test failed: the mirror was not fast-forwarded to the landed master: $out"; fails=1; } + ( cd "$d/src" && git checkout -q -b other master~1 && git -c user.name=t -c user.email=t@t commit -q --allow-empty -m fork ); fork=$(git -C "$d/src" rev-parse other) + 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 rm -rf "$d" - [ "$fails" = 0 ] && echo "self-test passed: 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: 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; } @@ -125,7 +148,8 @@ for i in $(seq 1 "$TRIES"); do 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')"; exit 0 + 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 fi echo "merge-to-master: try $i: the push was rejected (master moved or the hook was red); again" else