Whamcloud - gitweb
LU-2961 build: Fix commit-msg to love new files
[fs/lustre-release.git] / build / commit-msg
index 80a3442..e0803ea 100755 (executable)
 # Should be installed as .git/hooks/commit-msg.
 #
 
-ORIGINAL="$1"
-REVISED="$(mktemp "$1.XXXXXX")"
-SIGNOFF="Signed-off-by:"
-CHANGEID="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 TESTPARAMS="Test-Parameters:"
+        readonly INNOCUOUS=$(echo \
+                        Acked-by \
+                        Tested-by \
+                        Reported-by \
+                        Reviewed-by \
+                        CC \
+                | tr ' ' '|')
+        readonly WIDTH_SUM=62
+        readonly WIDTH_REG=70
+        readonly JIRA_FMT_A="^[A-Z]\{2,5\}-[0-9]\{1,5\} [-a-z0-9]\{2,11\}: "
+        readonly JIRA_FMT_B="^[A-Z]\{2,5\}-[0-9]\{1,5\} "
 
-# Check for, and add if missing, a unique Change-Id
+        # 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 -t commit --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
+        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 lines at 70 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)
+       $TESTPARAMS optional additional test parameters
+       {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
-
-grep -q "^$CHANGEID" "$ORIGINAL" && HAS_CHANGEID=true || HAS_CHANGEID=false
-
-export LINE=""
-export NUM=1
-
-IFS=""
-while read LINE; do
-       LENGTH=$(echo $LINE | wc -c)
-
-       case "$LINE" in
-       $SIGNOFF*)
-               $HAS_SUMMARY || usage "missing summary before $SIGNOFF."
-               $HAS_BLANK || usage "missing blank line before $SIGNOFF."
-               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."
-               HAS_CHANGEID=true
-               echo $LINE
-               ;;
-       "")     [ $HAS_SUMMARY -a $NUM -eq 2 ] && HAS_BLANK=true
-               echo $LINE
-               ;;
-       \#*)    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")
-                       [ $LENGTH -gt 64 ] &&
-                               usage "summary longer than 64 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 [ $LENGTH -gt 70 ]; then
-                       usage "has lines longer than 70 columns."
-               fi
-               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  ;;
+        $TESTPARAMS* ) ck_wrapup   ;;
+
+        "")
+                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 ;;
+               "new file 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: