Add pre-push validation hook
This commit is contained in:
12
AGENTS.md
12
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
|
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:
|
||||||
|
|
||||||
|
|||||||
24
README.md
24
README.md
@@ -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
83
scripts/hooks/pre-push
Executable 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
|
||||||
Reference in New Issue
Block a user