diff --git a/scripts/commit-msg.hook b/scripts/commit-msg.hook index d1863d085..dca3ee1cd 100755 --- a/scripts/commit-msg.hook +++ b/scripts/commit-msg.hook @@ -18,10 +18,7 @@ WHITE= CYAN= NC= -# # Set colour variables if the output should be coloured. -# - set_colors() { local default_color=$(git config --get hooks.goodcommit.color || git config --get color.ui || echo 'auto') if [[ $default_color == 'always' ]] || [[ $default_color == 'auto' && -t 1 ]]; then @@ -34,10 +31,7 @@ set_colors() { fi } -# # Set the hook editor, using the same approach as git. -# - set_editor() { # $GIT_EDITOR appears to always be set to `:` when the hook is executed by Git? # ref: http://stackoverflow.com/q/41468839/885540 @@ -49,10 +43,7 @@ set_editor() { test -z "${HOOK_EDITOR}" && HOOK_EDITOR='vi' } -# # Output prompt help information. -# - prompt_help() { echo -e "${RED}$(cat <<-EOF e - edit commit message @@ -62,20 +53,14 @@ EOF )${NC}" } -# # Add a warning with and . -# - add_warning() { local line_number=$1 local warning=$2 WARNINGS[$line_number]="${WARNINGS[$line_number]}$warning;" } -# # Output warnings. -# - display_warnings() { if [ $SKIP_DISPLAY_WARNINGS -eq 1 ]; then # if the warnings were skipped then they should be displayed next time @@ -98,10 +83,7 @@ EOF )${NC}" } -# # Read the contents of the commit msg into an array of lines. -# - read_commit_message() { # reset commit_msg_lines COMMIT_MSG_LINES=() @@ -159,10 +141,101 @@ get_all_match_positions() { done <<< "$targets" } -# -# Validate the contents of the commmit msg agains the good commit guidelines. -# +# Build regex for detecting commit trailers. +build_commit_trailer_regex() { + local -a keys specials standalones trailers + local _ each key separators + + # Get the trailer separators from git config (default to ':' if not set) + separators=$(git config --get trailer.separators || echo ':') + + # Predefined trailer keys. + trailers=( + 'CC' 'Change-Id' + 'Bug' 'Close' 'Closes' + 'Acked-by' 'Co-Authored-By' 'Reported-by' 'Reviewed-by' + 'Signed-off-by' 'Suggested-by' 'Tested-by' + ) + + # Standalone keys (those that do not require a value). + standalones=( + '(Doc|Upgrade|Security)Impact' + "Git-Dch[$separators] (Ignore|Short|Full)" + ) + + # Read custom trailer keys from git config and add them either to specials or trailers. + # This loop reads lines matching 'trailer.*.key'. + while read -r _ key; do + # Skip if key already exists in trailers or specials. + for each in "${trailers[@]}" "${specials[@]}"; do + if [ "$key" = "$each" ]; then + continue 2 + fi + done + # If key ends with a separator character, add to specials; otherwise, to trailers. + if [[ $key =~ [${separators}]$ ]]; then + specials+=("$key") + else + trailers+=("$key") + fi + done < <(git config --get-regexp 'trailer.*.key') + + # Read custom trailer keys again into the 'keys' array (if needed). + while IFS=. read -r _ key _; do + for each in "${keys[@]}"; do + if [ "$key" = "$each" ]; then + continue 2 + fi + done + keys+=("$key") + done < <(git config --get-regexp 'trailer.*.key') + # Begin constructing the regex. + TRAILER_REGEX='^(' + + # Append trailer keys (with values). + if ((${#trailers[@]} > 0)); then + TRAILER_REGEX+='((' + for each in "${trailers[@]}"; do + TRAILER_REGEX+="$each|" + done + # Remove the trailing pipe, then add a separator and blank space pattern. + TRAILER_REGEX="${TRAILER_REGEX%|})[$separators][[:blank:]]*)" + fi + + # Append standalone trailer keys. + if ((${#standalones[@]} > 0)); then + TRAILER_REGEX+='|((' + for each in "${standalones[@]}"; do + TRAILER_REGEX+="$each|" + done + TRAILER_REGEX="${TRAILER_REGEX%|})$)" + fi + + # Append specials. + if ((${#specials[@]} > 0)); then + TRAILER_REGEX+='|(' + for each in "${specials[@]}"; do + TRAILER_REGEX+="$each|" + done + TRAILER_REGEX="${TRAILER_REGEX%|})" + fi + + # Append additional keys. + if ((${#keys[@]} > 0)); then + TRAILER_REGEX+='|((' + for each in "${keys[@]}"; do + TRAILER_REGEX+="$each|" + done + # Use the second character of separators (if available) as a separator for keys. + TRAILER_REGEX="${TRAILER_REGEX%|})[${separators:1:1}[:blank:]])" + fi + + # End the regex. + TRAILER_REGEX+=")" +} + +# Validate the contents of the commmit msg agains the good commit guidelines. validate_commit_message() { # reset warnings WARNINGS=() @@ -286,31 +359,35 @@ validate_commit_message() { URL_REGEX='^[[:blank:]]*(https?|ftp|file)://[-A-Za-z0-9+&@#/%?=~_|!:,.;]*[-A-Za-z0-9+&@#/%=~_|]' -# Ensure the commit message lines are loaded into an array. -readarray -t COMMIT_MSG_LINES < "$COMMIT_MSG_FILE" + # Ensure the commit message lines are loaded into an array. + readarray -t commit_msg_lines < "$COMMIT_MSG_FILE" -for i in "${!COMMIT_MSG_LINES[@]}"; do - # Skip the first line (the subject) because the limit applies to the body. - if [ "$i" -eq 0 ]; then - continue - fi + for i in "${!commit_msg_lines[@]}"; do + # Skip the first line (the subject) since the limit applies to the body. + if [ "$i" -eq 0 ]; then + continue + fi - LINE="${COMMIT_MSG_LINES[$i]}" - - # Skip the line if it is a comment. - if [[ "$LINE" =~ ^[[:space:]]*# ]]; then - continue - fi + line="${commit_msg_lines[$i]}" - # Trim leading and trailing whitespace. - TRIMMED_LINE="${LINE#"${LINE%%[![:space:]]*}"}" - TRIMMED_LINE="${TRIMMED_LINE%"${TRIMMED_LINE##*[![:space:]]}"}" - LINE_NUMBER=$((i+1)) - - if [ "${#TRIMMED_LINE}" -gt 72 ] && ! [[ "$TRIMMED_LINE" =~ $URL_REGEX ]]; then - add_warning "$LINE_NUMBER" "Wrap the body at 72 characters (${#TRIMMED_LINE} chars)" - fi -done + # Skip lines that are comments. + if [[ "$line" =~ ^[[:space:]]*# ]]; then + continue + fi + + # Trim leading and trailing whitespace. + trimmed_line="${line#"${line%%[![:space:]]*}"}" + trimmed_line="${trimmed_line%"${trimmed_line##*[![:space:]]}"}" + line_number=$((i+1)) + + # Check if the trimmed line is longer than 72 characters and does not match a URL + # or commit trailer. The URL regex is used inline by stripping its leading caret. + if [ "${#trimmed_line}" -gt 72 ] && \ + ! [[ "$trimmed_line" =~ ${URL_REGEX#^} ]] && \ + ! [[ "$trimmed_line" =~ $TRAILER_REGEX ]]; then + add_warning "$line_number" "Wrap the body at 72 characters (${#trimmed_line} chars)" + fi + done # 7. Ensure the commit subject has more than one word. # ------------------------------------------------------------------------------ @@ -343,8 +420,9 @@ done # 8. Use the body to explain what and why vs. how # ------------------------------------------------------------------------------ - # Count non-comment, non-blank lines excluding "Change-Id:". - NON_COMMENT_COUNT=$(sed '/^[[:space:]]*#/d;/^[[:space:]]*$/d;/^[[:space:]]*Change-Id:/d' "${COMMIT_MSG_FILE}" | wc -l | xargs) + # Count non-comment, non-blank lines, excluding lines that match the trailer regex. + NON_COMMENT_COUNT=$(sed '/^[[:space:]]*#/d;/^[[:space:]]*$/d' "${COMMIT_MSG_FILE}" | \ + sed -E "/$TRAILER_REGEX/d" | wc -l | xargs) # If queue.c is modified and the commit message is oversimplified, forbid generic subjects. if git diff --cached --name-only | grep -Eq '(^|/)queue\.c$'; then @@ -383,8 +461,9 @@ done # 12. Avoid abusive language in commit message content # ------------------------------------------------------------------------------ - FULL_COMMIT_MSG_WITH_SPACE=$(sed '/^#/d;/^[[:space:]]*Change-Id:/d' "$COMMIT_MSG_FILE" | \ - sed -E "s@${URL_REGEX#^}@@g") + # Remove comment lines, trailer lines, and URLs. + FULL_COMMIT_MSG_WITH_SPACE=$(sed '/^[[:space:]]*#/d' "$COMMIT_MSG_FILE" | \ + sed -E "/$TRAILER_REGEX/d" | sed -E "s@${URL_REGEX#^}@@g") FULL_COMMIT_MSG=$(echo "$FULL_COMMIT_MSG_WITH_SPACE" | sed '/^[[:space:]]*$/d') # Extended list of abusive words (case-insensitive). @@ -409,7 +488,6 @@ done -e "s/\bcommit[[:space:]]+[0-9a-fA-F]{7,40}\b/commit/g") MSG_FOR_SPELLCHECK=$(echo "$MSG_FOR_SPELLCHECK_LINE_FINDING" | sed '/^[[:space:]]*$/d') - # Use aspell to list misspelled words according to American English, ignoring quoted text. MISSPELLED_WORDS=$(echo "$MSG_FOR_SPELLCHECK" | $ASPELL --lang=en --list --home-dir=scripts --personal=aspell-pws) if [ -n "$MISSPELLED_WORDS" ]; then @@ -427,7 +505,6 @@ CHANGE_ID_AFTER="Bug|Issue|Test" MSG="$1" # Ensure that a unique Change-Id is present, and generate one if it is not. -# # Partially taken from Gerrit Code Review 3.3.0-56-gbcecc47463 add_change_id() { clean_message=`sed -e ' @@ -578,14 +655,14 @@ _gen_changeid() { git hash-object -t commit --stdin } -# # It's showtime. -# set_colors set_editor +build_commit_trailer_regex + if tty >/dev/null 2>&1; then TTY=$(tty) else