Whamcloud - gitweb
LU-17705 ptlrpc: replace synchronize_rcu() with rcu_barrier()
[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 TEST_PARAMS="Test-Parameters:"
24         readonly TEST_PARAMS2="Test-parameters:"
25         readonly LUSTRE_CHANGE="Lustre-change:"
26         readonly LUSTRE_COMMIT="Lustre-commit:"
27         readonly LINUX_COMMIT="Linux-commit:"
28         readonly EMAILS=$(echo \
29                         Acked-by \
30                         Tested-by \
31                         Reported-by \
32                         Reviewed-by \
33                         CC \
34                 | tr ' ' '|')
35
36         # allow temporary override for rare cases (e.g. merge commits)
37         readonly WIDTH_SUM=${WIDTH_SUM:-62}
38         readonly WIDTH_REG=${WIDTH_REG:-70}
39         readonly JIRA_FMT_A="^[A-Z]\{2,9\}-[0-9]\{1,5\} [-a-z0-9]\{2,11\}: "
40         readonly JIRA_FMT_B="^[A-Z]\{2,9\}-[0-9]\{1,5\} "
41         readonly GERRIT_URL="https://review.whamcloud.com"
42
43         # Identify a name followed by an email address.
44         #
45         readonly EMAILPAT=$'[ \t]*[-._ [:alnum:]]* <[^@ \t>]+@[a-zA-Z0-9.-]+\.[a-z]+>'
46
47         HAS_ERROR=false
48         HAS_SUMMARY=false
49         HAS_LAST_BLANK=false
50         HAS_BODY=false
51         HAS_SIGNOFF=false
52         HAS_CHANGEID=false
53         NEEDS_FIRST_LINE=true
54
55         IS_WRAPPING_UP=false
56
57         LINE=""
58         NUM=0
59         set +a
60 }
61
62 # die: commit-msg fatal error: script error or empty input message
63 # All output redirected to stderr.
64 #
65 die() {
66         echo "commit-msg fatal error:  $*"
67         test -f "$REVISED" && rm -f "$REVISED"
68         exit 1
69 } 1>&2
70
71 # Called when doing the final "wrap up" clause because we've found
72 # one of the tagged lines that belongs in the final section.
73 #
74 function ck_wrapup() {
75         $IS_WRAPPING_UP && return
76
77         $HAS_LAST_BLANK || error "blank line must preceed signoff section"
78         $HAS_SUMMARY    || error "missing commit summary line."
79         $HAS_BODY       || error "missing commit description."
80
81         HAS_LAST_BLANK=false
82         IS_WRAPPING_UP=true
83 }
84
85 function do_signoff() {
86         ck_wrapup
87         # Signed-off-by: First Last <email@host.domain>
88         local txt=$(echo "${LINE#*: }" | grep -E "${EMAILPAT}")
89         if (( ${#txt} == 0 )); then
90                 error "$SIGNOFF line needs full name and email address"
91         else
92                 HAS_SIGNOFF=true # require at least one
93         fi
94 }
95
96 function do_changeid() {
97         ck_wrapup
98         $HAS_CHANGEID && error "multiple $CHANGEID lines not allowed"
99
100         # Change-Id: I1234567890123456789012345678901234567890
101         # capital "I" plus 40 hex digits
102         #
103         local txt=$(echo "$LINE" | grep "^$CHANGEID I[0-9a-fA-F]\{40\}\$")
104         (( ${#txt} > 0 )) ||
105                 error "has invalid $CHANGEID line for Gerrit tracking"
106
107         HAS_CHANGEID=true
108 }
109
110 function do_testparams() {
111         ck_wrapup
112
113         grep -q mdsfilesystemtype <<< $LINE &&
114                 error "mdsfilesystemtype is deprecated, use fstype"
115 }
116
117 function do_fixes() {
118         ck_wrapup
119
120         local commit=$(awk '{ print $2 }' <<<$LINE)
121         git describe --tags $commit 2>&1 | grep "[Nn]ot a valid" &&
122                 error "invalid $FIXES hash, want '<12hex> (\"summary line\")'"
123 }
124
125 # All "emails" lines specify a person and email address
126 #
127 function do_emails() {
128         ck_wrapup
129         local txt=$(echo "${LINE#*: }" | grep -E "${EMAILPAT}")
130         (( ${#txt} == 0 )) && error "${LINE%: *} invalid name and email"
131 }
132
133 # All "change" lines specify a Gerrit URL
134 #
135 function do_change() {
136         local url="${LINE#*change: }"
137         [[ $url =~ $GERRIT_URL/[0-9][0-9][0-9] ]] ||
138                 error "bad Gerrit URL, use '$GERRIT_URL/nnnnn' format"
139 }
140
141 # All "commit" lines specify a commit hash, but the commit may be in
142 # another repo, so the hash can't be directly verified
143 #
144 function do_commit() {
145         local val=${LINE#*commit: }
146         if [[ $val =~ TBD ]]; then
147                 val=${val#TBD (from }
148                 val=${val%)}
149         fi
150
151         [[ $val =~ [g-zG-Z] ]] && error "bad commit hash '$val', non-hex chars"
152         (( ${#val} == 40 )) || error "bad commit hash '$val', not 40 chars"
153 }
154
155 function do_default_line() {
156         $IS_WRAPPING_UP && {
157                 error "invalid signoff section line"
158                 return
159         }
160         if ${NEEDS_FIRST_LINE}; then
161                 HAS_JIRA_COMPONENT=$(echo "$LINE" | grep "$JIRA_FMT_A")
162
163                 if (( ${#HAS_JIRA_COMPONENT} == 0 )); then
164                         HAS_JIRA=$(echo "$LINE" | grep "$JIRA_FMT_B")
165                         if (( ${#HAS_JIRA} > 0 )); then
166                                 error "has no component in summary."
167                         else
168                                 error "missing JIRA ticket number."
169                         fi
170                 elif (( ${#LINE} > WIDTH_SUM )); then
171                         error "summary longer than $WIDTH_SUM columns."
172                 else
173                         HAS_SUMMARY=true
174                 fi
175                 NEEDS_FIRST_LINE=false
176
177         elif (( ${#LINE} > WIDTH_REG )) && ! [[ $LINE =~ http ]]; then
178                 # ignore long lines containing URLs
179                 error "has line longer than $WIDTH_REG columns."
180         elif ! $HAS_BODY && ! $HAS_LAST_BLANK; then
181                 error "has no blank line after summary."
182         else
183                 HAS_BODY=true
184         fi
185         HAS_LAST_BLANK=false
186 }
187
188 # Add a new unique Change-Id
189 #
190 new_changeid() {
191         local NEWID=$({
192                         git var GIT_AUTHOR_IDENT
193                         git var GIT_COMMITTER_IDENT
194                         git write-tree
195                         git rev-parse HEAD 2>/dev/null
196                         grep -v "^$SIGNOFF" "$ORIGINAL" | git stripspace -s
197                 } | git hash-object --stdin)
198         (( ${#NEWID} > 0 )) ||
199                 die "git hash-object failed for $CHANGEID:"
200
201         echo "$CHANGEID I$NEWID"
202 }
203
204 # A commit message error was encountered.
205 # All output redirected to stderr.
206 #
207 error() {
208         (( ${#LINE} > 0 )) && echo "line $NUM: $LINE"
209         echo "error: commit message $*"
210         HAS_ERROR=true
211 } 1>&2
212
213 short() {
214         echo "Run '$0 --help' for longer commit message help." 1>&2
215
216         mv "$ORIGINAL" "$SAVE" &&
217                 echo "$0: saved original commit comment to $SAVE" 1>&2
218 }
219
220 usage() {
221         cat << USAGE
222
223 Normally '$0' is invoked automatically by "git commit".
224
225 See https://wiki.whamcloud.com/display/PUB/Commit+Comments
226 for full details.  A good example of a valid commit comment is:
227
228     LU-nnnnn component: short description of change under 64 columns
229
230     The "component:" should be a lower-case single-word subsystem of the
231     Lustre code best covering the patch.  Example components include:
232         llite, lov, lmv, osc, mdc, ldlm, lnet, ptlrpc, mds, oss, osd,
233         ldiskfs, libcfs, socklnd, o2iblnd, recovery, quota, grant,
234         build, tests, docs. This list is not exhaustive, but a guideline.
235
236     The comment body should explan the change being made.  This can be
237     as long as needed.  Please include details of the problem that was
238     solved (including error messages that were seen), a good high-level
239     description of how it was solved, and which parts of the code were
240     changed (including important functions that were changed, if this
241     is useful to understand the patch, and for easier searching).
242     Performance patches should quanify the improvements being seen.
243     Wrap lines at/under $WIDTH_REG columns.
244
245     Optionally, if the patch is backported from master, include links
246     to the original patch to simplify tracking it across branches/repos:
247
248     $LUSTRE_CHANGE $GERRIT_URL/nnnn
249     $LUSTRE_COMMIT 40-char-git-hash-of-patch-on-master
250     or
251     $LUSTRE_COMMIT TBD (from 40-char-hash-of-unlanded-patch)
252
253     Finish the comment with a blank line followed by the signoff section.
254     The "$CHANGEID" line should only be present when updating a previous
255     commit/submission.  Keep the same $CHANGEID for ported patches. It
256     will automatically be added by the Git commit-msg hook if missing.
257
258     $TEST_PARAMS extra test options, see https://wiki.whamcloud.com/x/dICC
259     $FIXES 12-char-hash ("commit summary line of original broken patch")
260     $SIGNOFF Your Real Name <your_email@domain.name>
261     $CHANGEID Ixxxx(added automatically if missing)xxxx
262
263     The "signoff section" may optionally include other reviewer lines:
264         $(for T in $(tr '|' ' ' <<< "$EMAILS"); do              \
265                 echo "    $T: Some Person <email@example.com>"; \
266         done)
267     {Organization}-bug-id: associated external change identifier
268 USAGE
269 }
270
271 [[ "$1" == "--help" ]] && init && usage && exit 0
272
273 init ${1+"$@"}
274 exec 3< "$ORIGINAL" 4> "$REVISED" || exit 1
275
276 while IFS= read -u3 LINE; do
277         ((NUM += 1))
278         case "$LINE" in
279         $SIGNOFF* )             do_signoff ;;
280         $CHANGEID* )            do_changeid ;;
281         $FIXES* )               do_fixes ;;
282         $TEST_PARAMS* )         do_testparams ;;
283         $TEST_PARAMS2* )        do_testparams ;;
284         $LUSTRE_CHANGE* )       do_change ;;
285         $LUSTRE_COMMIT* )       do_commit ;;
286         $LINUX_COMMIT* )        do_commit ;;
287
288         "")
289                 HAS_LAST_BLANK=true
290
291                 # Do not emit blank lines before summary line or after
292                 # the tag lines have begun.
293                 #
294                 ${NEEDS_FIRST_LINE} || ${IS_WRAPPING_UP} && continue
295                 ;;
296
297         \#*)
298                 continue ## ignore and suppress comments
299                 ;;
300
301         "diff --git a/"* )
302                 # Beginning of uncommented diffstat from "commit -v".  If
303                 # there are diff and index lines, skip the rest of the input:
304                 #   diff --git a/build/commit-msg b/build/commit-msg
305                 #   index 80a3442..acb4c50 100755
306                 #   deleted file mode 100644
307                 #   old mode 100644
308                 # If a "diff --git" line is not followed by one of these
309                 # lines, do the default line processing on both lines.
310                 #
311                 IFS= read -u3 INDEX || break
312                 ((NUM += 1))
313                 case "$INDEX" in
314                 "index "[0-9a-fA-F]*) break ;;
315                 "deleted file mode "*) break  ;;
316                 "old mode "*) break ;;
317                 "new file mode "*) break ;;
318                 esac
319                 LINE=${LINE}$'\n'${INDEX}
320                 do_default_line
321                 ;;
322
323         *)
324                 if [[ "$LINE" =~ ^($EMAILS): ]]; then
325                         do_emails
326
327                 elif [[ "$LINE" =~ ^[A-Z][A-Za-z0-9_-]*-bug-id: ]]; then
328                         # Allow arbitrary external bug identifiers for tracking.
329                         #
330                         ck_wrapup
331
332                 else
333                         do_default_line
334                 fi
335                 ;;
336         esac
337
338         echo "$LINE" >&4
339 done
340
341 (( NUM <= 0 )) && die "empty commit message"
342
343 unset LINE
344 $HAS_SIGNOFF || error "missing valid $SIGNOFF: line."
345
346 if $HAS_ERROR && [[ -z "$SKIP" ]]; then
347         exec 3<&- 4>&-
348         short
349         rm "$REVISED"
350         exit 1
351 fi
352
353 $HAS_CHANGEID || new_changeid >&4
354 exec 3<&- 4>&-
355
356 mv "$REVISED" "$ORIGINAL"
357
358 ## Local Variables:
359 ## Mode: shell-script
360 ## sh-basic-offset:          8
361 ## sh-indent-after-do:       8
362 ## sh-indentation:           8
363 ## sh-indent-for-case-label: 0
364 ## sh-indent-for-case-alt:   8
365 ## indent-tabs-mode:         t
366 ## End: