diff options
| author | user <user@clank> | 2026-08-17 14:21:40 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-08-17 14:21:40 +0200 |
| commit | 1f52c39ab0875162be013e6d78720a780df1d889 (patch) | |
| tree | 29e9d72aa85a39d5aec52fafcecd80deb3bdf2a7 /Makefile | |
| parent | sl0ppty: a dead pane says what it ran (diff) | |
| download | slosh-1f52c39ab0875162be013e6d78720a780df1d889.tar.gz slosh-1f52c39ab0875162be013e6d78720a780df1d889.tar.xz slosh-1f52c39ab0875162be013e6d78720a780df1d889.zip | |
sl0ppty: clang-format, and a hook that runs it on what you staged
`make hooks` points `core.hooksPath` at `.githooks`, and from then on committing C
formats it first, so what lands in git is already formatted. `make fmt` does the
whole tree, `make fmt-check` only reports. The reformat itself is the next commit.
**The config was chosen by measuring, not by taste.** LLVM is the base because it
is what this code was already written in by hand -- two spaces, 80 columns,
`char *p`, braces attached -- but LLVM alone rewrote 38% of the lines. Three
disagreements account for most of that, and each is a real house idiom rather than
an accident:
- `if (!p) return false;` on one line, which is the guard idiom throughout. LLVM
splits every one of them in two, two thirds of the churn on its own.
`WithoutElse`, so an `if` that has an `else` still opens up: a one-line `if`
hiding an `else` is the arrangement that hides a bug.
- Comments are not reflowed. They are prose, wrapped by hand, and the breaks
sometimes carry meaning -- a paragraph, a list, a diagram of a pane. A comment
that runs long is a thing for a person to fix, not for a formatter to even out.
- Include order is left alone. It is grouped by hand here and is not alphabetical
within a group; sorting it would be churn at best and, at worst, a build that
depends on an order nobody wrote down.
Those three, plus one-line `case` labels and loops, bring the reformat from 38% to
17% -- and what is left is alignment and the hand-packed tables, which is the part
a formatter is *for*.
The vendored terminal core is excluded everywhere: `.githooks/pre-commit`, `make
fmt` and `make fmt-check` all filter `^vendor/`. It is pinned by commit (D12) and
reformatting it would turn every future re-vendor into a merge.
**The hook refuses a partly staged file rather than widening the commit.**
`clang-format -i` rewrites the working tree, so re-staging afterwards would commit
work somebody deliberately left out with `git add -p`. It says which files and what
to do instead. It also says when it formatted something, because a hook that edits
your commit silently is a hook people learn to distrust, and it fails with the
package name when `clang-format` is missing rather than letting unformatted C
through the one check meant to stop it. `--no-verify` still escapes.
Verified in a scratch clone, all four paths: a badly formatted file is committed
formatted; a partly staged one is refused with `HEAD` unchanged and the index
untouched; a commit with no C in it draws no comment; and `--no-verify` puts
unformatted code in, which is what asking for it means.
Diffstat (limited to 'Makefile')
| -rw-r--r-- | Makefile | 24 |
1 files changed, 23 insertions, 1 deletions
@@ -52,7 +52,8 @@ else say = @: endif -.PHONY: all clean vendor run test retest test-live test-all smoke help coverage docs +.PHONY: all clean vendor run test retest test-live test-all smoke help coverage \ + docs fmt fmt-check hooks .DEFAULT_GOAL := help help: ## show this @@ -243,6 +244,27 @@ coverage: $(COV_DIR)/sl0ppty $(COV_UNITS) ## how much of the code the tests run done @contrib/coverage +# The C in this tree, minus the vendored library: that one is pinned by commit +# (D12) and reformatting it would turn every re-vendor into a merge. +CSRC := $(shell git ls-files '*.c' '*.h' 2>/dev/null | grep -v '^vendor/') + +fmt: ## format the C in place (.clang-format) + $(Q)clang-format -i $(CSRC) + $(say) " [FMT] $(words $(CSRC)) files" + +fmt-check: ## ...or just say which files are not formatted + @bad=$$(for f in $(CSRC); do \ + clang-format "$$f" | cmp -s - "$$f" || echo "$$f"; \ + done); \ + if [ -n "$$bad" ]; then \ + echo "not formatted:"; echo "$$bad" | sed 's/^/ /'; \ + echo "run: make fmt"; exit 1; \ + else echo "all $(words $(CSRC)) files formatted"; fi + +hooks: ## install the pre-commit hook (formats staged C) + $(Q)git config core.hooksPath .githooks + @echo "core.hooksPath -> .githooks (git commit --no-verify to skip)" + clean: ## remove our build output (not the vendor lib) rm -rf build |
