Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
179 changes: 128 additions & 51 deletions scripts/commit-msg.hook
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -62,20 +53,14 @@ EOF
)${NC}"
}

#
# Add a warning with <line_number> and <msg>.
#

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
Expand All @@ -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=()
Expand Down Expand Up @@ -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'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having only one of Close and Closes seems better for consistency.

Besides, do colon-seperated key-value pairs work? The GitHub document doesn't mention this and the examples are all seperated by spaces.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do colon-seperated key-value pairs work? The GitHub document doesn't mention this and the examples are all seperated by spaces.

Feel free to submit pull request(s) to refine git hooks.

'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=()
Expand Down Expand Up @@ -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.
# ------------------------------------------------------------------------------
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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).
Expand All @@ -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
Expand All @@ -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 '
Expand Down Expand Up @@ -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
Expand Down