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).
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.
12 # Should be installed as .git/hooks/commit-msg.
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 \
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\} "
36 # Identify a name followed by an email address.
38 readonly EMAILPAT=$'[ \t]*[^<> ]* [^<>]* <[^@ \t>]+@[a-zA-Z0-9.-]+\.[a-z]+>'
55 # die: commit-msg fatal error: script error or empty input message
56 # All output redirected to stderr.
59 echo "commit-msg fatal error: $*"
60 test -f "$REVISED" && rm -f "$REVISED"
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.
67 function ck_wrapup() {
68 $IS_WRAPPING_UP && return
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."
78 function do_signoff() {
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"
85 HAS_SIGNOFF=true # require at least one
89 function do_changeid() {
91 $HAS_CHANGEID && error "multiple $CHANGEID lines are not allowed"
93 # Change-Id: I1234567890123456789012345678901234567890
94 # capital "I" plus 40 hex digits
96 local txt=$(echo "$LINE" | grep "^$CHANGEID I[0-9a-fA-F]\{40\}\$")
98 error "has invalid $CHANGEID line for Gerrit tracking"
103 function do_testparams() {
106 grep -q mdsfilesystemtype <<< $LINE &&
107 error "mdsfilesystemtype is deprecated, use mdtfilesystemtype"
110 function do_fixes() {
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"
118 # All "innocuous" lines specify a person and email address
120 function do_innocuous() {
122 local txt=$(echo "${LINE#*: }" | grep -E "${EMAILPAT}")
123 (( ${#txt} == 0 )) && error "invalid name and address"
126 function do_default_line() {
128 error "invalid signoff section line"
131 if ${NEEDS_FIRST_LINE}; then
132 HAS_JIRA_COMPONENT=$(echo "$LINE" | grep "$JIRA_FMT_A")
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."
139 error "missing JIRA ticket number."
141 elif (( ${#LINE} > WIDTH_SUM )); then
142 error "summary longer than $WIDTH_SUM columns."
146 NEEDS_FIRST_LINE=false
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."
158 # Add a new unique Change-Id
162 git var GIT_AUTHOR_IDENT
163 git var GIT_COMMITTER_IDENT
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:"
171 echo "$CHANGEID I$NEWID"
174 # A commit message error was encountered.
175 # All output redirected to stderr.
178 (( ${#LINE} > 0 )) && echo "line $NUM: $LINE"
179 echo "error: commit message $*" | fmt
187 See https://wiki.whamcloud.com/display/PUB/Commit+Comments
188 for full details. An example valid commit comment is:
190 LU-nnn component: short description of change under 64 columns
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.
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.
207 Finish the comment with a blank line followed by the signoff section:
209 $SIGNOFF Your Real Name <your_email@domain.name>
210 $CHANGEID Ixxxx(added automatically if missing)xxxx
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.
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>"; \
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
225 mv "$ORIGINAL" "$SAVE" &&
226 echo "$0: saved original commit comment to $SAVE" 1>&2
230 exec 3< "$ORIGINAL" 4> "$REVISED" || exit 1
232 while IFS= read -u3 LINE; do
235 $SIGNOFF* ) do_signoff ;;
236 $CHANGEID* ) do_changeid ;;
237 $FIXES* ) do_fixes ;;
238 $TESTPARAMS* ) do_testparams ;;
243 # Do not emit blank lines before summary line or after
244 # the tag lines have begun.
246 ${NEEDS_FIRST_LINE} || ${IS_WRAPPING_UP} && continue
250 continue ## ignore and suppress comments
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
260 # If a "diff --git" line is not followed by one of these
261 # lines, do the default line processing on both lines.
263 IFS= read -u3 INDEX || break
266 "index "[0-9a-fA-F]*) break ;;
267 "deleted file mode "*) break ;;
268 "old mode "*) break ;;
269 "new file mode "*) break ;;
271 LINE=${LINE}$'\n'${INDEX}
276 if [[ "$LINE" =~ ^($INNOCUOUS): ]]; then
279 elif [[ "$LINE" =~ ^[A-Za-z0-9_-]+-bug-id: ]]; then
280 # Allow arbitrary external bug identifiers for tracking.
293 (( NUM <= 0 )) && die "empty commit message"
296 $HAS_SIGNOFF || error "missing valid $SIGNOFF: line."
305 $HAS_CHANGEID || new_changeid >&4
308 mv "$REVISED" "$ORIGINAL"
311 ## Mode: shell-script
312 ## sh-basic-offset: 8
313 ## sh-indent-after-do: 8
315 ## sh-indent-for-case-label: 0
316 ## sh-indent-for-case-alt: 8
317 ## indent-tabs-mode: t