@@ -18,10 +18,7 @@ WHITE=
1818CYAN=
1919NC=
2020
21- #
2221# Set colour variables if the output should be coloured.
23- #
24-
2522set_colors () {
2623 local default_color=$( git config --get hooks.goodcommit.color || git config --get color.ui || echo ' auto' )
2724 if [[ $default_color == ' always' ]] || [[ $default_color == ' auto' && -t 1 ]]; then
@@ -34,10 +31,7 @@ set_colors() {
3431 fi
3532}
3633
37- #
3834# Set the hook editor, using the same approach as git.
39- #
40-
4135set_editor () {
4236 # $GIT_EDITOR appears to always be set to `:` when the hook is executed by Git?
4337 # ref: http://stackoverflow.com/q/41468839/885540
@@ -49,10 +43,7 @@ set_editor() {
4943 test -z " ${HOOK_EDITOR} " && HOOK_EDITOR=' vi'
5044}
5145
52- #
5346# Output prompt help information.
54- #
55-
5647prompt_help () {
5748 echo -e " ${RED} $( cat << -EOF
5849e - edit commit message
6253) ${NC} "
6354}
6455
65- #
6656# Add a warning with <line_number> and <msg>.
67- #
68-
6957add_warning () {
7058 local line_number=$1
7159 local warning=$2
7260 WARNINGS[$line_number ]=" ${WARNINGS[$line_number]} $warning ;"
7361}
7462
75- #
7663# Output warnings.
77- #
78-
7964display_warnings () {
8065 if [ $SKIP_DISPLAY_WARNINGS -eq 1 ]; then
8166 # if the warnings were skipped then they should be displayed next time
9883) ${NC} "
9984}
10085
101- #
10286# Read the contents of the commit msg into an array of lines.
103- #
104-
10587read_commit_message () {
10688 # reset commit_msg_lines
10789 COMMIT_MSG_LINES=()
@@ -159,10 +141,101 @@ get_all_match_positions() {
159141 done <<< " $targets"
160142}
161143
162- #
163- # Validate the contents of the commmit msg agains the good commit guidelines.
164- #
144+ # Build regex for detecting commit trailers.
145+ build_commit_trailer_regex () {
146+ local -a keys specials standalones trailers
147+ local _ each key separators
148+
149+ # Get the trailer separators from git config (default to ':' if not set)
150+ separators=$( git config --get trailer.separators || echo ' :' )
151+
152+ # Predefined trailer keys.
153+ trailers=(
154+ ' CC' ' Change-Id'
155+ ' Bug' ' Close' ' Closes'
156+ ' Acked-by' ' Co-Authored-By' ' Reported-by' ' Reviewed-by'
157+ ' Signed-off-by' ' Suggested-by' ' Tested-by'
158+ )
159+
160+ # Standalone keys (those that do not require a value).
161+ standalones=(
162+ ' (Doc|Upgrade|Security)Impact'
163+ " Git-Dch[$separators ] (Ignore|Short|Full)"
164+ )
165+
166+ # Read custom trailer keys from git config and add them either to specials or trailers.
167+ # This loop reads lines matching 'trailer.*.key'.
168+ while read -r _ key; do
169+ # Skip if key already exists in trailers or specials.
170+ for each in " ${trailers[@]} " " ${specials[@]} " ; do
171+ if [ " $key " = " $each " ]; then
172+ continue 2
173+ fi
174+ done
175+ # If key ends with a separator character, add to specials; otherwise, to trailers.
176+ if [[ $key =~ [${separators} ]$ ]]; then
177+ specials+=(" $key " )
178+ else
179+ trailers+=(" $key " )
180+ fi
181+ done < <( git config --get-regexp ' trailer.*.key' )
182+
183+ # Read custom trailer keys again into the 'keys' array (if needed).
184+ while IFS=. read -r _ key _; do
185+ for each in " ${keys[@]} " ; do
186+ if [ " $key " = " $each " ]; then
187+ continue 2
188+ fi
189+ done
190+ keys+=(" $key " )
191+ done < <( git config --get-regexp ' trailer.*.key' )
165192
193+ # Begin constructing the regex.
194+ TRAILER_REGEX=' ^('
195+
196+ # Append trailer keys (with values).
197+ if (( ${# trailers[@]} > 0 )) ; then
198+ TRAILER_REGEX+=' (('
199+ for each in " ${trailers[@]} " ; do
200+ TRAILER_REGEX+=" $each |"
201+ done
202+ # Remove the trailing pipe, then add a separator and blank space pattern.
203+ TRAILER_REGEX=" ${TRAILER_REGEX% |} )[$separators ][[:blank:]]*)"
204+ fi
205+
206+ # Append standalone trailer keys.
207+ if (( ${# standalones[@]} > 0 )) ; then
208+ TRAILER_REGEX+=' |(('
209+ for each in " ${standalones[@]} " ; do
210+ TRAILER_REGEX+=" $each |"
211+ done
212+ TRAILER_REGEX=" ${TRAILER_REGEX% |} )$)"
213+ fi
214+
215+ # Append specials.
216+ if (( ${# specials[@]} > 0 )) ; then
217+ TRAILER_REGEX+=' |('
218+ for each in " ${specials[@]} " ; do
219+ TRAILER_REGEX+=" $each |"
220+ done
221+ TRAILER_REGEX=" ${TRAILER_REGEX% |} )"
222+ fi
223+
224+ # Append additional keys.
225+ if (( ${# keys[@]} > 0 )) ; then
226+ TRAILER_REGEX+=' |(('
227+ for each in " ${keys[@]} " ; do
228+ TRAILER_REGEX+=" $each |"
229+ done
230+ # Use the second character of separators (if available) as a separator for keys.
231+ TRAILER_REGEX=" ${TRAILER_REGEX% |} )[${separators: 1: 1} [:blank:]])"
232+ fi
233+
234+ # End the regex.
235+ TRAILER_REGEX+=" )"
236+ }
237+
238+ # Validate the contents of the commmit msg agains the good commit guidelines.
166239validate_commit_message () {
167240 # reset warnings
168241 WARNINGS=()
@@ -286,31 +359,35 @@ validate_commit_message() {
286359
287360 URL_REGEX=' ^[[:blank:]]*(https?|ftp|file)://[-A-Za-z0-9+&@#/%?=~_|!:,.;]*[-A-Za-z0-9+&@#/%=~_|]'
288361
289- # Ensure the commit message lines are loaded into an array.
290- readarray -t COMMIT_MSG_LINES < " $COMMIT_MSG_FILE "
362+ # Ensure the commit message lines are loaded into an array.
363+ readarray -t commit_msg_lines < " $COMMIT_MSG_FILE "
291364
292- for i in " ${! COMMIT_MSG_LINES [@]} " ; do
293- # Skip the first line (the subject) because the limit applies to the body.
294- if [ " $i " -eq 0 ]; then
295- continue
296- fi
365+ for i in " ${! commit_msg_lines [@]} " ; do
366+ # Skip the first line (the subject) since the limit applies to the body.
367+ if [ " $i " -eq 0 ]; then
368+ continue
369+ fi
297370
298- LINE=" ${COMMIT_MSG_LINES[$i]} "
299-
300- # Skip the line if it is a comment.
301- if [[ " $LINE " =~ ^[[:space:]]* # ]]; then
302- continue
303- fi
371+ line=" ${commit_msg_lines[$i]} "
304372
305- # Trim leading and trailing whitespace.
306- TRIMMED_LINE= " ${LINE# " ${LINE%% [![:space:]]* } " } "
307- TRIMMED_LINE= " ${TRIMMED_LINE% " ${TRIMMED_LINE##* [![:space:]]} " } "
308- LINE_NUMBER= $(( i+ 1 ))
309-
310- if [ " ${# TRIMMED_LINE} " -gt 72 ] && ! [[ " $TRIMMED_LINE " =~ $URL_REGEX ]]; then
311- add_warning " $LINE_NUMBER " " Wrap the body at 72 characters (${# TRIMMED_LINE} chars)"
312- fi
313- done
373+ # Skip lines that are comments.
374+ if [[ " $line " =~ ^[[:space:]]* # ]]; then
375+ continue
376+ fi
377+
378+ # Trim leading and trailing whitespace.
379+ trimmed_line= " ${line# " ${line%% [![:space:]]* } " } "
380+ trimmed_line= " ${trimmed_line% " ${trimmed_line##* [![:space:]]} " } "
381+ line_number= $(( i+ 1 ))
382+
383+ # Check if the trimmed line is longer than 72 characters and does not match a URL
384+ # or commit trailer. The URL regex is used inline by stripping its leading caret.
385+ if [ " ${# trimmed_line} " -gt 72 ] && \
386+ ! [[ " $trimmed_line " =~ ${URL_REGEX# ^} ]] && \
387+ ! [[ " $trimmed_line " =~ $TRAILER_REGEX ]]; then
388+ add_warning " $line_number " " Wrap the body at 72 characters (${# trimmed_line} chars)"
389+ fi
390+ done
314391
315392 # 7. Ensure the commit subject has more than one word.
316393 # ------------------------------------------------------------------------------
343420 # 8. Use the body to explain what and why vs. how
344421 # ------------------------------------------------------------------------------
345422
346- # Count non-comment, non-blank lines excluding "Change-Id:".
347- NON_COMMENT_COUNT= $( sed ' /^[[:space:]]*#/d;/^[[:space:]]*$/d;/^[[:space:]]*Change-Id:/d' " ${COMMIT_MSG_FILE} " | wc -l | xargs)
423+ # Count non-comment, non-blank lines, excluding lines that match the trailer regex.
424+ NON_COMMENT_COUNT= $( sed ' /^[[:space:]]*#/d;/^[[:space:]]*$/d' " ${COMMIT_MSG_FILE} " | \
425+ sed -E " /$TRAILER_REGEX /d" | wc -l | xargs)
348426
349427 # If queue.c is modified and the commit message is oversimplified, forbid generic subjects.
350428 if git diff --cached --name-only | grep -Eq ' (^|/)queue\.c$' ; then
383461 # 12. Avoid abusive language in commit message content
384462 # ------------------------------------------------------------------------------
385463
386- FULL_COMMIT_MSG_WITH_SPACE=$( sed ' /^#/d;/^[[:space:]]*Change-Id:/d' " $COMMIT_MSG_FILE " | \
387- sed -E " s@${URL_REGEX# ^} @@g" )
464+ # Remove comment lines, trailer lines, and URLs.
465+ FULL_COMMIT_MSG_WITH_SPACE=$( sed ' /^[[:space:]]*#/d' " $COMMIT_MSG_FILE " | \
466+ sed -E " /$TRAILER_REGEX /d" | sed -E " s@${URL_REGEX# ^} @@g" )
388467 FULL_COMMIT_MSG=$( echo " $FULL_COMMIT_MSG_WITH_SPACE " | sed ' /^[[:space:]]*$/d' )
389468
390469 # Extended list of abusive words (case-insensitive).
409488 -e " s/\bcommit[[:space:]]+[0-9a-fA-F]{7,40}\b/commit/g" )
410489 MSG_FOR_SPELLCHECK=$( echo " $MSG_FOR_SPELLCHECK_LINE_FINDING " | sed ' /^[[:space:]]*$/d' )
411490
412-
413491 # Use aspell to list misspelled words according to American English, ignoring quoted text.
414492 MISSPELLED_WORDS=$( echo " $MSG_FOR_SPELLCHECK " | $ASPELL --lang=en --list --home-dir=scripts --personal=aspell-pws)
415493 if [ -n " $MISSPELLED_WORDS " ]; then
@@ -427,7 +505,6 @@ CHANGE_ID_AFTER="Bug|Issue|Test"
427505MSG=" $1 "
428506
429507# Ensure that a unique Change-Id is present, and generate one if it is not.
430- #
431508# Partially taken from Gerrit Code Review 3.3.0-56-gbcecc47463
432509add_change_id () {
433510 clean_message=` sed -e '
@@ -578,14 +655,14 @@ _gen_changeid() {
578655 git hash-object -t commit --stdin
579656}
580657
581- #
582658# It's showtime.
583- #
584659
585660set_colors
586661
587662set_editor
588663
664+ build_commit_trailer_regex
665+
589666if tty > /dev/null 2>&1 ; then
590667 TTY=$( tty)
591668else
0 commit comments