Whamcloud - gitweb
LU-13990 ldlm: ldlm_flock_deadlock() ASSERTION(req != lock) failed
[fs/lustre-release.git] / contrib / git-hooks / commit-msg
1 #!/bin/bash
2 #
3 # A hook script to check the commit log message to ensure that it has
4 # a well-formed commit summary and body, a valid Signed-off-by: line,
5 # and a Gerrit Change-Id: line (added automatically if missing).
6 #
7 # Called by git-commit with one argument, the name of the file
8 # that has the commit message.  The hook should exit with non-zero
9 # status after issuing an appropriate message if it wants to stop the
10 # commit.  The hook is allowed to edit the commit message file.
11 #
12 # Should be installed as .git/hooks/commit-msg.
13 #
14
15 init() {
16         set -a
17         readonly ORIGINAL="$1"
18         readonly REVISED="$(mktemp "$ORIGINAL.XXXXXX")"
19         readonly SAVE="$(basename $ORIGINAL).$(date +%Y%m%d.%H%M%S)"
20         readonly SIGNOFF="Signed-off-by:"
21         readonly CHANGEID="Change-Id:"
22         readonly FIXES="Fixes:"
23         readonly TESTPARAMS="Test-Parameters:"
24         readonly INNOCUOUS=$(echo \
25                         Acked-by \
26                         Tested-by \
27                         Reported-by \
28                         Reviewed-by \
29                         CC \
30                 | tr ' ' '|')
31         readonly WIDTH_SUM=62
32         readonly WIDTH_REG=70
33         readonly JIRA_FMT_A="^[A-Z]\{2,9\}-[0-9]\{1,5\} [-a-z0-9]\{2,11\}: "
34         readonly JIRA_FMT_B="^[A-Z]\{2,9\}-[0-9]\{1,5\} "
35
36         # Identify a name followed by an email address.
37         #
38         readonly EMAILPAT=$'[ \t]*[^<> ]* [^<>]* <[^@ \t>]+@[a-zA-Z0-9.-]+\.[a-z]+>'
39
40         HAS_ERROR=false
41         HAS_SUMMARY=false
42         HAS_LAST_BLANK=false
43         HAS_BODY=false
44         HAS_SIGNOFF=false
45         HAS_CHANGEID=false
46         NEEDS_FIRST_LINE=true
47
48         IS_WRAPPING_UP=false
49
50         LINE=""
51         NUM=0
52         set +a
53 }
54
55 # die: commit-msg fatal error: script error or empty input message
56 # All output redirected to stderr.
57 #
58 die() {
59         echo "commit-msg fatal error:  $*"
60         test -f "$REVISED" && rm -f "$REVISED"
61         exit 1
62 } 1>&2
63
64 # Called when doing the final "wrap up" clause because we've found
65 # one of the tagged lines that belongs in the final section.
66 #
67 function ck_wrapup() {
68         $IS_WRAPPING_UP && return
69
70         $HAS_LAST_BLANK || error "blank line must preceed signoff section"
71         $HAS_SUMMARY    || error "missing commit summary line."
72         $HAS_BODY       || error "missing commit description."
73
74         HAS_LAST_BLANK=false
75         IS_WRAPPING_UP=true
76 }
77
78 function do_signoff() {
79         ck_wrapup
80         # Signed-off-by: First Last <email@host.domain>
81         local txt=$(echo "${LINE#*: }" | grep -E "${EMAILPAT}")
82         if (( ${#txt} == 0 )); then
83                 error "$SIGNOFF line requires name and email address"
84         else
85                 HAS_SIGNOFF=true # require at least one
86         fi
87 }
88
89 function do_changeid() {
90         ck_wrapup
91         $HAS_CHANGEID && error "multiple $CHANGEID lines are not allowed"
92
93         # Change-Id: I1234567890123456789012345678901234567890
94         # capital "I" plus 40 hex digits
95         #
96         local txt=$(echo "$LINE" | grep "^$CHANGEID I[0-9a-fA-F]\{40\}\$")
97         (( ${#txt} > 0 )) ||
98                 error "has invalid $CHANGEID line for Gerrit tracking"
99
100         HAS_CHANGEID=true
101 }
102
103 function do_testparams() {
104         ck_wrapup
105
106         grep -q mdsfilesystemtype <<< $LINE &&
107                 error "mdsfilesystemtype is deprecated, use mdtfilesystemtype"
108 }
109
110 function do_fixes() {
111         ck_wrapup
112
113         local commit=$(awk '{ print $2 }' <<<$LINE)
114         git describe --tags $commit 2>&1 | grep "[Nn]ot a valid" &&
115                 error "has invalid $FIXES commit hash"
116 }
117
118 # All "innocuous" lines specify a person and email address
119 #
120 function do_innocuous() {
121         ck_wrapup
122         local txt=$(echo "${LINE#*: }" | grep -E "${EMAILPAT}")
123         (( ${#txt} == 0 )) && error "invalid name and address"
124 }
125
126 function do_default_line() {
127         $IS_WRAPPING_UP && {
128                 error "invalid signoff section line"
129                 return
130         }
131         if ${NEEDS_FIRST_LINE}; then
132                 HAS_JIRA_COMPONENT=$(echo "$LINE" | grep "$JIRA_FMT_A")
133
134                 if (( ${#HAS_JIRA_COMPONENT} == 0 )); then
135                         HAS_JIRA=$(echo "$LINE" | grep "$JIRA_FMT_B")
136                         if (( ${#HAS_JIRA} > 0 )); then
137                                 error "has no component in summary."
138                         else
139                                 error "missing JIRA ticket number."
140                         fi
141                 elif (( ${#LINE} > WIDTH_SUM )); then
142                         error "summary longer than $WIDTH_SUM columns."
143                 else
144                         HAS_SUMMARY=true
145                 fi
146                 NEEDS_FIRST_LINE=false
147
148         elif (( ${#LINE} > WIDTH_REG )); then
149                 error "has line longer than $WIDTH_REG columns."
150         elif ! $HAS_BODY && ! $HAS_LAST_BLANK; then
151                 error "has no blank line after summary."
152         else
153                 HAS_BODY=true
154         fi
155         HAS_LAST_BLANK=false
156 }
157
158 # Add a new unique Change-Id
159 #
160 new_changeid() {
161         local NEWID=$({
162                         git var GIT_AUTHOR_IDENT
163                         git var GIT_COMMITTER_IDENT
164                         git write-tree
165                         git rev-parse HEAD 2>/dev/null
166                         grep -v "^$SIGNOFF" "$ORIGINAL" | git stripspace -s
167                 } | git hash-object --stdin)
168         (( ${#NEWID} > 0 )) ||
169                 die "git hash-object failed for $CHANGEID:"
170
171         echo "$CHANGEID I$NEWID"
172 }
173
174 # A commit message error was encountered.
175 # All output redirected to stderr.
176 #
177 error() {
178         (( ${#LINE} > 0 )) && echo "line $NUM: $LINE"
179         echo "error: commit message $*" | fmt
180         HAS_ERROR=true
181 } 1>&2
182
183 usage() {
184         exec 1>&2
185         cat <<- EOF
186
187         See https://wiki.whamcloud.com/display/PUB/Commit+Comments
188         for full details.  An example valid commit comment is:
189
190         LU-nnn component: short description of change under 64 columns
191
192         The "component:" should be a lower-case single-word subsystem of the
193         Lustre code best covering the patch.  Example components include:
194            llite, lov, lmv, osc, mdc, ldlm, lnet, ptlrpc, mds, oss, osd,
195            ldiskfs, libcfs, socklnd, o2iblnd; recovery, quota, grant;
196            build, tests, docs. This list is not exhaustive, but a guideline.
197
198         The comment body should explan the change being made.  This can be
199         as long as needed.  Please include details of the problem that was
200         solved (including error messages that were seen), a good high-level
201         description of how it was solved, and which parts of the code were
202         changed (including important functions that were changed, if this is
203         useful to understand the patch, and for easier searching).
204         Performance patches should quanify the improvements being seen.
205         Wrap lines at/under $WIDTH_REG columns.
206
207         Finish the comment with a blank line followed by the signoff section:
208
209         $SIGNOFF Your Real Name <your_email@domain.name>
210         $CHANGEID Ixxxx(added automatically if missing)xxxx
211
212         The "$CHANGEID" line should only be present when updating a previous
213         commit/submission.  Copy the $CHANGEID from the original commit. It
214         will automatically be added by the Git commit-msg hook if missing.
215
216         The "signoff section" may optionally include other tag lines:
217         $(for T in $(tr '|' ' ' <<< "$INNOCUOUS"); do       \
218              echo "    $T: Some Person <email@domain.com>"; \
219           done)
220         $FIXES git_commit_hash ("optional summary of original broken patch")
221         $TESTPARAMS optional additional test parameters
222         {Organization}-bug-id: associated external change identifier
223         EOF
224
225         mv "$ORIGINAL" "$SAVE" &&
226                 echo "$0: saved original commit comment to $SAVE" 1>&2
227 }
228
229 init ${1+"$@"}
230 exec 3< "$ORIGINAL" 4> "$REVISED" || exit 1
231
232 while IFS= read -u3 LINE; do
233         ((NUM += 1))
234         case "$LINE" in
235         $SIGNOFF* )     do_signoff ;;
236         $CHANGEID* )    do_changeid ;;
237         $FIXES* )       do_fixes ;;
238         $TESTPARAMS* )  do_testparams ;;
239
240         "")
241                 HAS_LAST_BLANK=true
242
243                 # Do not emit blank lines before summary line or after
244                 # the tag lines have begun.
245                 #
246                 ${NEEDS_FIRST_LINE} || ${IS_WRAPPING_UP} && continue
247                 ;;
248
249         \#*)
250                 continue ## ignore and suppress comments
251                 ;;
252
253         "diff --git a/"* )
254                 # Beginning of uncommented diffstat from "commit -v".  If
255                 # there are diff and index lines, skip the rest of the input:
256                 #   diff --git a/build/commit-msg b/build/commit-msg
257                 #   index 80a3442..acb4c50 100755
258                 #   deleted file mode 100644
259                 #   old mode 100644
260                 # If a "diff --git" line is not followed by one of these
261                 # lines, do the default line processing on both lines.
262                 #
263                 IFS= read -u3 INDEX || break
264                 ((NUM += 1))
265                 case "$INDEX" in
266                 "index "[0-9a-fA-F]*) break ;;
267                 "deleted file mode "*) break  ;;
268                 "old mode "*) break ;;
269                 "new file mode "*) break ;;
270                 esac
271                 LINE=${LINE}$'\n'${INDEX}
272                 do_default_line
273                 ;;
274
275         *)
276                 if [[ "$LINE" =~ ^($INNOCUOUS): ]]; then
277                         do_innocuous
278
279                 elif [[ "$LINE" =~ ^[A-Za-z0-9_-]+-bug-id: ]]; then
280                         # Allow arbitrary external bug identifiers for tracking.
281                         #
282                         ck_wrapup
283
284                 else
285                         do_default_line
286                 fi
287                 ;;
288         esac
289
290         echo "$LINE" >&4
291 done
292
293 (( NUM <= 0 )) && die "empty commit message"
294
295 unset LINE
296 $HAS_SIGNOFF || error "missing valid $SIGNOFF: line."
297
298 if $HAS_ERROR; then
299         exec 3<&- 4>&-
300         usage
301         rm "$REVISED"
302         exit 1
303 fi
304
305 $HAS_CHANGEID || new_changeid >&4
306 exec 3<&- 4>&-
307
308 mv "$REVISED" "$ORIGINAL"
309
310 ## Local Variables:
311 ## Mode: shell-script
312 ## sh-basic-offset:          8
313 ## sh-indent-after-do:       8
314 ## sh-indentation:           8
315 ## sh-indent-for-case-label: 0
316 ## sh-indent-for-case-alt:   8
317 ## indent-tabs-mode:         t
318 ## End: