diff --git a/committed.toml b/committed.toml new file mode 100644 index 0000000..fc9651a --- /dev/null +++ b/committed.toml @@ -0,0 +1,75 @@ +# Rules for `committed` (https://github.com/crate-ci/committed), run as a +# commit-msg hook from prek.toml. +# +# Nearly every line below overrides a default, which looks like fussing until +# you run the defaults against this repo: with a bare `style = "conventional"` +# and nothing else, all 35 conforming commits since 5f7d819 fail. With the +# settings below, all 35 pass and the pre-5f7d819 prose subjects still fail. +# That is the test this file was written against — a linter that rejects the +# history it is meant to describe is a linter that gets uninstalled. + +style = "conventional" + +# committed capitalizes the subject by default, which for a conventional commit +# means requiring `feat(auth): Leave the active account alone`. This project +# writes the description in lower case. This one default is what failed all 35. +subject_capitalized = false + +# The defaults are 50 columns of subject and 72 of any wrapped line. The +# longest subject in the conforming range is 77 — +# fix(http): bound every request, so an unreachable host cannot stall a command +# — and bodies here are wrapped at 80. So 80 is the limit that already exists; +# 50 is a limit that the whole history violates, and one of those is a rule and +# the other is a lie. Note that the conventional `type(scope)!: ` prefix eats +# 10-20 columns before the description starts, which is most of why 50 does not +# fit. +subject_length = 80 +line_length = 80 + +# The default list is fix, feat, chore, docs, style, refactor, perf, test. Two +# additions are load-bearing rather than speculative: `build` is already in the +# history (build: keep the whitespace fixers off captured fixtures), and `chore` +# is what cargo-release writes for the version commit (see release.toml), so +# without it the bump command cannot commit through its own hook. `ci` and +# `revert` are here to complete the conventional set, and are unused so far. +allowed_types = [ + "feat", + "fix", + "docs", + "style", + "refactor", + "perf", + "test", + "build", + "ci", + "chore", + "revert", +] + +# Left at their defaults, deliberately: +# +# allowed_scopes — unset, so any scope passes. Twelve are in use (auth, authlog, +# account, bobbin, http, identity, models, pr, repo, resolve, review, +# sessions) and they track the module layout, which still moves. Pinning the +# list would mean a rename could only land by editing this file first. +# +# imperative_subject — true, and worth knowing it is a wordlist heuristic, not +# grammar, and an uneven one. It rejects a description opening with an +# article (`fix: a fix for it`, `fix: the thing`) and some inflected verbs +# (`adds`, `added`); it passes `corrects it`, `correcting it` and non-verb +# openings like `how it works`. Kept on because the forms it does catch are +# ones people type, and because every subject in the conforming history +# survives it, but it is a nudge and not a guarantee. If it ever rejects a +# subject that is genuinely imperative, that is the bug, and this is the knob. +# +# subject_not_punctuated — true. Trailing `.` is rejected; inline punctuation is +# fine, including the asterisks in +# `fix(review): read the record the appview page is *about*`. +# +# no_wip, no_fixup — true. Both are aimed at a branch that is about to be +# rebased anyway, and this project rebases. +# +# merge_commit — true, and inert here. In commit-msg mode committed is handed a +# message file with no parent information, so it cannot tell a merge from +# anything else. The rule only bites when committed is run over a commit +# range, which nothing in this repo does. diff --git a/prek.toml b/prek.toml index 21efe44..c526488 100644 --- a/prek.toml +++ b/prek.toml @@ -7,17 +7,37 @@ # the read-only checks below are left to cover both, since a merge marker or a # broken Cargo.toml in there would be worth hearing about. +# `prek install` wires the pre-commit hook and nothing else unless it is told +# otherwise, so without this line the commit-msg hook below would sit in the +# file being installed by nobody and firing never. Naming both types here keeps +# the contributor instruction at one command; CONTRIBUTING.md says the same +# thing, and also gives the explicit `--hook-type` form for anyone whose prek +# predates support for this key. +default_install_hook_types = ["pre-commit", "commit-msg"] + +# `stages` appears on every hook below now that there is more than one stage in +# play. A hook that does not name its stages runs in all of them, so without +# this the file-hygiene and cargo hooks would also fire on commit-msg, where +# the only path they are handed is .git/COMMIT_EDITMSG — eight lines of +# "no files to check" before every commit, and the whitespace fixers quietly +# rewriting the message file. Not harmful, but noise on a path nobody reads. +# The one hook deliberately without a `stages` here is committed, further down, +# which declares `commit-msg` in its own manifest. It has to be spelled out per +# hook rather than once as a top-level `default_stages`, because that key is +# applied to hooks before the manifest is consulted and would override +# committed's own `commit-msg` — turning the new hook off while looking like it +# tidied up. [[repos]] repo = "https://github.com/pre-commit/pre-commit-hooks" rev = "v6.0.0" hooks = [ - { id = "check-merge-conflict" }, - { id = "check-added-large-files" }, - { id = "check-yaml" }, - { id = "check-toml" }, - { id = "mixed-line-ending", args = ["--fix=lf"], exclude = '^(vendor|tests/fixtures)/' }, - { id = "end-of-file-fixer", exclude = '^(vendor|tests/fixtures)/' }, - { id = "trailing-whitespace", exclude = '^(vendor|tests/fixtures)/' }, + { id = "check-merge-conflict", stages = ["pre-commit"] }, + { id = "check-added-large-files", stages = ["pre-commit"] }, + { id = "check-yaml", stages = ["pre-commit"] }, + { id = "check-toml", stages = ["pre-commit"] }, + { id = "mixed-line-ending", args = ["--fix=lf"], exclude = '^(vendor|tests/fixtures)/', stages = ["pre-commit"] }, + { id = "end-of-file-fixer", exclude = '^(vendor|tests/fixtures)/', stages = ["pre-commit"] }, + { id = "trailing-whitespace", exclude = '^(vendor|tests/fixtures)/', stages = ["pre-commit"] }, ] # Run the project's own toolchain rather than a pinned copy of it. @@ -32,9 +52,9 @@ hooks = [ [[repos]] repo = "local" hooks = [ - { id = "cargo-fmt", name = "cargo fmt", entry = "cargo fmt --all -- --check", language = "system", types = ["file"], files = '\.rs$', pass_filenames = false }, + { id = "cargo-fmt", name = "cargo fmt", entry = "cargo fmt --all -- --check", language = "system", types = ["file"], files = '\.rs$', pass_filenames = false, stages = ["pre-commit"] }, # clippy type-checks as it lints, so there is no separate cargo check hook. - { id = "cargo-clippy", name = "cargo clippy", entry = "cargo clippy --all-targets --all-features -- -D warnings", language = "system", types = ["file"], files = '(\.rs$|^Cargo\.(toml|lock)$)', pass_filenames = false }, + { id = "cargo-clippy", name = "cargo clippy", entry = "cargo clippy --all-targets --all-features -- -D warnings", language = "system", types = ["file"], files = '(\.rs$|^Cargo\.(toml|lock)$)', pass_filenames = false, stages = ["pre-commit"] }, # The doc build is a check, not just a build: .cargo/config.toml sets # rustdocflags = -D warnings, so a prose page that links to an item that no # longer exists fails here. That is the whole reason the narrative docs are @@ -43,8 +63,33 @@ hooks = [ # warm target dir. Triggered by docs/ as well as src/, since the pages are # include_str!'d into the crate and a change to one is a change to the doc # build's input. - { id = "cargo-doc", name = "cargo doc", entry = "cargo docs", language = "system", types = ["file"], files = '(\.rs$|^docs/.*\.md$|^Cargo\.(toml|lock)$|^\.cargo/config\.toml$)', pass_filenames = false }, + { id = "cargo-doc", name = "cargo doc", entry = "cargo docs", language = "system", types = ["file"], files = '(\.rs$|^docs/.*\.md$|^Cargo\.(toml|lock)$|^\.cargo/config\.toml$)', pass_filenames = false, stages = ["pre-commit"] }, # The two things the doc build cannot see: an untested rust code fence, and # a relative link between pages whose target no longer exists. - { id = "doc-lint", name = "doc lint", entry = "scripts/doc-lint.sh", language = "system", types = ["file"], files = '^(docs/.*\.md|scripts/doc-lint\.sh)$', pass_filenames = false }, + { id = "doc-lint", name = "doc lint", entry = "scripts/doc-lint.sh", language = "system", types = ["file"], files = '^(docs/.*\.md|scripts/doc-lint\.sh)$', pass_filenames = false, stages = ["pre-commit"] }, ] + +# Conventional Commits, checked as the message is written. +# +# This is not a new rule. Every commit from 5f7d819 onwards already conforms — +# 36 of the 82 in the tree, and zero violations in the 35 since — so all this +# does is move a convention that lived in someone's head into a place that can +# fail. It matters more than house style now that the version in Cargo.toml is +# computed from these subjects (see cliff.toml): a subject that does not parse +# is a change that silently does not count towards the next version, and the +# failure is invisible until someone reads a version number and disbelieves it. +# +# The pre-5f7d819 prose subjects are not a problem and need no rewrite. A +# commit-msg hook is handed one path — .git/COMMIT_EDITMSG — and committed is +# invoked here with `--commit-file`, so it reads that file and nothing else; +# history is not an input. Confirmed rather than assumed: `committed +# --commit-file` was run against every subject in the tree, and the old ones +# fail exactly as they would if they were being written today, which is a +# result no hook ever sees. +# +# The rules are in committed.toml, which has to override most of committed's +# defaults to describe the style already in use here. See the notes there. +[[repos]] +repo = "https://github.com/crate-ci/committed" +rev = "v1.1.11" +hooks = [{ id = "committed" }]