Whamcloud - gitweb
LU-553 build: improve checks for commit-msg
[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 ORIGINAL="$1"
16 REVISED="$(mktemp "$ORIGINAL.XXXXXX")"
17 SAVE="$(basename $ORIGINAL).$(date +%Y%m%d.%H%M%S)"
18 SIGNOFF="Signed-off-by"
19 CHANGEID="Change-Id"
20 WIDTH_SUM=64
21 WIDTH_REG=70
22
23 # Check for, and add if missing, a unique Change-Id
24 new_changeid() {
25         NEWID=$({
26                         git var GIT_AUTHOR_IDENT
27                         git var GIT_COMMITTER_IDENT
28                         git write-tree
29                         git rev-parse HEAD 2>/dev/null
30                         grep -v "^$SIGNOFF" "$ORIGINAL" | git stripspace -s
31                 } | git hash-object --stdin)
32         if [ -z "$NEWID" ]; then
33                 error "$0: git hash-object failed for $CHANGEID:"
34                 exit 1
35         fi
36
37         echo "$CHANGEID: I$NEWID"
38         HAS_CHANGEID=true
39 }
40
41 error() {
42         [ "$LINE" ] && echo "line $NUM: $LINE" 1>&2
43         [ "$1" ] && echo "error: commit message $1" 1>&2
44         [ "$2" ] && echo "$2" 1>&2
45
46         HAS_ERROR=true
47 }
48
49 usage() {
50         cat 1>&2 <<- EOF
51
52         See http://wiki.whamcloud.com/display/PUB/Commit+Comments
53         for full details.  An example valid commit comment is:
54
55         LU-nnn component: short description of change under 64 columns
56
57         The "component:" should be a lower-case single-word subsystem of the
58         Lustre code that best encompasses the change being made.  Examples of
59         components include modules like: llite, lov, lmv, osc, mdc, ldlm, lnet,
60         ptlrpc, mds, oss, osd, ldiskfs, libcfs, socklnd, o2iblnd; functional
61         subsystems like: recovery, quota, grant; and auxilliary areas like:
62         build, tests, docs.  This list is not exhaustive, but is a guideline.
63
64         The commit comment should contain a detailed explanation of the change
65         being made.  This can be as long as you'd like.  Please give details
66         of what problem was solved (including error messages or problems that
67         were seen), a good high-level description of how it was solved, and
68         which parts of the code were changed (including important functions
69         that were changed, if this is useful to understand the patch, and
70         for easier searching).  Wrap lines at/under $WIDTH_REG columns.
71
72         $SIGNOFF: Your Real Name <your_email@domain.name>
73         $CHANGEID: Ixxxx(added automatically if missing)xxxx
74
75         EOF
76
77         mv "$ORIGINAL" "$SAVE" &&
78                 echo "$0: saved original commit comment to $SAVE" 1>&2
79 }
80
81 export HAS_ERROR=false
82 export HAS_SUMMARY=false
83 export HAS_LAST_BLANK=false
84 export HAS_BODY=false
85 export HAS_SIGNOFF=false
86 case $(grep -c "^$CHANGEID:" "$ORIGINAL") in
87 0)      export HAS_CHANGEID=false ;;
88 1)      export HAS_CHANGEID=true ;;
89 *)      error "with multiple $CHANGEID: lines not allowed."
90         export HAS_CHANGEID=true
91         HAS_ERROR=true ;;
92 esac
93 export HAS_COMMENTS=false
94 export HAS_DIFF=false
95
96 export LINE=""
97 export NUM=1
98
99 IFS="" # don't eat whitespace, to preserve message formatting
100 while read LINE; do
101         WIDTH=$(($(echo $LINE | wc -c) - 1)) # -1 for end-of-line character
102
103         case "$LINE" in
104         $SIGNOFF*)
105                 # Signed-off-by: First Last <email@host.domain>
106                 HAS_SOB=$(echo "$LINE" | grep "^$SIGNOFF: .* .* <.*@[^.]*\..*>")
107                 if [ -z "$HAS_SOB" ]; then
108                         error "missing valid commit summary line to show" \
109                               "agreement with code submission requirements."
110                 elif ! $HAS_SUMMARY; then
111                         error "missing summary before $SIGNOFF:."
112                 elif ! $HAS_LAST_BLANK; then
113                         error "missing blank line before $SIGNOFF:."
114                 elif ! $HAS_BODY; then
115                         error "missing useful comments before $SIGNOFF:."
116                 else
117                         HAS_SIGNOFF=true # allow multiple signoff lines
118                 fi
119                 echo $LINE
120                 ! $HAS_CHANGEID && new_changeid
121                 ;;
122         $CHANGEID*)
123                 # Change-Id: I762ab50568f25527176ae54e92c446cf06112097
124                 HAS_ID=$(echo "$LINE" | grep "^$CHANGEID: I[0-9a-fA-F]\{40\}")
125                 if [ -z "$HAS_ID" ]; then
126                         error "has invalid $CHANGEID: line for Gerrit tracking"
127                 elif ! $HAS_SUMMARY; then
128                         error "missing summary before $CHANGEID:."
129                 elif ! $HAS_BODY; then
130                         error "missing useful comments before $CHANGEID:."
131
132                 # $CHANGEID used to come before $SIGNOFF in old commits.
133                 # Allow this to continue for some time, until they are gone.
134                 # elif ! $HAS_SIGNOFF; then
135                 #       error "missing $SIGNOFF: before $CHANGEID:."
136
137                 # $HAS_CHANGEID was already checked and set above
138                 #elif $HAS_CHANGEID; then
139                 #       error "does not allow multiple $CHANGEID: lines."
140                 #else
141                 #       HAS_CHANGEID=true
142                 fi
143                 echo $LINE
144                 ;;
145         "")
146                 [ $HAS_SUMMARY -a ! $HAS_BODY ] && HAS_LAST_BLANK=true
147                 [ $HAS_BODY ] && HAS_LAST_BLANK=true
148                 echo $LINE
149                 ;;
150         diff*|index*)
151                 # Beginning of uncommented diffstat from "commit -v".  If
152                 # there are diff and index lines, skip the rest of the input.
153                 # diff --git a/build/commit-msg b/build/commit-msg
154                 # index 80a3442..acb4c50 100755
155                 DIFF=$(echo "$LINE" | grep -- "^diff --git a/")
156                 [ "$DIFF" ] && HAS_DIFF=true && continue
157                 INDEX=$(echo "$LINE" | grep -- "^index [0-9a-fA-F]\{6,\}\.\.")
158                 [ $HAS_DIFF -a "$INDEX" ] && break || HAS_DIFF=false
159                 ;;
160         \#*)
161                 HAS_COMMENTS=true
162                 continue
163                 ;;
164         *)      if [ $NUM -eq 1 ]; then
165                         FMT="^[A-Z]\{2,5\}-[0-9]\{1,5\} [-a-z0-9]\{2,9\}: "
166                         HAS_JIRA_COMPONENT=$(echo "$LINE" | grep "$FMT")
167                         if [ -z "$HAS_JIRA_COMPONENT" ]; then
168                                 FMT="^[A-Z]\{2,5\}-[0-9]\{1,5\} "
169                                 HAS_JIRA=$(echo "$LINE" | grep "$FMT")
170                                 if [ "$HAS_JIRA" ]; then
171                                         error "has no component in summary."
172                                 else
173                                         error "missing JIRA ticket number."
174                                 fi
175                         elif [ $WIDTH -gt $WIDTH_SUM ]; then
176                                 error "summary longer than $WIDTH_SUM columns."
177                         else
178                                 HAS_SUMMARY=true
179                         fi
180                 elif $HAS_SIGNOFF; then
181                         error "trailing garbage after $SIGNOFF: line."
182                 elif [ $WIDTH -gt $WIDTH_REG ]; then
183                         error "has line longer than $WIDTH_REG columns."
184                 elif ! $HAS_BODY && ! $HAS_LAST_BLANK; then
185                         error "has no blank line after summary."
186                 else
187                         HAS_BODY=true
188                 fi
189                 HAS_LAST_BLANK=false
190                 HAS_DIFF=false
191                 echo $LINE
192                 ;;
193         esac
194
195         NUM=$((NUM + 1))
196 done < "$ORIGINAL" > "$REVISED"
197
198 [ $NUM -eq 1 ] && exit 1 # empty file
199
200 LINE=""
201 if ! $HAS_SUMMARY; then
202         error "missing commit summary line."
203 elif ! $HAS_BODY; then
204         error "missing commit description."
205 elif ! $HAS_SIGNOFF; then
206         error "missing valid $SIGNOFF: line."
207 elif ! $HAS_CHANGEID; then
208         error "missing valid $CHANGEID: line."
209 fi
210 if $HAS_ERROR; then
211         usage
212         rm "$REVISED"
213         exit 1
214 fi
215
216 mv "$REVISED" "$ORIGINAL"