@@ -141,6 +141,100 @@ get_all_match_positions() {
141
141
done <<< " $targets"
142
142
}
143
143
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' )
192
+
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
+
144
238
# Validate the contents of the commmit msg agains the good commit guidelines.
145
239
validate_commit_message () {
146
240
# reset warnings
@@ -265,31 +359,35 @@ validate_commit_message() {
265
359
266
360
URL_REGEX=' ^[[:blank:]]*(https?|ftp|file)://[-A-Za-z0-9+&@#/%?=~_|!:,.;]*[-A-Za-z0-9+&@#/%=~_|]'
267
361
268
- # Ensure the commit message lines are loaded into an array.
269
- 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 "
270
364
271
- for i in " ${! COMMIT_MSG_LINES [@]} " ; do
272
- # Skip the first line (the subject) because the limit applies to the body.
273
- if [ " $i " -eq 0 ]; then
274
- continue
275
- 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
276
370
277
- LINE=" ${COMMIT_MSG_LINES[$i]} "
278
-
279
- # Skip the line if it is a comment.
280
- if [[ " $LINE " =~ ^[[:space:]]* # ]]; then
281
- continue
282
- fi
371
+ line=" ${commit_msg_lines[$i]} "
283
372
284
- # Trim leading and trailing whitespace.
285
- TRIMMED_LINE= " ${LINE# " ${LINE%% [![:space:]]* } " } "
286
- TRIMMED_LINE= " ${TRIMMED_LINE% " ${TRIMMED_LINE##* [![:space:]]} " } "
287
- LINE_NUMBER= $(( i+ 1 ))
288
-
289
- if [ " ${# TRIMMED_LINE} " -gt 72 ] && ! [[ " $TRIMMED_LINE " =~ $URL_REGEX ]]; then
290
- add_warning " $LINE_NUMBER " " Wrap the body at 72 characters (${# TRIMMED_LINE} chars)"
291
- fi
292
- 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
293
391
294
392
# 7. Ensure the commit subject has more than one word.
295
393
# ------------------------------------------------------------------------------
322
420
# 8. Use the body to explain what and why vs. how
323
421
# ------------------------------------------------------------------------------
324
422
325
- # Count non-comment, non-blank lines excluding "Change-Id:".
326
- 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)
327
426
328
427
# If queue.c is modified and the commit message is oversimplified, forbid generic subjects.
329
428
if git diff --cached --name-only | grep -Eq ' (^|/)queue\.c$' ; then
362
461
# 12. Avoid abusive language in commit message content
363
462
# ------------------------------------------------------------------------------
364
463
365
- FULL_COMMIT_MSG_WITH_SPACE=$( sed ' /^#/d;/^[[:space:]]*Change-Id:/d' " $COMMIT_MSG_FILE " | \
366
- 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" )
367
467
FULL_COMMIT_MSG=$( echo " $FULL_COMMIT_MSG_WITH_SPACE " | sed ' /^[[:space:]]*$/d' )
368
468
369
469
# Extended list of abusive words (case-insensitive).
388
488
-e " s/\bcommit[[:space:]]+[0-9a-fA-F]{7,40}\b/commit/g" )
389
489
MSG_FOR_SPELLCHECK=$( echo " $MSG_FOR_SPELLCHECK_LINE_FINDING " | sed ' /^[[:space:]]*$/d' )
390
490
391
-
392
491
# Use aspell to list misspelled words according to American English, ignoring quoted text.
393
492
MISSPELLED_WORDS=$( echo " $MSG_FOR_SPELLCHECK " | $ASPELL --lang=en --list --home-dir=scripts --personal=aspell-pws)
394
493
if [ -n " $MISSPELLED_WORDS " ]; then
@@ -562,6 +661,8 @@ set_colors
562
661
563
662
set_editor
564
663
664
+ build_commit_trailer_regex
665
+
565
666
if tty > /dev/null 2>&1 ; then
566
667
TTY=$( tty)
567
668
else
0 commit comments