Uh oh!
There was an error while loading. Please reload this page.
Conversation
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Both checkers selected their own inputs through git ls-files, so a caller wanting the same patterns applied to different content had to copy them. A pre-commit hook wants exactly that: it judges staged content, which is not what the working tree holds during a partial git add -p. Named paths now win over the tracked set. With no arguments both behave as before, which is how CI calls them. Each also separates a file that is wrong from a file it could not read, the convention check-commentflow.sh already documents: 1 for the first, 2 for the second. A path that does not exist otherwise reaches the diff and comes back as a file differing in its entirety, and a machine without clang-format-22 comes back the same way, so a caller acting on either would reject correct code over its own environment.
CONTRIBUTING.md states seven commit-message rules that no gate enforced. Nothing in the tree read a commit message, so they held by review or not at all. The hooks check what can be decided mechanically and leave the rest to a reviewer. Imperative mood and what-and-why are the two that cannot be settled by a script, so they look only for the forms that are wrong beyond argument: a past-tense or gerund opening verb, and a body that announces it is about to describe the mechanism. Both were calibrated against 400 commits of this history and reject none of them. The expensive failure here is a false rejection, not a missed one, and the exemptions are where that gets decided. A merge subject routinely runs past 50 characters and rejecting one aborts the merge halfway, so the six forms git writes are named in full rather than matched as "Merge ", which is also how an ordinary imperative subject begins. A verbose commit appends its diff below a scissors line that git strips only after the hook runs, so the hook cuts there first rather than linting the diff it was handed, and the message template splices in above that line rather than below it, where git would discard it. The pre-commit hook runs the CI checkers against the extracted index rather than carrying a second copy of their patterns. A checker it cannot reach, a formatter that is missing or unpinned, and a reflow tool that could not parse a file all warn rather than block: none of them is a defect in the change, and blocking on one teaches contributors to pass --no-verify, which costs every check that does work. What none of them may do is pass in silence. pre-push fails closed on a revision walk it cannot complete. A force-push advertises a remote tip the local repository may never have fetched, and a failed walk would otherwise land in the same variable an empty range does and read as "no commits to check". test-git-hooks.sh covers each rule and the shapes that break a hook rather than a message: an empty array under the bash 3.2 that a stock macOS supplies, a push to a remote with no refs yet, an unpinned formatter on an isolated PATH, and the hooks reached through the symlinks that install them. Every check asserts a rejection somewhere, because a clean commit and push succeed whether or not a hook ran.
Left to a make target, hooks reach whoever read the README carefully, which is not the population that needs them. The first make of a fresh clone now links them, once per tree, and says so in one line. A stamp target rather than work done while the makefile is read, so a dry run and a query stay inert. Both exist to answer a question without touching the checkout, and writing symlinks is not answering. The stamp records only a run the installer reported success for, so a hooks directory that was briefly unavailable is retried rather than remembered as done, and neither failure stops the build. Hung off compilation and the help screen, which is to say anything that builds and the thing a bare make prints, rather than off MAKECMDGOALS. Naming the goals would give every one of them a rule, including the misspelled ones: a typo that stops today with "No rule to make target" would instead find an empty rule, report nothing to be done, and exit 0. Every binary here reaches the two object rules, host unit tests included, so one prerequisite there covers every build entry point without listing any of them. A hook already in place is never replaced, since it belongs to whoever put it there. uninstall-hooks enumerates the links rather than the scripts, so a hook whose script a branch removed is still removable, and a tarball export with no git dir does nothing at all. The test for a linked worktree carrying the scripts before the primary one does lands here rather than with the hooks, because it drives the install through this makefile and would fail on the commit before it.
The seven rules were documented as enforced by continuous integration while nothing in the workflows read a commit message. The gate runs the same script the local hook does, so a contributor with hooks installed and one without are judged by one implementation rather than two that drift. The checkout is shallow, and a shallow range comes back wrong rather than failing: rev-list exits 0 having listed whatever it could reach. Both ends can be truncated and the two look nothing alike. Fetched at depth 1 the base has no known ancestry, so the range excludes the base commit and nothing else, and every commit below the fork point stays in it; that is the ordinary case the moment the base branch moves on, and it comes back longer, not shorter. So both ends are fetched to the same depth, merge-base has to succeed before the range is trusted, and the reachable count is still compared against the number the pull request reports, which is what catches truncation at the head end. The hook self-test runs here too, because the gate and the hooks are the same rules and a regression in one is a regression in both. shellcheck widens to scripts/, where the hooks now live. A hook that aborts on its own shell is worse than no hook: it teaches contributors to pass --no-verify.
The contributing guide said continuous integration enforced the commit-message conventions, which was true of no check in the tree until the gate landed. Both documents now describe what runs, when it runs, and which two rules a script can only approximate.
Uh oh!
There was an error while loading. Please reload this page.
This was referenced Aug 20, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary by cubic
Install Git hooks and a CI gate to enforce commit‑message rules and staged checks. Previously rules were documented but unenforced and hooks were opt‑in; now hooks auto‑install on first build, validate locally, and CI checks every PR commit with a shallow‑safe range.
.ci/check-format.shreturns 1 for style diffs and 2 when the pinned formatter is unavailable;.ci/check-security.shalso accepts named paths and fails on unreadable inputs. The pre-commit hook runs these against the extracted index.scripts/:git-commit-msg.sh(rules),git-pre-commit.sh(format, comment reflow, banned APIs, whitespace, shellcheck),git-pre-push.sh(validates new non‑merge commits),check-commit-log.sh(shared),git-prepare-commit-msg.sh(injects rules; respects scissors and core.commentChar),install-git-hooks.sh(link/unlink without overwriting), andtest-git-hooks.sh(self‑tests).scripts/check-commit-log.sh; runs hook self‑tests; extends shellcheck toscripts/.Rollout and developer impact
makevia symlinks; existing hooks are never replaced. Remove withmake uninstall-hooks; reinstall withmake install-hooks.clang-format, missingcommentflow, missingshellcheck); CI enforces the same checks.Written for commit 40a1bd1. Summary will update on new commits.