#!/usr/bin/env bash # # pre-push: run the cheap local gates before a commit reaches the forge. # # Install once per clone: # # git config core.hooksPath .githooks # # The AKGL build and mutation harness are opt-in because they are expensive: # # AKBASIC_HOOK_AKGL=1 git push # AKBASIC_HOOK_MUTATION=1 git push # # Bypass everything with Git's escape hatch: git push --no-verify set -u ZERO_SHA=0000000000000000000000000000000000000000 root=$(git rev-parse --show-toplevel) || exit 1 cd "$root" || exit 1 # A push that only deletes refs, or an empty push input, has no new commit to # test. Git calls hooks for those pushes too. 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 if [ ! -f deps/libakerror/CMakeLists.txt ]; then echo "pre-push: deps/libakerror is empty. Run:" >&2 echo " git submodule update --init --recursive" >&2 exit 1 fi builddir="${AKBASIC_HOOK_BUILD_DIR:-$(git rev-parse --git-dir)/akbasic-prepush}" mkdir -p "$builddir" || exit 1 logfile="$builddir/last.log" # Keep output quiet on success, but retain the complete failing command output # so a failed push is actionable without rerunning the gate by hand. 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 [ -x scripts/cppcheck.sh ]; then if command -v cppcheck > /dev/null 2>&1; then echo "pre-push: cppcheck" run scripts/cppcheck.sh else echo "pre-push: cppcheck not installed, skipping the optional check" >&2 fi else echo "pre-push: scripts/cppcheck.sh not present, skipping the optional check" >&2 fi echo "pre-push: default build + ctest" run cmake -S . -B "$builddir/default" run cmake --build "$builddir/default" --parallel 2 run ctest --test-dir "$builddir/default" --output-on-failure echo "pre-push: sanitizer build + ctest" run cmake -S . -B "$builddir/sanitize" -DAKBASIC_SANITIZE=ON run cmake --build "$builddir/sanitize" --parallel 2 run ctest --test-dir "$builddir/sanitize" --output-on-failure if [ "${AKBASIC_HOOK_AKGL:-0}" = "1" ]; then echo "pre-push: AKGL build + ctest" run cmake -S . -B "$builddir/akgl" -DAKBASIC_WITH_AKGL=ON run cmake --build "$builddir/akgl" --parallel 2 run env SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy \ SDL_RENDER_DRIVER=software ctest --test-dir "$builddir/akgl" --output-on-failure fi if [ "${AKBASIC_HOOK_MUTATION:-0}" = "1" ]; then threshold="${AKBASIC_MUTATION_THRESHOLD:-65}" mutants="${AKBASIC_HOOK_MUTANTS:-260}" echo "pre-push: mutation testing, threshold ${threshold}%" run python3 scripts/mutation_test.py \ --target src/symtab.c \ --max-mutants "$mutants" \ --threshold "$threshold" fi echo "pre-push: OK" exit 0