diff --git a/README.md b/README.md index ed529de..cee0fa9 100644 --- a/README.md +++ b/README.md @@ -270,6 +270,98 @@ PASS(e, akgl_heap_release_string(width)); ``` +## 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, +but **Git configuration is not cloned**, so every clone has to be pointed at it +once: + +```sh +git config core.hooksPath scripts/hooks +``` + +Confirm it took effect: + +```sh +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`. + +### What the hook does + +On each commit it looks at the **staged** content of any added, copied, modified, +or renamed `.c`/`.h` file under `src/`, `include/`, `tests/`, or `util/`, and +reindents it if it does not already match the canonical style. Checking the +staged content rather than the working tree means what lands in the commit is +what was actually verified. + +When a file needs reindenting, the hook fixes it in the working tree and +re-stages it — but only when the index and working tree agree for that file. If +they differ, you have staged part of a file with `git add -p`, and re-staging +would sweep your unstaged work into the commit. Rather than do that silently the +hook stops and prints the commands to run yourself: + +```sh +scripts/reindent.sh path/to/file.c +git add path/to/file.c +``` + +`include/akgl/SDL_GameControllerDB.h` is generated and always skipped. + +### Requirements + +The hook drives Emacs in batch mode, because `cc-mode`'s indentation engine is +the definition of the style and `clang-format` cannot reproduce it exactly. If +`emacs` is not on `PATH` the hook prints a warning and allows the commit — a +hook that refuses to run without an optional tool only teaches people to reach +for `--no-verify`. Install Emacs to get the check; without it, formatting is on +you. + +### Bypassing and uninstalling + +Skip the hook for a single commit: + +```sh +git commit --no-verify +``` + +Remove it entirely: + +```sh +git config --unset core.hooksPath +``` + +### If you already have local hooks + +`core.hooksPath` **replaces** the hooks directory outright — once it is set, Git +stops reading `.git/hooks/` altogether, so any hooks you keep there will silently +stop firing. If that matters, leave `core.hooksPath` unset and symlink just this +one hook instead: + +```sh +ln -s ../../scripts/hooks/pre-commit .git/hooks/pre-commit +``` + +### Formatting without the hook + +The hook is a convenience, not the source of truth. The same check is available +directly, and is what you would run in CI: + +```sh +scripts/reindent.sh --check # list non-conforming files; exit 1 if any +scripts/reindent.sh # reindent every tracked C source in place +scripts/reindent.sh src/game.c # reindent specific files +``` + +`--check` exits `0` when everything conforms, `1` when a file needs reindenting, +and `2` if Emacs is missing or the script cannot run — so a CI job that treats +any non-zero status as failure will not mistake a broken toolchain for a clean +tree. + ## Mutation testing The mutation harness makes one deliberate source-code change at a time in a scratch copy, then rebuilds and runs the passing CTest suite to measure whether tests detect the change. The known-failing `character` test is excluded by default.