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,9\}-[0-9]\{1,5\} [-a-z0-9]\{2,11\}: "
33 readonly JIRA_FMT_B="^[A-Z]\{2,9\}-[0-9]\{1,5\} "
35 # Identify a name followed by an email address.
37 readonly EMAILPAT=$'[ \t]*[^<> ]* [^<>]* <[^@ \t>]+@[a-zA-Z0-9.-]+\.[a-z]+>'
54 # die: commit-msg fatal error: script error or empty input message
55 # All output redirected to stderr.
58 echo "commit-msg fatal error: $*"
59 test -f "$REVISED" && rm -f "$REVISED"
63 # Called when doing the final "wrap up" clause because we've found
64 # one of the tagged lines that belongs in the final section.
66 function ck_wrapup() {
67 $IS_WRAPPING_UP && return
69 $HAS_LAST_BLANK || error "blank line must preceed signoff section"
70 $HAS_SUMMARY || error "missing commit summary line."
71 $HAS_BODY || error "missing commit description."
77 function do_signoff() {
79 # Signed-off-by: First Last <email@host.domain>
80 local txt=$(echo "${LINE#*: }" | grep -E "${EMAILPAT}")
81 if (( ${#txt} == 0 )); then
82 error "$SIGNOFF line requires name and email address"
84 HAS_SIGNOFF=true # require at least one
88 function do_changeid() {
90 $HAS_CHANGEID && error "multiple $CHANGEID lines are not allowed"
92 # Change-Id: I1234567890123456789012345678901234567890
93 # capital "I" plus 40 hex digits
95 local txt=$(echo "$LINE" | grep "^$CHANGEID I[0-9a-fA-F]\{40\}\$")
97 error "has invalid $CHANGEID line for Gerrit tracking"
102 function do_testparams() {
105 grep -q mdsfilesystemtype <<< $LINE &&
106 error "mdsfilesystemtype is deprecated, use mdtfilesystemtype"
109 # All "innocuous" lines specify a person and email address
111 function do_innocuous() {
113 local txt=$(echo "${LINE#*: }" | grep -E "${EMAILPAT}")
114 (( ${#txt} == 0 )) && error "invalid name and address"
117 function do_default_line() {
119 error "invalid signoff section line"
122 if ${NEEDS_FIRST_LINE}; then
123 HAS_JIRA_COMPONENT=$(echo "$LINE" | grep "$JIRA_FMT_A")
125 if (( ${#HAS_JIRA_COMPONENT} == 0 )); then
126 HAS_JIRA=$(echo "$LINE" | grep "$JIRA_FMT_B")
127 if (( ${#HAS_JIRA} > 0 )); then
128 error "has no component in summary."
130 error "missing JIRA ticket number."
132 elif (( ${#LINE} > WIDTH_SUM )); then
133 error "summary longer than $WIDTH_SUM columns."
137 NEEDS_FIRST_LINE=false
139 elif (( ${#LINE} > WIDTH_REG )); then
140 error "has line longer than $WIDTH_REG columns."
141 elif ! $HAS_BODY && ! $HAS_LAST_BLANK; then
142 error "has no blank line after summary."
149 # Add a new unique Change-Id
153 git var GIT_AUTHOR_IDENT
154 git var GIT_COMMITTER_IDENT
156 git rev-parse HEAD 2>/dev/null
157 grep -v "^$SIGNOFF" "$ORIGINAL" | git stripspace -s
158 } | git hash-object --stdin)
159 (( ${#NEWID} > 0 )) ||
160 die "git hash-object failed for $CHANGEID:"
162 echo "$CHANGEID I$NEWID"
165 # A commit message error was encountered.
166 # All output redirected to stderr.
169 (( ${#LINE} > 0 )) && echo "line $NUM: $LINE"
170 echo "error: commit message $*" | fmt
178 See https://wiki.whamcloud.com/display/PUB/Commit+Comments
179 for full details. An example valid commit comment is:
181 LU-nnn component: short description of change under 64 columns
183 The "component:" should be a lower-case single-word subsystem of the
184 Lustre code that best encompasses the change being made. Examples of
185 components include modules like: llite, lov, lmv, osc, mdc, ldlm, lnet,
186 ptlrpc, mds, oss, osd, ldiskfs, libcfs, socklnd, o2iblnd; functional
187 subsystems like: recovery, quota, grant; and auxilliary areas like:
188 build, tests, docs. This list is not exhaustive, but is a guideline.
190 The commit comment should contain a detailed explanation of the change
191 being made. This can be as long as you'd like. Please give details
192 of what problem was solved (including error messages or problems that
193 were seen), a good high-level description of how it was solved, and
194 which parts of the code were changed (including important functions
195 that were changed, if this is useful to understand the patch, and
196 for easier searching). Wrap lines at/under $WIDTH_REG columns.
198 Finish the comment with a blank line and a blank-line-free
201 $SIGNOFF Your Real Name <your_email@domain.name>
202 $CHANGEID Ixxxx(added automatically if missing)xxxx
204 The "$CHANGEID" line should only be there when updating a previous
205 commit/submission. Copy the one from the original commit.
207 The "sign off section" may also include several other tag lines:
208 $(for T in $(tr '|' ' ' <<< "$INNOCUOUS"); do \
209 echo " $T: Some Person <email@domain.com>"; \
211 $TESTPARAMS optional additional test parameters
212 {Organization}-bug-id: associated external change identifier
215 mv "$ORIGINAL" "$SAVE" &&
216 echo "$0: saved original commit comment to $SAVE" 1>&2
220 exec 3< "$ORIGINAL" 4> "$REVISED" || exit 1
222 while IFS= read -u3 LINE; do
225 $SIGNOFF* ) do_signoff ;;
226 $CHANGEID* ) do_changeid ;;
227 $TESTPARAMS* ) do_testparams ;;
232 # Do not emit blank lines before summary line or after
233 # the tag lines have begun.
235 ${NEEDS_FIRST_LINE} || ${IS_WRAPPING_UP} && continue
239 continue ## ignore and suppress comments
243 # Beginning of uncommented diffstat from "commit -v". If
244 # there are diff and index lines, skip the rest of the input:
245 # diff --git a/build/commit-msg b/build/commit-msg
246 # index 80a3442..acb4c50 100755
247 # deleted file mode 100644
249 # If a "diff --git" line is not followed by one of these
250 # lines, do the default line processing on both lines.
252 IFS= read -u3 INDEX || break
255 "index "[0-9a-fA-F]*) break ;;
256 "deleted file mode "*) break ;;
257 "old mode "*) break ;;
258 "new file mode "*) break ;;
260 LINE=${LINE}$'\n'${INDEX}
265 if [[ "$LINE" =~ ^($INNOCUOUS): ]]; then
268 elif [[ "$LINE" =~ ^[A-Za-z0-9_-]+-bug-id: ]]; then
269 # Allow arbitrary external bug identifiers for tracking.
282 (( NUM <= 0 )) && die "empty commit message"
285 $HAS_SIGNOFF || error "missing valid $SIGNOFF: line."
294 $HAS_CHANGEID || new_changeid >&4
297 mv "$REVISED" "$ORIGINAL"
300 ## Mode: shell-script
301 ## sh-basic-offset: 8
302 ## sh-indent-after-do: 8
304 ## sh-indent-for-case-label: 0
305 ## sh-indent-for-case-alt: 8
306 ## indent-tabs-mode: t