Files
libakerror/.githooks/pre-push
Logikoma 5dfb03eadd
All checks were successful
libakerror CI Build / cmake_build_freestanding (push) Successful in 2m37s
libakerror CI Build / cmake_build (push) Successful in 2m52s
libakerror CI Build / sanitizer (push) Successful in 2m56s
libakerror CI Build / coverage (push) Successful in 2m49s
libakerror CI Build / mutation_test (push) Successful in 47m10s
Add local pre-push validation gates
Co-authored-by: Andrew Kesterson <andrew@starfort.tech>
2026-08-05 18:33:54 -04:00

95 lines
2.8 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# Run the repository's cheap gates before a push reaches the forge. CI remains
# the hard gate; this hook makes the common failure modes cheap to find.
#
# Install once per clone:
#
# git config core.hooksPath .githooks
#
# The mutation harness is deliberately opt-in because it is slow:
#
# AKERR_HOOK_MUTATION=1 git push
#
# Git's normal escape hatch remains available:
#
# git push --no-verify
#
# Builds go under .git/akerr-prepush so the hook does not disturb build/.
# Override that location with AKERR_HOOK_BUILD_DIR when needed.
set -u
ZERO_SHA=0000000000000000000000000000000000000000
root=$(git rev-parse --show-toplevel) || exit 1
cd "$root" || exit 1
# A delete-only push, or an invocation with no refs on stdin, has no new code
# to validate.
has_updates=0
while read -r _local_ref local_sha _remote_ref _remote_sha; do
if [ "$local_sha" != "$ZERO_SHA" ]; then
has_updates=1
fi
done
if [ "$has_updates" -eq 0 ]; then
exit 0
fi
builddir="${AKERR_HOOK_BUILD_DIR:-$(git rev-parse --git-dir)/akerr-prepush}"
mkdir -p "$builddir" || exit 1
logfile="$builddir/last.log"
# Keep normal output short, but preserve the complete failing command output.
run() {
if ! "$@" > "$logfile" 2>&1; then
echo >&2
echo "pre-push: FAILED: $*" >&2
echo "---------------------------------------------------------------" >&2
cat "$logfile" >&2
echo "---------------------------------------------------------------" >&2
echo "pre-push: push aborted. Use 'git push --no-verify' to override." >&2
exit 1
fi
}
if [ -f scripts/cppcheck.sh ] && command -v cppcheck > /dev/null 2>&1; then
echo "pre-push: cppcheck"
run bash scripts/cppcheck.sh
elif [ ! -f scripts/cppcheck.sh ]; then
echo "pre-push: scripts/cppcheck.sh not present, skipping cppcheck"
else
echo "pre-push: cppcheck not installed, skipping cppcheck"
fi
echo "pre-push: default build + ctest"
run cmake -S . -B "$builddir/default"
run cmake --build "$builddir/default"
run ctest --test-dir "$builddir/default" --output-on-failure
echo "pre-push: AKERR_USE_STDLIB=OFF build + ctest"
run cmake -S . -B "$builddir/stdlib-off" \
-DAKERR_USE_STDLIB=OFF -DAKERR_THREADS=none
run cmake --build "$builddir/stdlib-off"
run ctest --test-dir "$builddir/stdlib-off" --output-on-failure
if [ "${AKERR_HOOK_MUTATION:-0}" = "1" ]; then
if command -v python3 > /dev/null 2>&1; then
echo "pre-push: mutation testing (this takes a while)"
if ! python3 scripts/mutation_test.py \
--target src/error.c \
--threshold "${AKERR_MUTATION_THRESHOLD:-65}"; then
echo >&2
echo "pre-push: mutation score below threshold. Push aborted." >&2
echo "pre-push: use 'git push --no-verify' to override." >&2
exit 1
fi
else
echo "pre-push: python3 not installed, skipping mutation testing"
fi
fi
echo "pre-push: OK"
exit 0