# Should be installed as .git/hooks/commit-msg.
#
-ORIGINAL="$1"
-REVISED="$(mktemp "$1.XXXXXX")"
-SIGNOFF="Signed-off-by:"
-CHANGEID="Change-Id:"
-WIDTH_SUM=64
-WIDTH_REG=70
-
-# Check for, and add if missing, a unique Change-Id
+init() {
+ set -a
+ readonly ORIGINAL="$1"
+ readonly REVISED="$(mktemp "$ORIGINAL.XXXXXX")"
+ readonly SAVE="$(basename $ORIGINAL).$(date +%Y%m%d.%H%M%S)"
+ readonly SIGNOFF="Signed-off-by:"
+ readonly CHANGEID="Change-Id:"
+ readonly INNOCUOUS=$(echo \
+ Acked-by \
+ Tested-by \
+ Reported-by \
+ Reviewed-by \
+ CC \
+ | tr ' ' '|')
+ readonly WIDTH_SUM=64
+ readonly WIDTH_REG=70
+ readonly JIRA_FMT_A="^[A-Z]\{2,5\}-[0-9]\{1,5\} [-a-z0-9]\{2,9\}: "
+ readonly JIRA_FMT_B="^[A-Z]\{2,5\}-[0-9]\{1,5\} "
+
+ # Identify a name followed by an email address.
+ #
+ readonly EMAILPAT=$'[ \t]*[^<> ]* [^<>]* <[^@ \t>]+@[a-zA-Z0-9.-]+\.[a-z]+>'
+
+ HAS_ERROR=false
+ HAS_SUMMARY=false
+ HAS_LAST_BLANK=false
+ HAS_BODY=false
+ HAS_SIGNOFF=false
+ HAS_CHANGEID=false
+
+ IS_WRAPPING_UP=false
+
+ LINE=""
+ NUM=0
+ set +a
+}
+
+# die: commit-msg fatal error: script error or empty input message
+# All output redirected to stderr.
+#
+die() {
+ echo "commit-msg fatal error: $*"
+ test -f "$REVISED" && rm -f "$REVISED"
+ exit 1
+} 1>&2
+
+# Called when doing the final "wrap up" clause because we've found
+# one of the tagged lines that belongs in the final section.
+#
+function ck_wrapup() {
+ $IS_WRAPPING_UP && return
+
+ $HAS_LAST_BLANK || error "blank line must preceed signoff section"
+ $HAS_SUMMARY || error "missing commit summary line."
+ $HAS_BODY || error "missing commit description."
+
+ HAS_LAST_BLANK=false
+ IS_WRAPPING_UP=true
+}
+
+function do_signoff() {
+ ck_wrapup
+ # Signed-off-by: First Last <email@host.domain>
+ local txt=$(echo "${LINE#*: }" | grep -E "${EMAILPAT}")
+ if (( ${#txt} == 0 )); then
+ error "$SIGNOFF line requires name and email address"
+ else
+ HAS_SIGNOFF=true # require at least one
+ fi
+}
+
+function do_changeid() {
+ ck_wrapup
+ $HAS_CHANGEID && error "multiple $CHANGEID lines are not allowed"
+
+ # Change-Id: I1234567890123456789012345678901234567890
+ # capital "I" plus 40 hex digits
+ #
+ local txt=$(echo "$LINE" | grep "^$CHANGEID I[0-9a-fA-F]\{40\}\$")
+ (( ${#txt} > 0 )) ||
+ error "has invalid $CHANGEID line for Gerrit tracking"
+
+ HAS_CHANGEID=true
+}
+
+# All "innocuous" lines specify a person and email address
+#
+function do_innocuous() {
+ ck_wrapup
+ local txt=$(echo "${LINE#*: }" | grep -E "${EMAILPAT}")
+ (( ${#txt} == 0 )) && error "invalid name and address"
+}
+
+function do_default_line() {
+ $IS_WRAPPING_UP && {
+ error "invalid signoff section line"
+ return
+ }
+ if (( NUM == 1 )); then
+ HAS_JIRA_COMPONENT=$(echo "$LINE" | grep "$JIRA_FMT_A")
+
+ if (( ${#HAS_JIRA_COMPONENT} == 0 )); then
+ HAS_JIRA=$(echo "$LINE" | grep "$JIRA_FMT_B")
+ if (( ${#HAS_JIRA} > 0 )); then
+ error "has no component in summary."
+ else
+ error "missing JIRA ticket number."
+ fi
+ elif (( ${#LINE} > WIDTH_SUM )); then
+ error "summary longer than $WIDTH_SUM columns."
+ else
+ HAS_SUMMARY=true
+ fi
+
+ elif (( ${#LINE} > WIDTH_REG )); then
+ error "has line longer than $WIDTH_REG columns."
+ elif ! $HAS_BODY && ! $HAS_LAST_BLANK; then
+ error "has no blank line after summary."
+ else
+ HAS_BODY=true
+ fi
+ HAS_LAST_BLANK=false
+}
+
+# Add a new unique Change-Id
+#
new_changeid() {
- {
- git var GIT_AUTHOR_IDENT
- git var GIT_COMMITTER_IDENT
- git write-tree
- git rev-parse HEAD 2>/dev/null
- grep -v "$SIGNOFF" "$ORIGINAL" | git stripspace -s
- } | git hash-object --stdin
+ local NEWID=$({
+ git var GIT_AUTHOR_IDENT
+ git var GIT_COMMITTER_IDENT
+ git write-tree
+ git rev-parse HEAD 2>/dev/null
+ grep -v "^$SIGNOFF" "$ORIGINAL" | git stripspace -s
+ } | git hash-object --stdin)
+ (( ${#NEWID} > 0 )) ||
+ die "git hash-object failed for $CHANGEID:"
+
+ echo "$CHANGEID I$NEWID"
}
+# A commit message error was encountered.
+# All output redirected to stderr.
+#
+error() {
+ (( ${#LINE} > 0 )) && echo "line $NUM: $LINE"
+ echo "error: commit message $*" | fmt --split-only
+ HAS_ERROR=true
+} 1>&2
+
usage() {
- [ "$LINE" ] && echo "error:$NUM: $LINE" 1>&2 && echo "" 1>&2
+ exec 1>&2
+ cat <<- EOF
- cat 1>&2 <<- EOF
- Commit message $1
- $2
- See http://wiki.whamcloud.com/display/PUB/Submitting+Changes
+ See http://wiki.whamcloud.com/display/PUB/Commit+Comments
for full details. An example valid commit comment is:
LU-nnn component: short description of change under 64 columns
- A more detailed explanation. This can be as detailed as you'd like.
- Please explain both what problem was solved and a good high-level
- description of how it was solved. Wrap at $WIDTH_REG columns or less.
+ The "component:" should be a lower-case single-word subsystem of the
+ Lustre code that best encompasses the change being made. Examples of
+ components include modules like: llite, lov, lmv, osc, mdc, ldlm, lnet,
+ ptlrpc, mds, oss, osd, ldiskfs, libcfs, socklnd, o2iblnd; functional
+ subsystems like: recovery, quota, grant; and auxilliary areas like:
+ build, tests, docs. This list is not exhaustive, but is a guideline.
+
+ The commit comment should contain a detailed explanation of the change
+ being made. This can be as long as you'd like. Please give details
+ of what problem was solved (including error messages or problems that
+ were seen), a good high-level description of how it was solved, and
+ which parts of the code were changed (including important functions
+ that were changed, if this is useful to understand the patch, and
+ for easier searching). Wrap lines at/under $WIDTH_REG columns.
+
+ Finish the comment with a blank line and a blank-line-free
+ sign off section:
$SIGNOFF Your Real Name <your_email@domain.name>
$CHANGEID Ixxxx(added automatically if missing)xxxx
+
+ The "$CHANGEID" line should only be there when updating a previous
+ commit/submission. Copy the one from the original commit.
+
+ The "sign off section" may also include several other tag lines:
+ $(for T in $(tr '|' ' ' <<< "$INNOCUOUS"); do \
+ echo " $T: Some Person <email@domain.com>"; \
+ done)
+ {Organization}-bug-id: associated external change identifier
EOF
- exit 1
+ mv "$ORIGINAL" "$SAVE" &&
+ echo "$0: saved original commit comment to $SAVE" 1>&2
}
-export HAS_SIGNOFF=false
-export HAS_SUMMARY=false
-export HAS_BLANK=false
-export HAS_COMMENTS=false
-export HAS_DIFF=false
-
-grep -q "^$CHANGEID" "$ORIGINAL" && HAS_CHANGEID=true || HAS_CHANGEID=false
-
-export LINE=""
-export NUM=1
-
-IFS=""
-while read LINE; do
- WIDTH=$(($(echo $LINE | wc -c) - 1)) # -1 for end-of-line character
-
- case "$LINE" in
- $SIGNOFF*)
- $HAS_SUMMARY || usage "missing summary before $SIGNOFF."
- $HAS_BLANK || usage "missing blank line before $SIGNOFF."
- # Signed-off-by: First Last <email@host.domain>
- GOOD=$(echo "$LINE" | grep "^$SIGNOFF .* .* <.*@[^.]*\..*>")
- [ -z "$GOOD" ] &&
- usage "missing valid commit summary line to show" \
- "agreement with code submission requirements at"
- HAS_SIGNOFF=true
- echo $LINE
- $HAS_CHANGEID || echo "$CHANGEID I$(new_changeid)"
- ;;
- $CHANGEID*)
- $HAS_SUMMARY || usage "missing summary before $CHANGEID line."
- $HAS_BLANK || usage "missing blank line before $CHANGEID line."
- # Change-Id: I762ab50568f25527176ae54e92c446cf06112097
- GOOD=$(echo "$LINE" | grep "^$CHANGEID I[0-9a-fA-F]\{40\}")
- [ -z "$GOOD" ] &&
- usage "missing valid $CHANGEID line for Gerrit tracking"
- HAS_CHANGEID=true
- echo $LINE
- ;;
- "") [ $HAS_SUMMARY -a $NUM -eq 2 ] && HAS_BLANK=true
- echo $LINE
- ;;
- diff*|index*) # beginning of uncommented diffstat from "commit -v"
- # diff --git a/build/commit-msg b/build/commit-msg
- # index 80a3442..acb4c50 100755
- DIFF=$(echo "$LINE" | grep -- "^diff --git a/")
- [ "$DIFF" ] && HAS_DIFF=true && continue
- INDEX=$(echo "$LINE" | grep -- "^index [0-9a-fA-F]\{6,\}\.\.")
- [ $HAS_DIFF -a "$INDEX" ] && break || HAS_DIFF=false
- ;;
- \#*) HAS_COMMENTS=true
- continue
- ;;
- *) if [ $NUM -eq 1 ]; then
- FMT="^[A-Z]\{2,5\}-[0-9]\{1,5\} [-a-z0-9]\{2,9\}: "
- GOOD=$(echo "$LINE" | grep "$FMT")
- [ $WIDTH -gt $WIDTH_SUM ] &&
- usage "summary longer than $WIDTH_SUM columns."
- if [ -z "$GOOD" ]; then
- FMT="^[A-Z]\{2,5\}-[0-9]\{1,5\} "
- NO_SUBSYS=$(echo "$LINE" | grep "$FMT")
- [ "$NO_SUBSYS" ] &&
- usage "has no subsys: in commit summary"
- usage "missing valid commit summary line."
- fi
- HAS_SUMMARY=true
- elif [ $WIDTH -gt $WIDTH_REG ]; then
- usage "has lines longer than $WIDTH_REG columns."
- fi
- HAS_DIFF=false
- echo $LINE
- ;;
- esac
-
- NUM=$((NUM + 1))
-done < "$ORIGINAL" > "$REVISED"
-
-[ $NUM -eq 1 ] && exit 1 # empty file
-
-LINE=""
-$HAS_SUMMARY || usage "missing commit summary line."
-$HAS_SIGNOFF || usage "missing $SIGNOFF line."
-$HAS_CHANGEID && rm "$REVISED" || mv "$REVISED" "$ORIGINAL"
+init ${1+"$@"}
+exec 3< "$ORIGINAL" 4> "$REVISED" || exit 1
+
+while IFS= read -u3 LINE; do
+ ((NUM += 1))
+ case "$LINE" in
+ $SIGNOFF* ) do_signoff ;;
+ $CHANGEID* ) do_changeid ;;
+
+ "")
+ HAS_LAST_BLANK=true
+ $IS_WRAPPING_UP && continue
+ ;;
+
+ \#*)
+ continue ## ignore and suppress comments
+ ;;
+
+ "diff --git a/"* )
+ # Beginning of uncommented diffstat from "commit -v". If
+ # there are diff and index lines, skip the rest of the input:
+ # diff --git a/build/commit-msg b/build/commit-msg
+ # index 80a3442..acb4c50 100755
+ # deleted file mode 100644
+ # old mode 100644
+ # If a "diff --git" line is not followed by one of these
+ # lines, do the default line processing on both lines.
+ #
+ IFS= read -u3 INDEX || break
+ ((NUM += 1))
+ case "$INDEX" in
+ "index "[0-9a-fA-F]*) break ;;
+ "deleted file mode "*) break ;;
+ "old mode "*) break ;;
+ esac
+ LINE=${LINE}$'\n'${INDEX}
+ do_default_line
+ ;;
+
+ *)
+ if [[ "$LINE" =~ ^($INNOCUOUS): ]]; then
+ do_innocuous
+ elif [[ "$LINE" =~ ^[A-Za-z0-9_-]+-bug-id: ]]; then
+ ck_wrapup
+ else
+ # Allow arbitrary external bug identifiers for tracking.
+ # I can't seem to find a pattern for the "case" that
+ # checks for "*-bug-id", so this is checked here.
+ do_default_line
+ fi
+ ;;
+ esac
+
+ echo "$LINE" >&4
+done
+
+(( NUM <= 0 )) && die "empty commit message"
+
+unset LINE
+$HAS_SIGNOFF || error "missing valid $SIGNOFF: line."
+
+if $HAS_ERROR; then
+ exec 3<&- 4>&-
+ usage
+ rm "$REVISED"
+ exit 1
+fi
+
+$HAS_CHANGEID || new_changeid >&4
+exec 3<&- 4>&-
+
+mv "$REVISED" "$ORIGINAL"
+
+## Local Variables:
+## Mode: shell-script
+## sh-basic-offset: 8
+## sh-indent-after-do: 8
+## sh-indentation: 8
+## sh-indent-for-case-label: 0
+## sh-indent-for-case-alt: 8
+## End: