From 3f6f3f1233d4f8ed3e491baabc830bea9b1310d4 Mon Sep 17 00:00:00 2001 From: Logikoma Date: Wed, 5 Aug 2026 18:34:08 -0400 Subject: [PATCH] Add pre-push validation hook --- AGENTS.md | 12 ++++-- README.md | 24 +++++++++--- scripts/hooks/pre-push | 83 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 111 insertions(+), 8 deletions(-) create mode 100755 scripts/hooks/pre-push diff --git a/AGENTS.md b/AGENTS.md index 396927e..b885d40 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -169,10 +169,11 @@ a line, so it rewrites runs of spaces anywhere and destroys the hand-aligned value columns in the bit-flag tables in `actor.h` and `iterator.h`. Only leading whitespace is ever rewritten. -### The pre-commit hook +### The Git hooks `scripts/hooks/pre-commit` reindents staged C sources before the commit is -written. Enable it once per clone: +written, and `scripts/hooks/pre-push` runs the local build and test gates before +anything leaves the clone. Enable both once per clone: ```sh git config core.hooksPath scripts/hooks @@ -184,7 +185,12 @@ working tree and re-staged — but only if the index and working tree agree for that file. If they differ, re-staging would sweep unstaged work into the commit, so the hook stops and tells you to reindent and stage it yourself. It steps aside during a merge, and warns rather than blocking if Emacs is unavailable. -`git commit --no-verify` bypasses it. +`git commit --no-verify` and `git push --no-verify` bypass their respective hooks. +The pre-push hook excludes performance suites, runs optional cppcheck and Doxygen +when available, and puts the slower memcheck and mutation gates behind +`AKGL_HOOK_MEMCHECK=1` and `AKGL_HOOK_MUTATION=1`. Its private build is under +`.git/akgl-prepush`; CI remains the hard gate because `core.hooksPath` is local to +each clone. For reference, `cc-mode` defines the style as: diff --git a/README.md b/README.md index e2e0d8e..74ad8e1 100644 --- a/README.md +++ b/README.md @@ -69,9 +69,10 @@ moved, not because somebody edited a chapter. ## Git hooks -The repository ships a `pre-commit` hook that keeps committed C sources in the -project's canonical format (Emacs `cc-mode` "stroustrup"; see `AGENTS.md` for the -full style guide). The hook lives in `scripts/hooks/` and is version controlled, +The repository ships `pre-commit` and `pre-push` hooks. `pre-commit` keeps committed C +sources in the project's canonical format (Emacs `cc-mode` "stroustrup"; see `AGENTS.md` +for the full style guide), while `pre-push` runs the local build and test gates. The hooks +live in `scripts/hooks/` and are version controlled, but **Git configuration is not cloned**, so every clone has to be pointed at it once: @@ -85,8 +86,21 @@ Confirm it took effect: git rev-parse --git-path hooks # should print: scripts/hooks ``` -That is the whole installation. The hook is already committed with its -executable bit set, so nothing needs `chmod`. +That is the whole installation: it enables both hooks. They are already committed with +their executable bits set, so nothing needs `chmod`. + +The pre-push hook builds with `AKGL_WERROR=ON` and runs CTest without the performance +suites. It also runs cppcheck and Doxygen when their tools are available. Valgrind and +mutation testing are opt-in because they are slower: + +```sh +AKGL_HOOK_MEMCHECK=1 git push +AKGL_HOOK_MUTATION=1 git push +``` + +The builds live under `.git/akgl-prepush`, so the hook does not disturb the developer's +own `build/` directory. A delete-only push is skipped. Missing optional tools are warned +about and skipped; CI remains the hard gate. Use `git push --no-verify` to bypass the hook. ### What the hook does diff --git a/scripts/hooks/pre-push b/scripts/hooks/pre-push new file mode 100755 index 0000000..64fe43f --- /dev/null +++ b/scripts/hooks/pre-push @@ -0,0 +1,83 @@ +#!/bin/sh +# +# Run the cheap local gates before a branch is pushed. CI remains the hard gate; +# this hook makes common failures cheap to find in clones that enable it. +# +# Install (once per clone): +# git config core.hooksPath scripts/hooks +# +# Opt in to the slow gates: +# AKGL_HOOK_MEMCHECK=1 git push +# AKGL_HOOK_MUTATION=1 git push +# +# Bypass everything (Git's escape hatch): +# git push --no-verify + +set -eu + +ZERO_SHA=0000000000000000000000000000000000000000 +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 + +root=$(git rev-parse --show-toplevel) || exit 1 +cd "$root" || exit 1 + +builddir=${AKGL_HOOK_BUILD_DIR:-$(git rev-parse --git-dir)/akgl-prepush} +mkdir -p "$builddir" || exit 1 +logfile="$builddir/last.log" + +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 "$root/scripts/cppcheck.sh" ]; then + echo "pre-push: cppcheck" + run "$root/scripts/cppcheck.sh" +else + echo "pre-push: scripts/cppcheck.sh not installed, skipping cppcheck" +fi + +echo "pre-push: default build + ctest (excluding performance suites)" +run cmake -S . -B "$builddir/default" -DAKGL_WERROR=ON +run cmake --build "$builddir/default" +run ctest --test-dir "$builddir/default" -LE perf --output-on-failure + +if command -v doxygen >/dev/null 2>&1; then + echo "pre-push: doxygen" + run doxygen Doxyfile +else + echo "pre-push: doxygen not installed, skipping documentation check" +fi + +if [ "${AKGL_HOOK_MEMCHECK:-0}" = "1" ]; then + if [ -x "$root/scripts/memcheck.sh" ]; then + echo "pre-push: memcheck" + run env AKGL_BUILD_DIR="$builddir/default" "$root/scripts/memcheck.sh" + else + echo "pre-push: scripts/memcheck.sh not executable, skipping memcheck" + fi +fi + +if [ "${AKGL_HOOK_MUTATION:-0}" = "1" ]; then + echo "pre-push: mutation testing (this takes a while)" + run cmake --build "$builddir/default" --target mutation +fi + +echo "pre-push: OK" +exit 0