1#!/usr/bin/env bash
2
3REF="HEAD"
4
5# test a url
6function test_url()
7{
8    url="$1"
9    if ! curl --output /dev/null --max-time 60 \
10		--silent --head --fail "$url" ; then
11        echo "\"$url\" is unreachable"
12        return 1
13    fi
14
15    return 0
16}
17
18# test commit body for length
19# lines containing urls are exempt for the length limit.
20function test_commit_bodylength()
21{
22    length="72"
23    body=$(git log -n 1 --pretty=%b "$REF" | grep -Ev "http(s)*://" | grep -E -m 1 ".{$((length + 1))}")
24    if [ -n "$body" ]; then
25        echo "error: commit message body contains line over ${length} characters"
26        return 1
27    fi
28
29    return 0
30}
31
32# check for a tagged line
33function check_tagged_line()
34{
35    regex='^\s*'"$1"':\s[[:print:]]+\s<[[:graph:]]+>$'
36    foundline=$(git log -n 1 "$REF" | grep -E -m 1 "$regex")
37    if [ -z "$foundline" ]; then
38        echo "error: missing \"$1\""
39        return 1
40    fi
41
42    return 0
43}
44
45# check for a tagged line and check that the link is valid
46function check_tagged_line_with_url()
47{
48    regex='^\s*'"$1"':\s\K([[:graph:]]+)$'
49    foundline=$(git log -n 1 "$REF" | grep -Po "$regex")
50    if [ -z "$foundline" ]; then
51        echo "error: missing \"$1\""
52        return 1
53    fi
54
55    OLDIFS=$IFS
56    IFS=$'\n'
57    for url in $(echo -e "$foundline"); do
58        if ! test_url "$url"; then
59            return 1
60        fi
61    done
62    IFS=$OLDIFS
63
64    return 0
65}
66
67# check commit message for a normal commit
68function new_change_commit()
69{
70    error=0
71
72    # subject is not longer than 72 characters
73    long_subject=$(git log -n 1 --pretty=%s "$REF" | grep -E -m 1 '.{73}')
74    if [ -n "$long_subject" ]; then
75        echo "error: commit subject over 72 characters"
76        error=1
77    fi
78
79    # need a signed off by
80    if ! check_tagged_line "Signed-off-by" ; then
81        error=1
82    fi
83
84    # ensure that no lines in the body of the commit are over 72 characters
85    if ! test_commit_bodylength ; then
86        error=1
87    fi
88
89    return $error
90}
91
92function is_openzfs_port()
93{
94    # subject starts with OpenZFS means it's an openzfs port
95    subject=$(git log -n 1 --pretty=%s "$REF" | grep -E -m 1 '^OpenZFS')
96    if [ -n "$subject" ]; then
97        return 0
98    fi
99
100    return 1
101}
102
103function openzfs_port_commit()
104{
105    error=0
106
107    # subject starts with OpenZFS dddd
108    subject=$(git log -n 1 --pretty=%s "$REF" | grep -E -m 1 '^OpenZFS [[:digit:]]+(, [[:digit:]]+)* - ')
109    if [ -z "$subject" ]; then
110        echo "error: OpenZFS patch ports must have a subject line that starts with \"OpenZFS dddd - \""
111        error=1
112    fi
113
114    # need an authored by line
115    if ! check_tagged_line "Authored by" ; then
116        error=1
117    fi
118
119    # need a reviewed by line
120    if ! check_tagged_line "Reviewed by" ; then
121        error=1
122    fi
123
124    # need ported by line
125    if ! check_tagged_line "Ported-by" ; then
126        error=1
127    fi
128
129    # need a url to openzfs commit and it should be valid
130    if ! check_tagged_line_with_url "OpenZFS-commit" ; then
131        error=1
132    fi
133
134    # need a url to illumos issue and it should be valid
135    if ! check_tagged_line_with_url "OpenZFS-issue" ; then
136        error=1
137    fi
138
139    return $error
140}
141
142function is_coverity_fix()
143{
144    # subject starts with Fix coverity defects means it's a coverity fix
145    subject=$(git log -n 1 --pretty=%s "$REF" | grep -E -m 1 '^Fix coverity defects')
146    if [ -n "$subject" ]; then
147        return 0
148    fi
149
150    return 1
151}
152
153function coverity_fix_commit()
154{
155    error=0
156
157    # subject starts with Fix coverity defects: CID dddd, dddd...
158    subject=$(git log -n 1 --pretty=%s "$REF" |
159        grep -E -m 1 'Fix coverity defects: CID [[:digit:]]+(, [[:digit:]]+)*')
160    if [ -z "$subject" ]; then
161        echo "error: Coverity defect fixes must have a subject line that starts with \"Fix coverity defects: CID dddd\""
162        error=1
163    fi
164
165    # need a signed off by
166    if ! check_tagged_line "Signed-off-by" ; then
167        error=1
168    fi
169
170    # test each summary line for the proper format
171    OLDIFS=$IFS
172    IFS=$'\n'
173    for line in $(git log -n 1 --pretty=%b "$REF" | grep -E '^CID'); do
174        echo "$line" | grep -E '^CID [[:digit:]]+: ([[:graph:]]+|[[:space:]])+ \(([[:upper:]]|\_)+\)' > /dev/null
175        # shellcheck disable=SC2181
176        if [[ $? -ne 0 ]]; then
177            echo "error: commit message has an improperly formatted CID defect line"
178            error=1
179        fi
180    done
181    IFS=$OLDIFS
182
183    # ensure that no lines in the body of the commit are over 72 characters
184    if ! test_commit_bodylength; then
185        error=1
186    fi
187
188    return $error
189}
190
191if [ -n "$1" ]; then
192    REF="$1"
193fi
194
195# if openzfs port, test against that
196if is_openzfs_port; then
197    if ! openzfs_port_commit ; then
198        exit 1
199    else
200        exit 0
201    fi
202fi
203
204# if coverity fix, test against that
205if is_coverity_fix; then
206    if ! coverity_fix_commit; then
207        exit 1
208    else
209        exit 0
210    fi
211fi
212
213# have a normal commit
214if ! new_change_commit ; then
215    exit 1
216fi
217
218exit 0
219