igneum/tools/ci/merge-to-master.sh
igneum-labs bb3e676f95 Gate: the green stamp and the fast path for merges (a full gate that ends GREEN over a clean tree records its commit; the hook lets a merge of a stamped branch onto the exact remote tip through on the light gate, CI runs the full one on landing); tools/ci/merge-to-master.sh; the wave script's seeds mode takes --wipe-genesis with the genesis and base-unit read-backs for the testnet go
Main, 7 October 2026: a 100 s hook gate on the merge commit against a master that moves every minute lost six pushes in a row.
Self-test: a fixture repository (unstamped branch, stamped branch, stale stamp, wrong tip, plain commit, dirty tree).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-07 13:54:10 +00:00

39 lines
2.9 KiB
Bash
Executable file

#!/usr/bin/env bash
# Merge a branch into master through a detached worktree and push, on the gate's fast path (main, 7 October 2026, the push-race
# class: master takes a push every minute and a 100 s hook gate on the merge commit lost six pushes in a row). The branch's own
# full gate must be GREEN on its HEAD over a clean tree (tools/ci/pre-push.sh records the stamp); the merge commit is then a
# two-parent merge of the branch onto the exact remote tip, which the hook lets through on the light gate (20 s) while CI runs
# the full gate on landing. Retries while master moves. Never force; never from a dirty branch.
#
# tools/ci/merge-to-master.sh [<branch>] [--tries N] default: the current branch, 6 tries
set -euo pipefail
ROOT=$(git rev-parse --show-toplevel); cd "$ROOT"
BRANCH="$(git rev-parse --abbrev-ref HEAD)"; TRIES=6
while [ $# -gt 0 ]; do case "$1" in --tries) TRIES="$2"; shift 2 ;; -*) echo "unknown option $1" >&2; exit 2 ;; *) BRANCH="$1"; shift ;; esac; done
[ -z "$(git status --porcelain --untracked-files=no)" ] || { echo "merge-to-master: the tree has uncommitted tracked changes; commit first" >&2; exit 1; }
SHA=$(git rev-parse "$BRANCH"); G=$(cd "$(git rev-parse --git-common-dir)" && pwd -P)
if [ ! -f "$G/igneum-gate-green/$SHA" ]; then
echo "merge-to-master: no green stamp for ${SHA:0:8}; running the full gate on the branch first"
bash tools/ci/pre-push.sh || exit 1
[ -f "$G/igneum-gate-green/$SHA" ] || { echo "merge-to-master: the gate was green but no stamp was written (dirty tree?)" >&2; exit 1; }
fi
AUTHOR=(-c user.name=igneum-labs -c user.email=337424239+[removed])
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
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
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
fi
echo "merge-to-master: try $i: the push was rejected (master moved or the hook was red); again"
else
echo "merge-to-master: the merge of $BRANCH onto ${TIP:0:8} does not apply cleanly; resolve on the branch (git merge origin/master) and retry" >&2
git worktree remove --force "$W"; exit 1
fi
git worktree remove --force "$W" 2>/dev/null || true
sleep 5
done
echo "merge-to-master: gave up after $TRIES tries" >&2; exit 1