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 TESTPARAMS="Test-Parameters:"
23 readonly INNOCUOUS=$(echo \
32 readonly JIRA_FMT_A="^[A-Z]\{2,5\}-[0-9]\{1,5\} [-a-z0-9]\{2,11\}: "
33 readonly JIRA_FMT_B="^[A-Z]\{2,5\}-[0-9]\{1,5\} "
35 # Identify a name followed by an email address.
37 readonly EMAILPAT=$'[ \t]*[^<> ]* [^<>]* <[^@ \t>]+@[a-zA-Z0-9.-]+\.[a-z]+>'
53 # die: commit-msg fatal error: script error or empty input message
54 # All output redirected to stderr.
57 echo "commit-msg fatal error: $*"
58 test -f "$REVISED" && rm -f "$REVISED"
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.
65 function ck_wrapup() {
66 $IS_WRAPPING_UP && return
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."
76 function do_signoff() {
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"
83 HAS_SIGNOFF=true # require at least one
87 function do_changeid() {
89 $HAS_CHANGEID && error "multiple $CHANGEID lines are not allowed"
91 # Change-Id: I1234567890123456789012345678901234567890
92 # capital "I" plus 40 hex digits
94 local txt=$(echo "$LINE" | grep "^$CHANGEID I[0-9a-fA-F]\{40\}\$")
96 error "has invalid $CHANGEID line for Gerrit tracking"
101 # All "innocuous" lines specify a person and email address
103 function do_innocuous() {
105 local txt=$(echo "${LINE#*: }" | grep -E "${EMAILPAT}")
106 (( ${#txt} == 0 )) && error "invalid name and address"
109 function do_default_line() {
111 error "invalid signoff section line"
114 if (( NUM == 1 )); then
115 HAS_JIRA_COMPONENT=$(echo "$LINE" | grep "$JIRA_FMT_A")
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."
122 error "missing JIRA ticket number."
124 elif (( ${#LINE} > WIDTH_SUM )); then
125 error "summary longer than $WIDTH_SUM columns."
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."
140 # Add a new unique Change-Id
144 git var GIT_AUTHOR_IDENT
145 git var GIT_COMMITTER_IDENT
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:"
153 echo "$CHANGEID I$NEWID"
156 # A commit message error was encountered.
157 # All output redirected to stderr.
160 (( ${#LINE} > 0 )) && echo "line $NUM: $LINE"
161 echo "error: commit message $*" | fmt
169 See http://wiki.whamcloud.com/display/PUB/Commit+Comments
170 for full details. An example valid commit comment is:
172 LUDOC-nnn component: short description of change under 64 columns
174 The "component:" should be a lower-case single-word section of the Lustre
175 manual that best encompasses the change being made. Examples of sections
176 include: setup, configuration, debug. This list is not exhaustive, but is a
179 The commit comment should contain a detailed explanation of the change
180 being made. This can be as long as you'd like. Wrap lines at/under $WIDTH_REG
183 Finish the comment with a blank line and a blank-line-free
186 $SIGNOFF Your Real Name <your_email@domain.name>
187 $CHANGEID Ixxxx(added automatically if missing)xxxx
189 The "$CHANGEID" line should only be there when updating a previous
190 commit/submission. Copy the one from the original commit.
192 The "sign off section" may also include several other tag lines:
193 $(for T in $(tr '|' ' ' <<< "$INNOCUOUS"); do \
194 echo " $T: Some Person <email@domain.com>"; \
196 $TESTPARAMS optional additional test parameters
197 {Organization}-bug-id: associated external change identifier
200 mv "$ORIGINAL" "$SAVE" &&
201 echo "$0: saved original commit comment to $SAVE" 1>&2
205 exec 3< "$ORIGINAL" 4> "$REVISED" || exit 1
207 while IFS= read -u3 LINE; do
210 $SIGNOFF* ) do_signoff ;;
211 $CHANGEID* ) do_changeid ;;
212 $TESTPARAMS* ) ck_wrapup ;;
216 $IS_WRAPPING_UP && continue
220 continue ## ignore and suppress comments
224 # Beginning of uncommented diffstat from "commit -v". If
225 # there are diff and index lines, skip the rest of the input:
226 # diff --git a/build/commit-msg b/build/commit-msg
227 # index 80a3442..acb4c50 100755
228 # deleted file mode 100644
230 # If a "diff --git" line is not followed by one of these
231 # lines, do the default line processing on both lines.
233 IFS= read -u3 INDEX || break
236 "index "[0-9a-fA-F]*) break ;;
237 "deleted file mode "*) break ;;
238 "old mode "*) break ;;
240 LINE=${LINE}$'\n'${INDEX}
245 if [[ "$LINE" =~ ^($INNOCUOUS): ]]; then
247 elif [[ "$LINE" =~ ^[A-Za-z0-9_-]+-bug-id: ]]; then
250 # Allow arbitrary external bug identifiers for tracking.
251 # I can't seem to find a pattern for the "case" that
252 # checks for "*-bug-id", so this is checked here.
261 (( NUM <= 0 )) && die "empty commit message"
264 $HAS_SIGNOFF || error "missing valid $SIGNOFF: line."
273 $HAS_CHANGEID || new_changeid >&4
276 mv "$REVISED" "$ORIGINAL"
279 ## Mode: shell-script
280 ## sh-basic-offset: 8
281 ## sh-indent-after-do: 8
283 ## sh-indent-for-case-label: 0
284 ## sh-indent-for-case-alt: 8