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