chore(workflow): branch-and-PR working method + ADR-number reservation (ADR-0059)
Adopt a trackable working method now the repo is public: - PRs onto a protected main; --no-ff merge commits; one worktree per branch. - Reserve-first ADR numbering: scripts/adr-reserve.sh claims the next number atomically against main (push = compare-and-swap; ledger docs/adr/RESERVATIONS.log), so a number is stable from creation. - Worktree helpers scripts/wt-new.sh + wt-clean.sh. - Local-origin test harnesses (reserve 10/10, worktrees 7/7, shellcheck clean). Record the decision in ADR-0059, supersede ADR-0000's placeholder-until-merge numbering default, and add the lean CONTRIBUTING.md sections + CLAUDE.md operational rules.
This commit is contained in:
Executable
+82
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env bash
|
||||
# Verifies adr-reserve.sh against a local file:// origin (no network/keys).
|
||||
# Exercises: number computation, dry-run, real reserve+push, sequential
|
||||
# allocation, idempotence, hard-rejection abort (vs infinite loop), and the
|
||||
# race-vs-abort discriminator.
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT="$(cd "$(dirname "$0")" && pwd)/adr-reserve.sh"
|
||||
PASS=0 FAIL=0
|
||||
check() { # <desc> <expected> <actual>
|
||||
if [ "$2" = "$3" ]; then echo "PASS: $1 ($3)"; PASS=$((PASS+1));
|
||||
else echo "FAIL: $1 — expected '$2' got '$3'"; FAIL=$((FAIL+1)); fi
|
||||
}
|
||||
|
||||
T="$(mktemp -d)"
|
||||
trap 'rm -rf "$T"' EXIT
|
||||
|
||||
# ---- seed a bare origin with ADRs up to 0057 -------------------------------
|
||||
git init -q --bare "$T/origin.git"
|
||||
git clone -q "$T/origin.git" "$T/seed"
|
||||
git -C "$T/seed" config user.email t@t.io
|
||||
git -C "$T/seed" config user.name tester
|
||||
mkdir -p "$T/seed/docs/adr"
|
||||
echo "# legacy" > "$T/seed/docs/adr/0056-alpha.md"
|
||||
echo "# legacy" > "$T/seed/docs/adr/0057-beta.md"
|
||||
git -C "$T/seed" add -A
|
||||
git -C "$T/seed" commit -qm "seed: ADRs through 0057"
|
||||
git -C "$T/seed" branch -M main
|
||||
git -C "$T/seed" push -q origin main
|
||||
|
||||
# the repo the script runs inside (origin = our bare repo)
|
||||
git clone -q "$T/origin.git" "$T/work"
|
||||
git -C "$T/work" config user.email t@t.io
|
||||
git -C "$T/work" config user.name tester
|
||||
|
||||
run() { ( cd "$T/work" && "$@" ); }
|
||||
|
||||
echo "--- Test 1: dry-run computes 0058 (no push) ---"
|
||||
out="$(run env ADR_RESERVE_DRY_RUN=1 bash "$SCRIPT" feature-one "Feature One" 2>/dev/null)"
|
||||
check "dry-run number" "0058" "$out"
|
||||
git -C "$T/seed" pull -q origin main
|
||||
check "dry-run pushed nothing" "no" "$( [ -f "$T/seed/docs/adr/RESERVATIONS.log" ] && echo yes || echo no )"
|
||||
|
||||
echo "--- Test 2: real reserve → 0058, pushed to origin ---"
|
||||
out="$(run bash "$SCRIPT" feature-one "Feature One" 2>/dev/null)"
|
||||
check "first reserve number" "0058" "$out"
|
||||
git -C "$T/seed" pull -q origin main
|
||||
check "ledger landed on origin" "0058 feature-one" "$(awk 'NR==1{print $1" "$2}' "$T/seed/docs/adr/RESERVATIONS.log")"
|
||||
|
||||
echo "--- Test 3: second slug → 0059 (sequential) ---"
|
||||
out="$(run bash "$SCRIPT" feature-two 2>/dev/null)"
|
||||
check "second reserve number" "0059" "$out"
|
||||
|
||||
echo "--- Test 4: idempotence — re-reserving feature-one returns 0058 ---"
|
||||
out="$(run bash "$SCRIPT" feature-one 2>/dev/null)"
|
||||
check "idempotent re-reserve" "0058" "$out"
|
||||
git -C "$T/seed" pull -q origin main
|
||||
check "no duplicate ledger line" "1" "$(grep -c ' feature-one ' "$T/seed/docs/adr/RESERVATIONS.log")"
|
||||
|
||||
echo "--- Test 5: hard rejection (branch protection) aborts, does NOT loop ---"
|
||||
cat > "$T/origin.git/hooks/pre-receive" <<'HOOK'
|
||||
#!/bin/sh
|
||||
echo "protected branch: push declined" >&2
|
||||
exit 1
|
||||
HOOK
|
||||
chmod +x "$T/origin.git/hooks/pre-receive"
|
||||
set +e
|
||||
run bash "$SCRIPT" feature-three >/dev/null 2>"$T/err"; rc=$?
|
||||
set -e
|
||||
check "abort exit code nonzero" "yes" "$( [ "$rc" -ne 0 ] && echo yes || echo no )"
|
||||
check "abort cites protection, not a loop" "yes" "$(grep -qi 'not a race\|protection' "$T/err" && echo yes || echo no)"
|
||||
rm -f "$T/origin.git/hooks/pre-receive"
|
||||
|
||||
echo "--- Test 6: race discriminator matches a real non-ff message ---"
|
||||
ff_msg=" ! [rejected] main -> main (non-fast-forward)
|
||||
error: failed to push some refs"
|
||||
check "non-ff treated as race" "yes" \
|
||||
"$(grep -qiE 'non-fast-forward|fetch first|tip of your .* is behind|stale info' <<<"$ff_msg" && echo yes || echo no)"
|
||||
|
||||
echo
|
||||
echo "==== $PASS passed, $FAIL failed ===="
|
||||
[ "$FAIL" -eq 0 ]
|
||||
Reference in New Issue
Block a user