Add pre-push validation hook
All checks were successful
libakgl CI Build / cmake_build (push) Successful in 8m44s
libakgl CI Build / performance (push) Successful in 10m6s
libakgl CI Build / memory_check (push) Successful in 13m24s
libakgl CI Build / mutation_test (push) Successful in 28m17s

This commit is contained in:
2026-08-05 18:34:08 -04:00
parent 8477ea698b
commit 3f6f3f1233
3 changed files with 111 additions and 8 deletions

View File

@@ -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 value columns in the bit-flag tables in `actor.h` and `iterator.h`. Only leading
whitespace is ever rewritten. whitespace is ever rewritten.
### The pre-commit hook ### The Git hooks
`scripts/hooks/pre-commit` reindents staged C sources before the commit is `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 ```sh
git config core.hooksPath scripts/hooks 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, 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 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. 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: For reference, `cc-mode` defines the style as:

View File

@@ -69,9 +69,10 @@ moved, not because somebody edited a chapter.
## Git hooks ## Git hooks
The repository ships a `pre-commit` hook that keeps committed C sources in the The repository ships `pre-commit` and `pre-push` hooks. `pre-commit` keeps committed C
project's canonical format (Emacs `cc-mode` "stroustrup"; see `AGENTS.md` for the sources in the project's canonical format (Emacs `cc-mode` "stroustrup"; see `AGENTS.md`
full style guide). The hook lives in `scripts/hooks/` and is version controlled, 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 but **Git configuration is not cloned**, so every clone has to be pointed at it
once: once:
@@ -85,8 +86,21 @@ Confirm it took effect:
git rev-parse --git-path hooks # should print: scripts/hooks git rev-parse --git-path hooks # should print: scripts/hooks
``` ```
That is the whole installation. The hook is already committed with its That is the whole installation: it enables both hooks. They are already committed with
executable bit set, so nothing needs `chmod`. 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 ### What the hook does

83
scripts/hooks/pre-push Executable file
View File

@@ -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