fix(git): push-guard — resolve re-review blockers; mutation table now generated
ci/woodpecker/pr/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
Authored by installer-7; committed by mos-claude (no credential for this remote). BLOCKER 1 — verify-clean-clone.sh had B1's own defect: it copied WORKING-TREE files into a scratch repo and asserted the SCRATCH index, so a stale local exec bit was laundered in and it reported success while the committed artifact was still 100644. v2 reads mode from 'git ls-tree' of the SOURCE COMMIT and runs from 'git clone --no-local --no-hardlinks' of that commit; there is no 'cp' in the file. Proven by A/B against one laundered repo: v1 EXIT=0, v2 EXIT=1 (both observed, not asserted). New test-verify-clean-clone.sh 6/6, incl. a fixture that first proves it really is git=100644 disk=755 before testing it. BLOCKER 2 — empty-merge needle + non-empty-merge control, both asserting on OUTPUT; the fixture first proves it IS a merge with a tree identical to both parents. The 'non-blocking' README item was not documentation: regenerating its mutation table (now generated by mutate-push-guard.sh, not hand-numbered) found TWO genuinely surviving mutants against a 46/46 green suite — staged-but-uncommitted opt-out honoured, and unparseable committed config ignored. Both still exit 6 under mutation because control falls to a SIBLING refusal, so an exit-code-only assertion would have been satisfied by the wrong branch. It also found a live instance of the substring-anchor defect already in the suite: three branches print 'OPT-OUT IS NOT REVIEWABLE', so deleting one let the needle RE-POINT to a sibling instead of failing. Re-anchored. Suite 44 -> 46. Verifier 6/6. 13 mutants, 0 survived. Also: README reformatted to satisfy 'pnpm format:check' (prettier), which was failing CI at both prior heads — a gate neither author nor reviewer had exercised. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01YKj59Qadrb2WBLaePvkM7H
This commit is contained in:
co-authored by
Claude Opus 5
parent
b0fb208b89
commit
8310075d33
@@ -1,75 +1,127 @@
|
||||
#!/usr/bin/env bash
|
||||
# verify-clean-clone.sh -- run the suite from a FRESH CLONE, never the working copy.
|
||||
# verify-clean-clone.sh -- prove the COMMITTED artifact runs, from a clean clone.
|
||||
#
|
||||
# BLOCKER 1 EXISTED BECAUSE EVERY RUN HAPPENED WHERE THE EXEC BIT WAS ALREADY SET.
|
||||
# Two people ran 32/32 on files whose local mode had been set by hand at creation
|
||||
# time; the COMMITTED mode was 100644, so the delivered artifact returned 126
|
||||
# Permission denied and aborted before printing a summary. The artifact had never
|
||||
# run for anyone.
|
||||
# ================== THIS FILE IS THE SECOND VERSION. THE FIRST HAD B1. ========
|
||||
# B1 was: the delivered scripts were committed mode 100644, so the artifact
|
||||
# returned 126 Permission denied for anyone who cloned it. Two of us ran 32/32
|
||||
# because our local working copies had the exec bit set by hand at creation.
|
||||
#
|
||||
# "Run it somewhere real" was not enough. The missing word is CLEAN: run it from a
|
||||
# checkout that carries only what the repository actually stores. Local state you
|
||||
# did not commit is invisible to you precisely because it is yours.
|
||||
# I wrote v1 of this file to prevent exactly that. V1 COPIED THE WORKING-TREE
|
||||
# FILES INTO A SCRATCH REPOSITORY AND ASSERTED THE SCRATCH REPOSITORY'S INDEX.
|
||||
# cp preserves the local exec bit, so `git add` in the scratch repo recorded
|
||||
# 100755 NO MATTER WHAT THE REAL REPOSITORY STORED. Reviewer reproduction:
|
||||
# git update-index --chmod=-x push-guard.sh test-push-guard.sh
|
||||
# ./verify-clean-clone.sh -> EXIT 0, "CLEAN CLONE: suite ran and passed"
|
||||
# while the real index read 100644 -- the precise contaminated state B1 was.
|
||||
#
|
||||
# This script commits the artifacts into a scratch repo, clones that repo, and
|
||||
# runs the suite from the clone -- so the only modes in play are the ones git
|
||||
# recorded. It also asserts the mode positively, so a future commit that drops the
|
||||
# bit fails here instead of at the next reviewer.
|
||||
# THE CONTROL FOR THE DEFECT INHERITED THE DEFECT, BECAUSE IT MEASURED A COPY
|
||||
# INSTEAD OF THE SUBJECT. cp LAUNDERS MODE.
|
||||
#
|
||||
# Both of v1's mechanisms were right -- assert the INDEX not the disk, execute
|
||||
# DIRECTLY not via `bash script`. They were applied to the wrong repository.
|
||||
# So v2 changes what is measured, not how:
|
||||
# * mode is read from the SOURCE repository's COMMITTED TREE (git ls-tree),
|
||||
# which no local chmod can influence;
|
||||
# * the tree under test is produced by CLONING THAT COMMIT, so every mode
|
||||
# comes out of the object store rather than off my filesystem.
|
||||
# There is no cp anywhere in this file, and that is deliberate.
|
||||
#
|
||||
# It also REFUSES rather than passes when the artifacts are untracked: an
|
||||
# artifact that is not committed has no mode to verify, and a verifier that
|
||||
# silently succeeds on nothing is the vacuous-absence failure all over again.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
readonly EX_FAIL=1
|
||||
readonly EX_USAGE=64
|
||||
|
||||
REPO=""
|
||||
REV="HEAD"
|
||||
|
||||
die() { printf 'usage error: %s\n' "$1" >&2; exit "$EX_USAGE"; }
|
||||
|
||||
while (( $# )); do
|
||||
case "$1" in
|
||||
--repo) REPO="${2:-}"; shift 2 ;;
|
||||
--rev) REV="${2:-}"; shift 2 ;;
|
||||
*) die "unknown argument: $1" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
[[ -n "$REPO" ]] || REPO="$HERE"
|
||||
|
||||
ARTIFACTS=(push-guard.sh test-push-guard.sh verify-clean-clone.sh mutate-push-guard.sh)
|
||||
SUITE="test-push-guard.sh"
|
||||
|
||||
# --- the subject must be a real repository, or there is nothing to verify -----
|
||||
if ! ROOT="$(git -C "$REPO" rev-parse --show-toplevel 2>/dev/null)"; then
|
||||
printf 'REFUSING: %s is not inside a git repository.\n' "$REPO" >&2
|
||||
printf 'This verifier checks the COMMITTED mode of the artifact. An uncommitted\n' >&2
|
||||
printf 'artifact has no committed mode, and passing here would prove nothing.\n' >&2
|
||||
exit "$EX_FAIL"
|
||||
fi
|
||||
if ! REV_SHA="$(git -C "$ROOT" rev-parse --verify --quiet "${REV}^{commit}")"; then
|
||||
printf 'REFUSING: %s does not resolve to a commit in %s\n' "$REV" "$ROOT" >&2
|
||||
exit "$EX_FAIL"
|
||||
fi
|
||||
|
||||
printf '=== subject ===\n'
|
||||
printf ' repo %s\n rev %s (%s)\n\n' "$ROOT" "$REV" "$REV_SHA"
|
||||
|
||||
# --- 1. MODE, FROM THE COMMITTED TREE OF THE SUBJECT -------------------------
|
||||
# git ls-tree reports what the COMMIT records. Nothing on my filesystem can
|
||||
# move this number -- which is the whole point, and what v1 got wrong.
|
||||
printf '=== committed modes (git ls-tree %s) ===\n' "$REV"
|
||||
rc=0
|
||||
for f in "${ARTIFACTS[@]}"; do
|
||||
entry="$(git -C "$ROOT" ls-tree "$REV_SHA" -- "$f")"
|
||||
if [[ -z "$entry" ]]; then
|
||||
printf ' FAIL %s is NOT TRACKED at %s -- nothing committed to verify\n' "$f" "$REV"
|
||||
rc=1; continue
|
||||
fi
|
||||
mode="${entry%% *}"; rest="${entry#* }"; type="${rest%% *}"
|
||||
if [[ "$type" != blob ]]; then
|
||||
printf ' FAIL %s is a %s at %s, not a regular file\n' "$f" "$type" "$REV"
|
||||
rc=1; continue
|
||||
fi
|
||||
case "$mode" in
|
||||
100755) printf ' ok %s committed %s\n' "$f" "$mode" ;;
|
||||
120000) printf ' FAIL %s is a SYMLINK (%s) -- the reviewed blob is a path, not the code\n' "$f" "$mode"; rc=1 ;;
|
||||
*) printf ' FAIL %s committed %s, needs 100755\n' "$f" "$mode"
|
||||
printf ' fix with: git update-index --chmod=+x %s && commit\n' "$f"
|
||||
rc=1 ;;
|
||||
esac
|
||||
done
|
||||
if (( rc != 0 )); then
|
||||
printf '\nREFUSING TO CONTINUE: the COMMITTED modes are wrong, whatever the disk says.\n'
|
||||
printf 'A clone of this commit would exit 126 for the next person.\n'
|
||||
exit "$EX_FAIL"
|
||||
fi
|
||||
|
||||
# --- 2. RUN FROM A CLONE OF THAT COMMIT --------------------------------------
|
||||
# Cloning materialises every file from the object store, so the modes on disk
|
||||
# are the COMMITTED modes by construction. No cp, no local state, nothing this
|
||||
# machine can contribute.
|
||||
WORK="$(mktemp -d)"
|
||||
trap 'rm -rf "$WORK"' EXIT
|
||||
|
||||
ARTIFACTS=(push-guard.sh test-push-guard.sh)
|
||||
|
||||
printf '=== packaging from %s ===\n' "$HERE"
|
||||
mkdir -p "$WORK/src"
|
||||
for f in "${ARTIFACTS[@]}"; do
|
||||
cp "$HERE/$f" "$WORK/src/$f"
|
||||
done
|
||||
[[ -f "$HERE/README.md" ]] && cp "$HERE/README.md" "$WORK/src/README.md"
|
||||
|
||||
cd "$WORK/src"
|
||||
git init -q .
|
||||
git config user.email "[email protected]"
|
||||
git config user.name "Clean Clone"
|
||||
git add -A
|
||||
|
||||
# THE ASSERTION THAT WOULD HAVE CAUGHT BLOCKER 1.
|
||||
# Check the mode in the INDEX -- what git will actually store -- not the mode on
|
||||
# disk, which is what misled us. These differ exactly when it matters.
|
||||
rc=0
|
||||
for f in "${ARTIFACTS[@]}"; do
|
||||
mode="$(git ls-files -s -- "$f" | awk '{print $1}')"
|
||||
if [[ "$mode" != "100755" ]]; then
|
||||
printf ' FAIL %s is mode %s in the index (needs 100755)\n' "$f" "$mode"
|
||||
printf ' fix with: git update-index --chmod=+x %s\n' "$f"
|
||||
rc=1
|
||||
else
|
||||
printf ' ok %s is mode %s in the index\n' "$f" "$mode"
|
||||
fi
|
||||
done
|
||||
(( rc == 0 )) || { printf '\nREFUSING TO CONTINUE: the committed modes are wrong.\n'; exit 1; }
|
||||
|
||||
git commit -q -m "artifacts under test"
|
||||
|
||||
printf '\n=== cloning (no local mode bits survive this) ===\n'
|
||||
cd "$WORK"
|
||||
git clone -q src clone
|
||||
cd clone
|
||||
printf '\n=== cloning %s (no local mode bits survive this) ===\n' "$REV_SHA"
|
||||
git clone -q --no-local --no-hardlinks "$ROOT" "$WORK/clone"
|
||||
git -C "$WORK/clone" -c advice.detachedHead=false checkout -q "$REV_SHA"
|
||||
|
||||
for f in "${ARTIFACTS[@]}"; do
|
||||
printf ' clone mode: %s %s\n' "$(stat -c '%a' "$f")" "$f"
|
||||
printf ' clone disk mode: %s %s\n' "$(stat -c '%a' "$WORK/clone/$f")" "$f"
|
||||
done
|
||||
|
||||
printf '\n=== executing DIRECTLY (./script), not via `bash script` ===\n'
|
||||
# Running `bash script` masks a missing exec bit completely -- it is how the
|
||||
# original 32/32 was obtained. Direct execution is the thing under test.
|
||||
if ! ./test-push-guard.sh; then
|
||||
printf '\n=== executing DIRECTLY (./%s), not via "bash %s" ===\n' "$SUITE" "$SUITE"
|
||||
# Invoking the interpreter explicitly masks a missing exec bit completely -- it
|
||||
# is how the original green was obtained. Direct execution is the thing under
|
||||
# test, so the mode has to be real for this line to succeed.
|
||||
cd "$WORK/clone"
|
||||
if ! "./$SUITE"; then
|
||||
printf '\nCLEAN-CLONE RUN FAILED.\n'
|
||||
exit 1
|
||||
exit "$EX_FAIL"
|
||||
fi
|
||||
|
||||
printf '\n=== CLEAN CLONE: suite ran and passed from a fresh checkout ===\n'
|
||||
printf '\n=== CLEAN CLONE: the COMMITTED artifact ran and passed ===\n'
|
||||
|
||||
Reference in New Issue
Block a user