111 lines
4.2 KiB
Bash
Executable File
111 lines
4.2 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# Every commit says what kind of change it is, before it says anything else.
|
|
#
|
|
# ## Why
|
|
#
|
|
# Ninety-nine of this repository's commits already carry a conventional type —
|
|
# `feat:`, `fix:`, `docs:`, `chore:` — and ninety-six do not, including a run of
|
|
# recent ones written as bare sentences. That split is the problem: `git log
|
|
# --grep '^fix'` answers "what have we fixed" for half the history and quietly
|
|
# omits the other half, which is worse than having no convention at all, because
|
|
# the answer looks complete.
|
|
#
|
|
# So the type is required, and the vocabulary is closed. A closed list is the
|
|
# point — `feat`, `feature` and `feat!` as three spellings of one idea is how a
|
|
# convention stops being searchable.
|
|
#
|
|
# ## The vocabulary
|
|
#
|
|
# feat a new capability somebody can use
|
|
# fix a defect. The thing behaved wrongly and now does not
|
|
# ui appearance, layout, copy, or interaction, with no change in what
|
|
# the software knows or decides
|
|
# docs documentation only
|
|
# test tests only, with no change to what they test
|
|
# refactor same behaviour, different shape. If behaviour changed it is not this
|
|
# security hardening, boundaries, secret handling. Kept separate from `fix`
|
|
# on purpose: "what have we hardened" is a question worth being able
|
|
# to ask on its own, and it is the one an auditor asks first
|
|
# perf faster or lighter, same answers
|
|
# chore tooling, dependencies, releases. The bucket for work that is not
|
|
# about the product
|
|
#
|
|
# `harden`, `style` and `content` each appear once or twice in the history and
|
|
# are deliberately not here — they are `security`, `ui` and `docs` under other
|
|
# names, and a synonym is a hole in a closed list.
|
|
#
|
|
# ## Scope is optional, and lowercase
|
|
#
|
|
# fix(admin): ... the fifteen existing `admin` scopes, and `release`,
|
|
# `integrations`, `db` and the rest, all keep working.
|
|
#
|
|
# ## What it deliberately does not enforce
|
|
#
|
|
# Subject length. Thirty-five existing subjects run past 72 characters, several
|
|
# of them deliberately, and rejecting a commit for a well-written 80-character
|
|
# sentence would teach people to use `--no-verify` — which switches off the
|
|
# checks that actually matter. The type is the part a tool reads; the length is
|
|
# a matter of taste and stays that way.
|
|
#
|
|
# ## Escape hatch
|
|
#
|
|
# SKIP_GUARDS=1 git commit ... skips this too, and says so
|
|
#
|
|
# The same variable the pre-commit hook uses, because two switches for "I know
|
|
# what I am doing" is one more than anybody will remember.
|
|
|
|
set -uo pipefail
|
|
|
|
say() { printf '\033[1mcommit-msg:\033[0m %s\n' "$*" >&2; }
|
|
|
|
message_file="$1"
|
|
subject=$(head -1 "$message_file")
|
|
|
|
if [ -n "${SKIP_GUARDS:-}" ]; then
|
|
say "SKIP_GUARDS set — the commit type was NOT checked."
|
|
exit 0
|
|
fi
|
|
|
|
# Git writes these itself, or writes them on a human's behalf during a rebase.
|
|
# Rejecting them would break `git merge`, `git revert` and autosquash for a
|
|
# convention none of them ever agreed to.
|
|
case "$subject" in
|
|
"Merge "*|"Revert "*|"fixup!"*|"squash!"*|"amend!"*)
|
|
exit 0
|
|
;;
|
|
esac
|
|
|
|
# A comment-only file is an aborted commit; git handles that itself.
|
|
if [ -z "${subject// /}" ]; then
|
|
exit 0
|
|
fi
|
|
|
|
TYPES="feat|fix|ui|docs|test|refactor|security|perf|chore"
|
|
|
|
if printf '%s' "$subject" | grep -qE "^(${TYPES})(\([a-z0-9._-]+\))?!?: .+"; then
|
|
exit 0
|
|
fi
|
|
|
|
say "the subject line needs a type."
|
|
say ""
|
|
say " got: ${subject}"
|
|
say ""
|
|
say " expected: <type>: <subject> e.g. fix: stop counting milestones as records"
|
|
say " <type>(<scope>): <subject> ui(admin): mark cloud models on the picker"
|
|
say ""
|
|
say " types: feat a new capability"
|
|
say " fix a defect, now not"
|
|
say " ui appearance, layout, copy — no change to what it decides"
|
|
say " docs documentation only"
|
|
say " test tests only"
|
|
say " refactor same behaviour, different shape"
|
|
say " security hardening, boundaries, secrets"
|
|
say " perf faster or lighter, same answers"
|
|
say " chore tooling, dependencies, releases"
|
|
say ""
|
|
say " Your message is kept. Run 'git commit' again to edit it, or"
|
|
say " SKIP_GUARDS=1 git commit ... to bypass this loudly."
|
|
|
|
exit 1
|