Codify the canonical C style and add reindent tooling
AGENTS.md described style in one paragraph that said little more than
"follow the surrounding code", which was not actionable once files had
drifted apart. Replace it with an explicit specification.
The canonical style is Emacs cc-mode "stroustrup" with tabs enabled: a
4-column offset with 8-column tabs, so depth 1 is four spaces, depth 2 is
one tab, depth 3 is a tab plus four spaces. Most of src/ already followed
this; what looked like randomly mixed tabs and spaces was simply cc-mode
output. Document the byte ladder explicitly, since editors that expand
tabs or assume a 4-column tab silently corrupt it.
Rules beyond indentation are derived from counted majorities in the
existing code rather than invented: errctx over e (92 to 45), padded
control parens (140 to 7), pointer binding to the identifier (486 to 5),
and always-brace (only four unbraced bodies exist). Brace and else
placement are called out as house conventions that cc-mode does not
enforce, so "run the indenter" and "follow the guide" cannot conflict.
Also record the naming, error-handling, and API-surface rules that a
consistency sweep showed were being broken silently -- in particular that
a *_RETURN macro inside an ATTEMPT block skips CLEANUP.
Add the tooling to apply it:
.dir-locals.el applies the style to every c-mode buffer
scripts/reindent.el batch reindent via Emacs
scripts/reindent.sh reindent or --check the tree
scripts/hooks/pre-commit reindents staged sources
reindent.el deliberately avoids Emacs' tabify: its tabify-regexp is
" [ \t]+", which is not anchored to the start of 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, and lines starting inside a string literal are skipped.
The hook checks staged content rather than the working tree, so what is
committed is what was verified. It re-stages a fixed file only when the
index and working tree agree, otherwise a partial `git add -p` would
sweep unstaged work into the commit.
Enable with: git config core.hooksPath scripts/hooks
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 18:17:26 -04:00
|
|
|
#!/bin/sh
|
|
|
|
|
#
|
|
|
|
|
# Reindent staged C sources to the canonical style before the commit is made.
|
|
|
|
|
# See AGENTS.md -> "Coding Style".
|
|
|
|
|
#
|
|
|
|
|
# Enable with:
|
|
|
|
|
# git config core.hooksPath scripts/hooks
|
|
|
|
|
# Bypass a single commit with `git commit --no-verify`.
|
|
|
|
|
#
|
|
|
|
|
# The hook checks the *staged* content, not the working tree, so what gets
|
|
|
|
|
# committed is what was verified. When a file needs reindenting it is fixed in
|
|
|
|
|
# the working tree and re-staged -- but only when the working tree and the index
|
|
|
|
|
# agree for that file. If they disagree (a partial `git add -p`), re-staging
|
|
|
|
|
# would sweep unstaged work into the commit, so the hook stops and asks you to
|
|
|
|
|
# do it yourself.
|
|
|
|
|
|
|
|
|
|
set -eu
|
|
|
|
|
|
|
|
|
|
root=$(git rev-parse --show-toplevel)
|
|
|
|
|
reindent="$root/scripts/reindent.sh"
|
|
|
|
|
|
|
|
|
|
# Leave conflict resolution alone.
|
|
|
|
|
if [ -e "$root/.git/MERGE_HEAD" ]; then
|
|
|
|
|
exit 0
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
if [ ! -x "$reindent" ]; then
|
|
|
|
|
echo "pre-commit: $reindent missing or not executable; skipping style check" >&2
|
|
|
|
|
exit 0
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Without Emacs the canonical style cannot be applied or even checked. Warn
|
|
|
|
|
# rather than block: a hook that fails closed on a missing optional tool just
|
|
|
|
|
# teaches everyone to pass --no-verify.
|
|
|
|
|
if ! command -v emacs >/dev/null 2>&1; then
|
|
|
|
|
echo "pre-commit: emacs not found; skipping reindent (see AGENTS.md)" >&2
|
|
|
|
|
exit 0
|
|
|
|
|
fi
|
|
|
|
|
|
2026-08-01 20:57:37 -04:00
|
|
|
# Keep this list in step with SCOPE in scripts/reindent.sh. They are two copies
|
|
|
|
|
# of one decision, and they have already disagreed once: `tools` and `examples`
|
|
|
|
|
# arrived with the documentation harness and were reindentable by hand while
|
|
|
|
|
# neither this hook nor `--check` could see them.
|
Codify the canonical C style and add reindent tooling
AGENTS.md described style in one paragraph that said little more than
"follow the surrounding code", which was not actionable once files had
drifted apart. Replace it with an explicit specification.
The canonical style is Emacs cc-mode "stroustrup" with tabs enabled: a
4-column offset with 8-column tabs, so depth 1 is four spaces, depth 2 is
one tab, depth 3 is a tab plus four spaces. Most of src/ already followed
this; what looked like randomly mixed tabs and spaces was simply cc-mode
output. Document the byte ladder explicitly, since editors that expand
tabs or assume a 4-column tab silently corrupt it.
Rules beyond indentation are derived from counted majorities in the
existing code rather than invented: errctx over e (92 to 45), padded
control parens (140 to 7), pointer binding to the identifier (486 to 5),
and always-brace (only four unbraced bodies exist). Brace and else
placement are called out as house conventions that cc-mode does not
enforce, so "run the indenter" and "follow the guide" cannot conflict.
Also record the naming, error-handling, and API-surface rules that a
consistency sweep showed were being broken silently -- in particular that
a *_RETURN macro inside an ATTEMPT block skips CLEANUP.
Add the tooling to apply it:
.dir-locals.el applies the style to every c-mode buffer
scripts/reindent.el batch reindent via Emacs
scripts/reindent.sh reindent or --check the tree
scripts/hooks/pre-commit reindents staged sources
reindent.el deliberately avoids Emacs' tabify: its tabify-regexp is
" [ \t]+", which is not anchored to the start of 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, and lines starting inside a string literal are skipped.
The hook checks staged content rather than the working tree, so what is
committed is what was verified. It re-stages a fixed file only when the
index and working tree agree, otherwise a partial `git add -p` would
sweep unstaged work into the commit.
Enable with: git config core.hooksPath scripts/hooks
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 18:17:26 -04:00
|
|
|
staged=$(git diff --cached --name-only --diff-filter=ACMR \
|
2026-08-01 20:57:37 -04:00
|
|
|
| grep -E '^(src|include|tests|util|tools|examples)/.*\.[ch]$' \
|
Codify the canonical C style and add reindent tooling
AGENTS.md described style in one paragraph that said little more than
"follow the surrounding code", which was not actionable once files had
drifted apart. Replace it with an explicit specification.
The canonical style is Emacs cc-mode "stroustrup" with tabs enabled: a
4-column offset with 8-column tabs, so depth 1 is four spaces, depth 2 is
one tab, depth 3 is a tab plus four spaces. Most of src/ already followed
this; what looked like randomly mixed tabs and spaces was simply cc-mode
output. Document the byte ladder explicitly, since editors that expand
tabs or assume a 4-column tab silently corrupt it.
Rules beyond indentation are derived from counted majorities in the
existing code rather than invented: errctx over e (92 to 45), padded
control parens (140 to 7), pointer binding to the identifier (486 to 5),
and always-brace (only four unbraced bodies exist). Brace and else
placement are called out as house conventions that cc-mode does not
enforce, so "run the indenter" and "follow the guide" cannot conflict.
Also record the naming, error-handling, and API-surface rules that a
consistency sweep showed were being broken silently -- in particular that
a *_RETURN macro inside an ATTEMPT block skips CLEANUP.
Add the tooling to apply it:
.dir-locals.el applies the style to every c-mode buffer
scripts/reindent.el batch reindent via Emacs
scripts/reindent.sh reindent or --check the tree
scripts/hooks/pre-commit reindents staged sources
reindent.el deliberately avoids Emacs' tabify: its tabify-regexp is
" [ \t]+", which is not anchored to the start of 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, and lines starting inside a string literal are skipped.
The hook checks staged content rather than the working tree, so what is
committed is what was verified. It re-stages a fixed file only when the
index and working tree agree, otherwise a partial `git add -p` would
sweep unstaged work into the commit.
Enable with: git config core.hooksPath scripts/hooks
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 18:17:26 -04:00
|
|
|
| grep -v -x -F 'include/akgl/SDL_GameControllerDB.h' || true)
|
|
|
|
|
|
|
|
|
|
if [ -z "$staged" ]; then
|
|
|
|
|
exit 0
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
tmp=$(mktemp -d)
|
|
|
|
|
trap 'rm -rf "$tmp"' EXIT INT TERM
|
|
|
|
|
|
|
|
|
|
# Reindent a copy of each file's staged content and see whether it moves.
|
|
|
|
|
needs=''
|
|
|
|
|
for f in $staged; do
|
|
|
|
|
copy="$tmp/$(printf '%s' "$f" | tr '/' '_')"
|
|
|
|
|
git show ":$f" >"$copy"
|
|
|
|
|
cp "$copy" "$copy.orig"
|
|
|
|
|
if ! "$reindent" "$copy" >/dev/null 2>&1; then
|
|
|
|
|
echo "pre-commit: reindent failed on $f; commit aborted" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
if ! cmp -s "$copy" "$copy.orig"; then
|
|
|
|
|
needs="$needs $f"
|
|
|
|
|
fi
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
if [ -z "$needs" ]; then
|
|
|
|
|
exit 0
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Refuse to touch anything if even one file is partially staged.
|
|
|
|
|
blocked=''
|
|
|
|
|
for f in $needs; do
|
|
|
|
|
if ! git diff --quiet -- "$f"; then
|
|
|
|
|
blocked="$blocked $f"
|
|
|
|
|
fi
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
if [ -n "$blocked" ]; then
|
|
|
|
|
echo "pre-commit: these files need reindenting but have unstaged changes," >&2
|
|
|
|
|
echo "so re-staging them would pull unstaged work into the commit:" >&2
|
|
|
|
|
for f in $blocked; do echo " $f" >&2; done
|
|
|
|
|
echo >&2
|
|
|
|
|
echo "Reindent and stage them yourself, then commit again:" >&2
|
|
|
|
|
echo " scripts/reindent.sh$(for f in $blocked; do printf ' %s' "$f"; done)" >&2
|
|
|
|
|
echo " git add$(for f in $blocked; do printf ' %s' "$f"; done)" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Safe: for every file needing work, the index and working tree agree.
|
|
|
|
|
# shellcheck disable=SC2086
|
|
|
|
|
"$reindent" $needs
|
|
|
|
|
# shellcheck disable=SC2086
|
|
|
|
|
git add $needs
|
|
|
|
|
|
|
|
|
|
echo "pre-commit: reindented and re-staged:" >&2
|
|
|
|
|
for f in $needs; do echo " $f" >&2; done
|
|
|
|
|
|
|
|
|
|
exit 0
|