]> git.giacomolli.uk - onechat/blob - .githooks/commit-msg
Import WhatsApp history into a child
[onechat] / .githooks / commit-msg
1 #!/bin/sh
2 # Rejects a commit message that breaks the rules in AGENTS.md: a subject of 50
3 # characters or fewer without a final period, one blank line, a body, body lines
4 # of 72 characters or fewer, and real line breaks instead of "\n". Prose
5 # paragraphs fill each line before wrapping. Formatted blocks keep their line
6 # breaks. A body line without spaces, such as a URL, may be longer.
7 set -eu
8
9 file=$1
10 subject=$(sed -n 1p "$file")
11 case $subject in
12   "Merge "* | "Revert "* | "fixup! "* | "squash! "*) exit 0 ;;
13 esac
14
15 fail() {
16   printf 'commit-msg: %s\n' "$1" "write the message to a file and commit with git commit -F." >&2
17   exit 1
18 }
19
20 if grep -q '\\n' "$file"; then
21   fail 'the message has a literal "\n"; use real line breaks'
22 fi
23
24 [ ${#subject} -le 50 ] || fail "the subject has ${#subject} characters; the limit is 50"
25
26 case $subject in
27   *.) fail "the subject ends with a period" ;;
28 esac
29
30 [ -z "$(sed -n 2p "$file")" ] || fail "the second line must be blank"
31
32 sed -n '3,$p' "$file" | grep -q '[^[:space:]]' || fail "the message has no body"
33
34 long=$(sed -n '3,$p' "$file" | awk 'length($0) > 72 && $0 ~ / / { print NR + 2; exit }')
35 [ -z "$long" ] || fail "line $long is longer than 72 characters"
36
37 short=$(awk '
38   function check_paragraph(    i, word) {
39     if (!formatted && !bad_line) {
40       for (i = 1; i < count; i++) {
41         word = lines[i + 1]
42         sub(/[[:space:]].*$/, "", word)
43         if (length(lines[i]) + 1 + length(word) <= 72) {
44           bad_line = numbers[i]
45           break
46         }
47       }
48     }
49
50     count = 0
51     formatted = 0
52   }
53
54   NR <= 2 { next }
55
56   fence != "" {
57     if ($0 ~ /^(`{3,}|~{3,})[[:space:]]*$/ && substr($0, 1, 1) == fence) {
58       match($0, /^(`+|~+)/)
59       if (RLENGTH >= fence_length) fence = ""
60     }
61     next
62   }
63
64   /^(`{3,}|~{3,})/ {
65     check_paragraph()
66     match($0, /^(`+|~+)/)
67     fence = substr($0, 1, 1)
68     fence_length = RLENGTH
69     next
70   }
71
72   /^[[:space:]]*$/ {
73     check_paragraph()
74     next
75   }
76
77   {
78     if ($0 ~ /^[[:space:]>#|]/ ||
79         $0 ~ /^([-*+][[:space:]]|[0-9]+[.)][[:space:]])/ ||
80         $0 ~ /^[-=*_]{3,}$/ ||
81         $0 ~ /^[[:alnum:]][[:alnum:]-]*:[[:space:]]/) {
82       formatted = 1
83     }
84
85     lines[++count] = $0
86     numbers[count] = NR
87   }
88
89   END {
90     check_paragraph()
91     if (bad_line) print bad_line
92   }
93 ' "$file")
94 [ -z "$short" ] || fail "unnecessary line break after line $short; reflow the paragraph to 72 characters"