78 lines
2.5 KiB
Bash
Executable File
78 lines
2.5 KiB
Bash
Executable File
#!/bin/sh
|
|
# commit-msg hook: enforce the repository's commit format and prose rules.
|
|
# Install per clone (hooks are not cloned): git config core.hooksPath .githooks
|
|
# See core/commits.md and core/git-discipline.md.
|
|
|
|
set -eu
|
|
|
|
msg_file="${1:?usage: commit-msg <message-file>}"
|
|
clean=$(mktemp "${TMPDIR:-/tmp}/rules-swift-commit-message.XXXXXX")
|
|
trap 'rm -f "$clean"' EXIT HUP INT TERM
|
|
|
|
# Ignore Git's comment lines and everything after the scissors marker.
|
|
awk '
|
|
/^# ------------------------ >8 ------------------------$/ { exit }
|
|
!/^#/ { print }
|
|
' "$msg_file" > "$clean"
|
|
|
|
subject=$(sed -n '1p' "$clean")
|
|
|
|
if [ -z "$subject" ]; then
|
|
echo "commit-msg: subject must not be empty." >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [ "${#subject}" -gt 72 ]; then
|
|
echo "commit-msg: subject exceeds 72 characters (${#subject})." >&2
|
|
exit 1
|
|
fi
|
|
|
|
if ! printf '%s\n' "$subject" | grep -qE '^(feat|fix|docs|style|refactor|perf|test|build|ci|chore)(\([a-z0-9._-]+\))?!?: [^ ].*[^.]$'; then
|
|
echo "commit-msg: use <type>(<scope>): imperative description, with an allowed Conventional Commit type and no trailing period." >&2
|
|
exit 1
|
|
fi
|
|
|
|
case "$subject" in
|
|
*'!:'*)
|
|
if ! grep -qE '^BREAKING CHANGE: .+' "$clean"; then
|
|
echo "commit-msg: a breaking subject requires a BREAKING CHANGE footer with a migration explanation." >&2
|
|
exit 1
|
|
fi
|
|
;;
|
|
*)
|
|
if grep -q '^BREAKING CHANGE:' "$clean"; then
|
|
echo "commit-msg: a BREAKING CHANGE footer requires ! in the subject." >&2
|
|
exit 1
|
|
fi
|
|
;;
|
|
esac
|
|
|
|
if grep -qiE '(claude|anthropic|cursor|copilot|codex|openai|chatgpt|gpt-[0-9a-z.-]*|gemini|antigravity|(^|[^[:alnum:]])AI([^[:alnum:]]|$)|LLM|assistant|machine-generated|co-pilot|pair-programmer|robot)' "$clean"; then
|
|
echo "commit-msg: Rule 5.1 forbids tool or automated-author attribution." >&2
|
|
exit 1
|
|
fi
|
|
|
|
if grep -q "$(printf '\342\200\224')" "$clean"; then
|
|
echo "commit-msg: Rule 5.2 forbids em dashes; use commas, colons, or restructure." >&2
|
|
exit 1
|
|
fi
|
|
|
|
if grep -q "$(printf '\360\237\244\226')" "$clean" \
|
|
|| grep -q "$(printf '\342\234\250')" "$clean"; then
|
|
echo "commit-msg: Rule 5.1 forbids tool-signature emoji." >&2
|
|
exit 1
|
|
fi
|
|
|
|
if grep -q "$(printf '\342\200\213')" "$clean" \
|
|
|| grep -q "$(printf '\357\273\277')" "$clean"; then
|
|
echo "commit-msg: Rule 5.1 forbids hidden watermark characters." >&2
|
|
exit 1
|
|
fi
|
|
|
|
if awk 'length($0) > 72 { found = 1 } END { exit found ? 0 : 1 }' "$clean"; then
|
|
echo "commit-msg: body and footer lines must be wrapped at 72 characters." >&2
|
|
exit 1
|
|
fi
|
|
|
|
exit 0
|