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>
39 lines
2.9 KiB
Bash
Executable file
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
|