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