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 '$commit'"
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;
197 This list is not exhaustive, but a guideline.
199 The comment body should explan the change being made. This can be
200 as long as needed. Please include details of the problem that was
201 solved (including error messages that were seen), a good high-level
202 description of how it was solved, and which parts of the code were
203 changed (including important functions that were changed, if this is
204 useful to understand the patch, and for easier searching).
205 Performance patches should quanify the improvements being seen.
206 Wrap lines at/under $WIDTH_REG columns.
208 Finish the comment with a blank line followed by the signoff section:
210 $SIGNOFF Your Real Name <your_email@domain.name>
211 $CHANGEID Ixxxx(added automatically if missing)xxxx
213 The "$CHANGEID" line should only be present when updating a previous
214 commit/submission. Copy the $CHANGEID from the original commit. It
215 will automatically be added by the Git commit-msg hook if missing.
217 The "signoff section" may optionally include other tag lines:
218 $(for T in $(tr '|' ' ' <<< "$INNOCUOUS"); do \
219 echo " $T: Some Person <email@domain.com>"; \
221 $FIXES 12chrgithash ("summary of original broken patch")
222 $TESTPARAMS optional additional test parameters
223 {Organization}-bug-id: associated external change identifier
226 mv "$ORIGINAL" "$SAVE" &&
227 echo "$0: saved original commit comment to $SAVE" 1>&2
231 exec 3< "$ORIGINAL" 4> "$REVISED" || exit 1
233 while IFS= read -u3 LINE; do
236 $SIGNOFF* ) do_signoff ;;
237 $CHANGEID* ) do_changeid ;;
238 $FIXES* ) do_fixes ;;
239 $TESTPARAMS* ) do_testparams ;;
244 # Do not emit blank lines before summary line or after
245 # the tag lines have begun.
247 ${NEEDS_FIRST_LINE} || ${IS_WRAPPING_UP} && continue
251 continue ## ignore and suppress comments
255 # Beginning of uncommented diffstat from "commit -v". If
256 # there are diff and index lines, skip the rest of the input:
257 # diff --git a/build/commit-msg b/build/commit-msg
258 # index 80a3442..acb4c50 100755
259 # deleted file mode 100644
261 # If a "diff --git" line is not followed by one of these
262 # lines, do the default line processing on both lines.
264 IFS= read -u3 INDEX || break
267 "index "[0-9a-fA-F]*) break ;;
268 "deleted file mode "*) break ;;
269 "old mode "*) break ;;
270 "new file mode "*) break ;;
272 LINE=${LINE}$'\n'${INDEX}
277 if [[ "$LINE" =~ ^($INNOCUOUS): ]]; then
280 elif [[ "$LINE" =~ ^[A-Za-z0-9_-]+-bug-id: ]]; then
281 # Allow arbitrary external bug identifiers for tracking.
294 (( NUM <= 0 )) && die "empty commit message"
297 $HAS_SIGNOFF || error "missing valid $SIGNOFF: line."
306 $HAS_CHANGEID || new_changeid >&4
309 mv "$REVISED" "$ORIGINAL"
312 ## Mode: shell-script
313 ## sh-basic-offset: 8
314 ## sh-indent-after-do: 8
316 ## sh-indent-for-case-label: 0
317 ## sh-indent-for-case-alt: 8
318 ## indent-tabs-mode: t