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 INNOCUOUS=$(echo \
31 readonly JIRA_FMT_A="^[A-Z]\{2,5\}-[0-9]\{1,5\} [-a-z0-9]\{2,9\}: "
32 readonly JIRA_FMT_B="^[A-Z]\{2,5\}-[0-9]\{1,5\} "
34 # Identify a name followed by an email address.
36 readonly EMAILPAT=$'[ \t]*[a-zA-Z][^<>]*[ \t]<[^@ \t>]+@[^@ \t>]+>'
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\}\$")
95 (( ${#txt} > 0 )) || \
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."
141 # Add a new unique Change-Id
145 git var GIT_AUTHOR_IDENT
146 git var GIT_COMMITTER_IDENT
148 git rev-parse HEAD 2>/dev/null
149 grep -v "^$SIGNOFF" "$ORIGINAL" | git stripspace -s
150 } | git hash-object --stdin)
151 (( ${#NEWID} > 0 )) || \
152 die "git hash-object failed for $CHANGEID:"
154 echo "$CHANGEID I$NEWID"
157 # A commit message error was encountered.
158 # All output redirected to stderr.
161 (( ${#LINE} > 0 )) && echo "line $NUM: $LINE"
162 echo "error: commit message $*" | fmt --split-only
170 See http://wiki.whamcloud.com/display/PUB/Commit+Comments
171 for full details. An example valid commit comment is:
173 LU-nnn component: short description of change under 64 columns
175 The "component:" should be a lower-case single-word subsystem of the
176 Lustre code that best encompasses the change being made. Examples of
177 components include modules like: llite, lov, lmv, osc, mdc, ldlm, lnet,
178 ptlrpc, mds, oss, osd, ldiskfs, libcfs, socklnd, o2iblnd; functional
179 subsystems like: recovery, quota, grant; and auxilliary areas like:
180 build, tests, docs. This list is not exhaustive, but is a guideline.
182 The commit comment should contain a detailed explanation of the change
183 being made. This can be as long as you'd like. Please give details
184 of what problem was solved (including error messages or problems that
185 were seen), a good high-level description of how it was solved, and
186 which parts of the code were changed (including important functions
187 that were changed, if this is useful to understand the patch, and
188 for easier searching). Wrap lines at/under $WIDTH_REG columns.
190 Finish the comment with a blank line and a blank-line-free
193 $SIGNOFF Your Real Name <your_email@domain.name>
194 $CHANGEID Ixxxx(added automatically if missing)xxxx
196 Included in the "sign off section" may be any of several other
198 $(echo "$INNOCUOUS" | tr '|' ' ')
199 The "$CHANGEID" line should only be there when updating a previous
200 commit/submission. Copy the one from that commit.
203 mv "$ORIGINAL" "$SAVE" && \
204 echo "$0: saved original commit comment to $SAVE" 1>&2
208 exec 3< "$ORIGINAL" 4> "$REVISED" || exit 1
210 while IFS= read -u3 LINE; do
213 $SIGNOFF* ) do_signoff ;;
214 $CHANGEID* ) do_changeid ;;
218 error "blank lines not allowed in signoff section"
223 continue ## ignore and suppress comments
227 # Beginning of uncommented diffstat from "commit -v". If
228 # there are diff and index lines, skip the rest of the input:
229 # diff --git a/build/commit-msg b/build/commit-msg
230 # index 80a3442..acb4c50 100755
231 # If a "diff --git" line is not followed by an index line,
232 # do the default line processing on both lines.
234 IFS= read -u3 INDEX || break
236 ln=$(echo "$INDEX" | grep "^index [0-9a-fA-F]\{6,\}\.\.")
237 (( ${#ln} > 1 )) && break
238 LINE=${LINE}$'\n'${INDEX}
243 if [[ "$LINE" =~ ^($INNOCUOUS): ]] ; then
254 (( NUM <= 0 )) && die "empty commit message"
257 $HAS_SIGNOFF || error "missing valid $SIGNOFF: line."
266 $HAS_CHANGEID || new_changeid >&4
269 mv "$REVISED" "$ORIGINAL"
272 ## Mode: shell-script
273 ## sh-basic-offset: 8
274 ## sh-indent-after-do: 8
276 ## sh-indent-for-case-label: 0
277 ## sh-indent-for-case-alt: 8