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]*[^<> ]* [^<>]* <[^@ \t>]+@[a-zA-Z0-9.-]+\.[a-z]+>'
52 # die: commit-msg fatal error: script error or empty input message
53 # All output redirected to stderr.
56 echo "commit-msg fatal error: $*"
57 test -f "$REVISED" && rm -f "$REVISED"
61 # Called when doing the final "wrap up" clause because we've found
62 # one of the tagged lines that belongs in the final section.
64 function ck_wrapup() {
65 $IS_WRAPPING_UP && return
67 $HAS_LAST_BLANK || error "blank line must preceed signoff section"
68 $HAS_SUMMARY || error "missing commit summary line."
69 $HAS_BODY || error "missing commit description."
75 function do_signoff() {
77 # Signed-off-by: First Last <email@host.domain>
78 local txt=$(echo "${LINE#*: }" | grep -E "${EMAILPAT}")
79 if (( ${#txt} == 0 )); then
80 error "$SIGNOFF line requires name and email address"
82 HAS_SIGNOFF=true # require at least one
86 function do_changeid() {
88 $HAS_CHANGEID && error "multiple $CHANGEID lines are not allowed"
90 # Change-Id: I1234567890123456789012345678901234567890
91 # capital "I" plus 40 hex digits
93 local txt=$(echo "$LINE" | grep "^$CHANGEID I[0-9a-fA-F]\{40\}\$")
95 error "has invalid $CHANGEID line for Gerrit tracking"
100 # All "innocuous" lines specify a person and email address
102 function do_innocuous() {
104 local txt=$(echo "${LINE#*: }" | grep -E "${EMAILPAT}")
105 (( ${#txt} == 0 )) && error "invalid name and address"
108 function do_default_line() {
110 error "invalid signoff section line"
113 if (( NUM == 1 )); then
114 HAS_JIRA_COMPONENT=$(echo "$LINE" | grep "$JIRA_FMT_A")
116 if (( ${#HAS_JIRA_COMPONENT} == 0 )); then
117 HAS_JIRA=$(echo "$LINE" | grep "$JIRA_FMT_B")
118 if (( ${#HAS_JIRA} > 0 )); then
119 error "has no component in summary."
121 error "missing JIRA ticket number."
123 elif (( ${#LINE} > WIDTH_SUM )); then
124 error "summary longer than $WIDTH_SUM columns."
129 elif (( ${#LINE} > WIDTH_REG )); then
130 error "has line longer than $WIDTH_REG columns."
131 elif ! $HAS_BODY && ! $HAS_LAST_BLANK; then
132 error "has no blank line after summary."
139 # Add a new unique Change-Id
143 git var GIT_AUTHOR_IDENT
144 git var GIT_COMMITTER_IDENT
146 git rev-parse HEAD 2>/dev/null
147 grep -v "^$SIGNOFF" "$ORIGINAL" | git stripspace -s
148 } | git hash-object --stdin)
149 (( ${#NEWID} > 0 )) ||
150 die "git hash-object failed for $CHANGEID:"
152 echo "$CHANGEID I$NEWID"
155 # A commit message error was encountered.
156 # All output redirected to stderr.
159 (( ${#LINE} > 0 )) && echo "line $NUM: $LINE"
160 echo "error: commit message $*" | fmt --split-only
168 See http://wiki.whamcloud.com/display/PUB/Commit+Comments
169 for full details. An example valid commit comment is:
171 LU-nnn component: short description of change under 64 columns
173 The "component:" should be a lower-case single-word subsystem of the
174 Lustre code that best encompasses the change being made. Examples of
175 components include modules like: llite, lov, lmv, osc, mdc, ldlm, lnet,
176 ptlrpc, mds, oss, osd, ldiskfs, libcfs, socklnd, o2iblnd; functional
177 subsystems like: recovery, quota, grant; and auxilliary areas like:
178 build, tests, docs. This list is not exhaustive, but is a guideline.
180 The commit comment should contain a detailed explanation of the change
181 being made. This can be as long as you'd like. Please give details
182 of what problem was solved (including error messages or problems that
183 were seen), a good high-level description of how it was solved, and
184 which parts of the code were changed (including important functions
185 that were changed, if this is useful to understand the patch, and
186 for easier searching). Wrap lines at/under $WIDTH_REG columns.
188 Finish the comment with a blank line and a blank-line-free
191 $SIGNOFF Your Real Name <your_email@domain.name>
192 $CHANGEID Ixxxx(added automatically if missing)xxxx
194 The "$CHANGEID" line should only be there when updating a previous
195 commit/submission. Copy the one from the original commit.
197 The "sign off section" may also include several other tag lines:
198 $(for T in $(tr '|' ' ' <<< "$INNOCUOUS"); do \
199 echo " $T: Some Person <email@domain.com>"; \
201 {Organization}-bug-id: associated external change identifier
204 mv "$ORIGINAL" "$SAVE" &&
205 echo "$0: saved original commit comment to $SAVE" 1>&2
209 exec 3< "$ORIGINAL" 4> "$REVISED" || exit 1
211 while IFS= read -u3 LINE; do
214 $SIGNOFF* ) do_signoff ;;
215 $CHANGEID* ) do_changeid ;;
219 $IS_WRAPPING_UP && continue
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 # deleted file mode 100644
233 # If a "diff --git" line is not followed by one of these
234 # lines, do the default line processing on both lines.
236 IFS= read -u3 INDEX || break
239 "index "[0-9a-fA-F]*) break ;;
240 "deleted file mode "*) break ;;
241 "old mode "*) break ;;
243 LINE=${LINE}$'\n'${INDEX}
248 if [[ "$LINE" =~ ^($INNOCUOUS): ]]; then
250 elif [[ "$LINE" =~ ^[A-Za-z0-9_-]+-bug-id: ]]; then
253 # Allow arbitrary external bug identifiers for tracking.
254 # I can't seem to find a pattern for the "case" that
255 # checks for "*-bug-id", so this is checked here.
264 (( NUM <= 0 )) && die "empty commit message"
267 $HAS_SIGNOFF || error "missing valid $SIGNOFF: line."
276 $HAS_CHANGEID || new_changeid >&4
279 mv "$REVISED" "$ORIGINAL"
282 ## Mode: shell-script
283 ## sh-basic-offset: 8
284 ## sh-indent-after-do: 8
286 ## sh-indent-for-case-label: 0
287 ## sh-indent-for-case-alt: 8