-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathcommit-msg
More file actions
executable file
·61 lines (54 loc) · 2.89 KB
/
Copy pathcommit-msg
File metadata and controls
executable file
·61 lines (54 loc) · 2.89 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#!/usr/bin/env bash
# commit-msg hook — the message half of the commit gate (rules.md §3), enforced
# rather than remembered. A failing check exits non-zero and BLOCKS the commit.
#
# Composes the two message gates:
# 1. check_commit_msg.py — Conventional Commits FORM (header length, type,
# blank-line separation, body wrapping).
# 2. check_selfcontained.sh — SEMANTIC self-containment: no internal campaign
# refs (node IDs, layer tags, AC/C ids) that a
# stranger reading `git log` could not resolve.
#
# git passes the path to the prepared message file as $1.
set -u
msg_file="${1:-}"
if [ -z "$msg_file" ] || [ ! -f "$msg_file" ]; then
echo "commit-msg: no message file provided" >&2
exit 2
fi
# Two distinct locations, deliberately kept apart (the packaging invariant):
# $plugin = where predicate's MACHINERY lives. Resolved from THIS hook's own
# real path. The installed hook is a SYMLINK in the consuming repo's
# .git/hooks/ pointing back to <plugin>/hooks/commit-msg, so resolve
# symlinks (realpath) to land in the real plugin tree. All sibling
# machinery (the form checker, the self-containment gate) is located
# relative to $plugin — never relative to the repo being gated.
# $root = the repo being GATED. Used here only to locate the GATED repo's
# config; the message under inspection arrives as $1 from git. In the
# self-host case ($plugin == $root) both coincide.
plugin="$(cd "$(dirname "$(realpath "${BASH_SOURCE[0]}")")/.." && pwd)"
# Resolve the repo root so the hook works from the main checkout and any linked
# worktree (git invokes the hook with cwd = the working tree root, but be explicit).
root="$(git rev-parse --show-toplevel)"
# Load project config if present; a downstream repo can override COMMIT_HYGIENE_CHECK
# (path to the Conventional Commits form checker). Config belongs to the GATED
# repo ($root), not the plugin. Absent config → predicate default.
# shellcheck source=/dev/null
[ -f "$root/.ledger/config.sh" ] && source "$root/.ledger/config.sh"
# Path to the Conventional Commits form checker — predicate MACHINERY, so it
# lives under $plugin. Downstream repos can still redirect here via config.
: "${COMMIT_HYGIENE_CHECK:=$plugin/skills/commit-hygiene/scripts/check_commit_msg.py}"
rc=0
python3 "$COMMIT_HYGIENE_CHECK" --file "$msg_file"
hygiene_rc=$?
if [ "$hygiene_rc" -ne 0 ]; then rc=1; fi
# The self-containment gate takes the message TEXT, not a file path.
msg="$(cat "$msg_file")"
bash "$plugin/gates/check_selfcontained.sh" "$msg"
selfc_rc=$?
if [ "$selfc_rc" -ne 0 ]; then rc=1; fi
if [ "$rc" -ne 0 ]; then
echo "commit-msg: message gate failed — commit blocked (fix the message above)." >&2
echo " (Hooks enforce the commit gate; bypassing with --no-verify defeats it.)" >&2
fi
exit "$rc"