Something went wrong. Try again.
My own corner of monopam
Something went wrong. Try again.
6.9 kB · 155 lines
Shell
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156#!/usr/bin/env bash# Reads what .githooks/reference-transaction does in a primary: it tells the# mq at $HOME/.local/bin/mq that a ref moved, refuses no update, and carries# the line that keeps mq from starting it for mq's own writes.## scripts/check-reference-transaction.sh .githooks/reference-transaction MQ## MQ is the mq this tree builds. Nothing is contacted.
set -euo pipefail
usage='usage: check-reference-transaction.sh <reference-transaction> <mq>'hook=${1:?$usage}mq_built=${2:?$usage}
abs() { printf '%s\n' "$(cd -- "$(dirname -- "$1")" && pwd -P)/$(basename -- "$1")"; }hook=$(abs "$hook")mq_built=$(abs "$mq_built")
tmp=$(mktemp -d "${TMPDIR:-/tmp}/check-reference-transaction.XXXXXX")tmp=$(cd -- "$tmp" && pwd -P)trap 'rm -rf -- "$tmp"' EXIT
fail() { printf 'check-reference-transaction: %s\n' "$1" >&2 shift || true for line in "$@"; do printf ' %s\n' "$line" >&2; done exit 1}
unset GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE GIT_OBJECT_DIRECTORY \ GIT_COMMON_DIR GIT_ALTERNATE_OBJECT_DIRECTORIES GIT_PREFIX \ GIT_QUARANTINE_PATHexport GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_SYSTEM=/dev/null
# Where the hook calls mq, $HOME/.local/bin/mq, a wrapper around the mq this# tree builds: each call appends `== <arguments>` and the lines on its# standard input to $tmp/mq.log, then hands both to the real one, so what the# hook told mq is read and mq still does what it would.export HOME=$tmp/homemq_log=$tmp/mq.logmkdir -p "$HOME/.local/bin": > "$mq_log"cat > "$HOME/.local/bin/mq" <<WRAP#!/bin/shinput=\$(cat){ echo "== \$*"; printf '%s\n' "\$input"; } >> "$mq_log"printf '%s\n' "\$input" | "$mq_built" "\$@"WRAPchmod +x "$HOME/.local/bin/mq"
primary=$tmp/primarymkdir -p "$primary/.githooks"cp "$hook" "$primary/.githooks/reference-transaction"# The rules beside it, as the primary has them.cp -R "$(dirname -- "$hook")/lib" "$primary/.githooks/lib"chmod +x "$primary/.githooks/reference-transaction"git -C "$primary" init --quiet --initial-branch=maingit -C "$primary" config user.name 'hook check'git -C "$primary" config user.email 'check@invalid'git -C "$primary" config commit.gpgsign falsegit -C "$primary" config mq.pool "$tmp/pool"printf 'let x = 0\n' > "$primary/lib.ml"git -C "$primary" add -- lib.mlgit -C "$primary" commit --quiet -m baselineout=$("$mq_built" -C "$primary" init 2>&1) || fail "mq init failed" "$out"# Absolute, as the primary is wired: git runs a push's hooks from the git# directory, where a relative hooks path names no hook at all.git -C "$primary" config core.hooksPath "$primary/.githooks"
# mq's own writes start the hook for none of their phases: a land, a dozen# transactions, calls the wrapper not once, and mq stats counts no hook run.# A hook that does more than tell mq, or does not say so, is started for# every phase of each.wt=$("$mq_built" -C "$primary" new n1 2> "$tmp/new.err") || fail "mq new n1 failed" "$(cat "$tmp/new.err")"printf '1\n' > "$wt/n1"git -C "$wt" add -- n1git -C "$wt" -c core.hooksPath=/dev/null commit --quiet -m 'add n1': > "$mq_log"out=$("$mq_built" -C "$primary" land n1 2>&1) || fail "mq land n1 failed" "$out"[ ! -s "$mq_log" ] || fail "mq's own writes for a land started the hook" "$(grep -c '^==' "$mq_log") calls"[ "$(git -C "$primary" log -1 --format=%s main)" = 'add n1' ] || fail "the land did not reach main" "$(git -C "$primary" log --oneline -3 main)"stats=$("$mq_built" -C "$primary" stats 2>&1) || fail "mq stats failed" "$stats"case $stats in*"hook reference-transaction"*) fail "mq stats counts hook runs for its own lands" "$stats" ;;esac
# A hand commit on main, made by cherry-pick, which runs no commit hook: the# hook tells mq, and mq wakes its scheduler. Nothing reads the message here,# so a message naming an issue moves main like any other; it is read at the# queue's entry and on its way to a remote (scripts/check-no-tracker-id.sh).out=$("$mq_built" -C "$primary" schedule start --scheduler=test 2>&1) || fail "mq schedule start failed" "$out"wake=$(git -C "$primary" rev-parse --path-format=absolute --git-common-dir)/mq/wake# The commit picked is made beside main, moving no ref.id=$(printf '%s-%s' space 1a2b3)side=$(git -C "$primary" hash-object -w --stdin <<< 'let y = 1')tree=$( (git -C "$primary" ls-tree main; printf '100644 blob %s\tside.ml\n' "$side") | git -C "$primary" mktree)picked=$(git -C "$primary" commit-tree -p main -m "side: y, for $id" "$tree")rm -f -- "$wake": > "$mq_log"out=$(git -C "$primary" cherry-pick "$picked" 2>&1) || fail "a cherry-pick onto main was refused" "$out"[ "$(git -C "$primary" log -1 --format=%s main)" = "side: y, for $id" ] || fail "the cherry-pick did not move main"grep -q '^== hook reference-transaction committed$' "$mq_log" || fail "the hook did not tell mq that main moved" "$(cat "$mq_log")"grep -q ' refs/heads/main$' "$mq_log" || fail "the hook did not hand mq main's line" "$(cat "$mq_log")"[ -e "$wake" ] || fail "a hand commit on main did not wake mq's scheduler"
# A commit starts mq once, in its committed phase: Git runs the hook at every# phase of every transaction, and the hook starts mq only for a committed# transaction naming a branch, or for a probe in any phase.: > "$mq_log"out=$(git -C "$primary" commit --quiet --allow-empty -m 'lib: one run' 2>&1) || fail "an empty commit on main was refused" "$out"calls=$(grep '^==' "$mq_log" || true)[ "$calls" = '== hook reference-transaction committed' ] || fail "a commit started mq other than once, in its committed phase" "$calls"# A probe comes in a transaction mq aborts, so the hook answers it in the# phases before: mq init then reads that the hook calls mq.out=$("$mq_built" -C "$primary" init 2>&1) || fail "mq init failed" "$out"case $out in*"the reference-transaction hook is installed"*) ;;*) fail "the hook left mq init's probe unanswered" "$out" ;;esac
# Whatever mq answers, the update goes through.mkdir -p "$tmp/failing/.local/bin"printf '#!/bin/sh\ncat >/dev/null\necho "mq failed" >&2\nexit 1\n' > "$tmp/failing/.local/bin/mq"chmod +x "$tmp/failing/.local/bin/mq"before=$(git -C "$primary" rev-parse main)printf 'let x = 1\n' > "$primary/lib.ml"git -C "$primary" add -- lib.mlout=$(HOME=$tmp/failing git -C "$primary" commit --quiet -m 'lib: x is one' 2>&1) || fail "a commit was refused when mq failed" "$out"[ "$(git -C "$primary" rev-parse main)" != "$before" ] || fail "main did not move when mq failed"case $out in*"mq failed"*) fail "mq's output reached the committer" "$out" ;;esac
# And with no mq at all.before=$(git -C "$primary" rev-parse main)out=$(HOME=$tmp/nobody git -C "$primary" commit --quiet --allow-empty -m 'lib: nothing' 2>&1) || fail "a commit was refused with no mq" "$out"[ "$(git -C "$primary" rev-parse main)" != "$before" ] || fail "main did not move with no mq"
printf 'check-reference-transaction: ok\n'