19a98f50eSThomas Monjalon#! /bin/sh 2f83a3d3fSOlivier Matz# SPDX-License-Identifier: BSD-3-Clause 39a98f50eSThomas Monjalon# Copyright 2016 6WIND S.A. 49a98f50eSThomas Monjalon 59a98f50eSThomas Monjalon# Check commit logs (headlines and references) 69a98f50eSThomas Monjalon# 79a98f50eSThomas Monjalon# If any doubt about the formatting, please check in the most recent history: 89a98f50eSThomas Monjalon# git log --format='%>|(15)%cr %s' --reverse | grep -i <pattern> 99a98f50eSThomas Monjalon 10b1214d98SCiara Powerprint_usage () { 119a98f50eSThomas Monjalon cat <<- END_OF_HELP 12b1214d98SCiara Power usage: $(basename $0) [-h] [-nX|-r range] 139a98f50eSThomas Monjalon 149a98f50eSThomas Monjalon Check commit log formatting. 15b1214d98SCiara Power The git commits to be checked can be specified as a "git log" option, 16b1214d98SCiara Power by latest git commits limited with -n option, or commits in the git 17b1214d98SCiara Power range specified with -r option. 18b1214d98SCiara Power e.g. To check only the last commit, ‘-n1’ or ‘-r@~..’ is used. 19b9b10ddbSThomas Monjalon If no range provided, default is origin/main..HEAD. 209a98f50eSThomas Monjalon END_OF_HELP 21b1214d98SCiara Power} 229a98f50eSThomas Monjalon 23a3e34aa8SBruce Richardsonselfdir=$(dirname $(readlink -f $0)) 24b1214d98SCiara Power# The script caters for two formats, the new preferred format, and the old 25b1214d98SCiara Power# format to ensure backward compatibility. 26b1214d98SCiara Power# The new format is aligned with the format of the checkpatches script, 27b1214d98SCiara Power# and allows for specifying the patches to check by passing -nX or -r range. 28b1214d98SCiara Power# The old format allows for specifying patches by passing -X or range 29b1214d98SCiara Power# as the first argument. 30b9b10ddbSThomas Monjalonrange=${1:-origin/main..} 31b1214d98SCiara Power 32b1214d98SCiara Powerif [ "$range" = '--help' ] ; then 33b1214d98SCiara Power print_usage 34b1214d98SCiara Power exit 0 359a98f50eSThomas Monjalon# convert -N to HEAD~N.. in order to comply with git-log-fixes.sh getopts 36b1214d98SCiara Powerelif printf -- "$range" | grep -q '^-[0-9]\+' ; then 37b1214d98SCiara Power range="HEAD$(printf -- "$range" | sed 's,^-,~,').." 38b1214d98SCiara Powerelse 39b1214d98SCiara Power while getopts hr:n: ARG ; do 40b1214d98SCiara Power case $ARG in 41b1214d98SCiara Power n ) range="HEAD~$OPTARG.." ;; 42b1214d98SCiara Power r ) range=$OPTARG ;; 43b1214d98SCiara Power h ) print_usage ; exit 0 ;; 44b1214d98SCiara Power ? ) print_usage ; exit 1 ;; 45b1214d98SCiara Power esac 46b1214d98SCiara Power done 47b1214d98SCiara Power shift $(($OPTIND - 1)) 489a98f50eSThomas Monjalonfi 499a98f50eSThomas Monjalon 509a98f50eSThomas Monjaloncommits=$(git log --format='%h' --reverse $range) 519a98f50eSThomas Monjalonheadlines=$(git log --format='%s' --reverse $range) 529a98f50eSThomas Monjalonbodylines=$(git log --format='%b' --reverse $range) 539a98f50eSThomas Monjalonfixes=$(git log --format='%h %s' --reverse $range | grep -i ': *fix' | cut -d' ' -f1) 549a98f50eSThomas Monjalonstablefixes=$($selfdir/git-log-fixes.sh $range | sed '/(N\/A)$/d' | cut -d' ' -f2) 559a98f50eSThomas Monjalontags=$(git log --format='%b' --reverse $range | grep -i -e 'by *:' -e 'fix.*:') 569a98f50eSThomas Monjalonbytag='\(Reported\|Suggested\|Signed-off\|Acked\|Reviewed\|Tested\)-by:' 579a98f50eSThomas Monjalon 58a8354c99SCiara Powerfailure=false 59a8354c99SCiara Power 609a98f50eSThomas Monjalon# check headline format (spacing, no punctuation, no code) 619a98f50eSThomas Monjalonbad=$(echo "$headlines" | grep --color=always \ 629a98f50eSThomas Monjalon -e ' ' \ 639a98f50eSThomas Monjalon -e '^ ' \ 649a98f50eSThomas Monjalon -e ' $' \ 659a98f50eSThomas Monjalon -e '\.$' \ 669a98f50eSThomas Monjalon -e '[,;!?&|]' \ 679a98f50eSThomas Monjalon -e ':.*_' \ 689a98f50eSThomas Monjalon -e '^[^:]\+$' \ 699a98f50eSThomas Monjalon -e ':[^ ]' \ 709a98f50eSThomas Monjalon -e ' :' \ 719a98f50eSThomas Monjalon | sed 's,^,\t,') 72a8354c99SCiara Power[ -z "$bad" ] || { printf "Wrong headline format:\n$bad\n" && failure=true;} 739a98f50eSThomas Monjalon 749a98f50eSThomas Monjalon# check headline prefix when touching only drivers, e.g. net/<driver name> 759a98f50eSThomas Monjalonbad=$(for commit in $commits ; do 769a98f50eSThomas Monjalon headline=$(git log --format='%s' -1 $commit) 779a98f50eSThomas Monjalon files=$(git diff-tree --no-commit-id --name-only -r $commit) 789a98f50eSThomas Monjalon [ -z "$(echo "$files" | grep -v '^\(drivers\|doc\|config\)/')" ] || 799a98f50eSThomas Monjalon continue 809a98f50eSThomas Monjalon drv=$(echo "$files" | grep '^drivers/' | cut -d "/" -f 2,3 | sort -u) 819a98f50eSThomas Monjalon drvgrp=$(echo "$drv" | cut -d "/" -f 1 | uniq) 829a98f50eSThomas Monjalon if [ $(echo "$drvgrp" | wc -l) -gt 1 ] ; then 839a98f50eSThomas Monjalon echo "$headline" | grep -v '^drivers:' 849a98f50eSThomas Monjalon elif [ $(echo "$drv" | wc -l) -gt 1 ] ; then 8587acacf8SFerruh Yigit echo "$headline" | grep -v "^drivers/$drvgrp" 869a98f50eSThomas Monjalon else 879a98f50eSThomas Monjalon echo "$headline" | grep -v "^$drv" 889a98f50eSThomas Monjalon fi 899a98f50eSThomas Monjalondone | sed 's,^,\t,') 90a8354c99SCiara Power[ -z "$bad" ] || { printf "Wrong headline prefix:\n$bad\n" && failure=true;} 919a98f50eSThomas Monjalon 92*807274d5SDavid Marchand# check headline prefix for libraries 93*807274d5SDavid Marchandbad=$(echo "$headlines" | grep --color=always \ 94*807274d5SDavid Marchand -e '^lib/' \ 95*807274d5SDavid Marchand | sed 's,^,\t,') 96*807274d5SDavid Marchand[ -z "$bad" ] || { printf "Wrong headline prefix:\n$bad\n" && failure=true;} 97*807274d5SDavid Marchand 989a98f50eSThomas Monjalon# check headline label for common typos 999a98f50eSThomas Monjalonbad=$(echo "$headlines" | grep --color=always \ 1009a98f50eSThomas Monjalon -e '^example[:/]' \ 1019a98f50eSThomas Monjalon -e '^apps/' \ 1029a98f50eSThomas Monjalon -e '^testpmd' \ 1039a98f50eSThomas Monjalon -e 'test-pmd' \ 1049a98f50eSThomas Monjalon -e '^bond:' \ 1059a98f50eSThomas Monjalon | sed 's,^,\t,') 106a8354c99SCiara Power[ -z "$bad" ] || { printf "Wrong headline label:\n$bad\n" && failure=true;} 1079a98f50eSThomas Monjalon 1089a98f50eSThomas Monjalon# check headline lowercase for first words 1099a98f50eSThomas Monjalonbad=$(echo "$headlines" | grep --color=always \ 1105831a219SAndy Green -e '^.*[[:upper:]].*:' \ 1115831a219SAndy Green -e ': *[[:upper:]]' \ 1129a98f50eSThomas Monjalon | sed 's,^,\t,') 113a8354c99SCiara Power[ -z "$bad" ] || { printf "Wrong headline uppercase:\n$bad\n" && failure=true;} 1149a98f50eSThomas Monjalon 115d448efa2SSean Morrissey# check headline case (Rx/Tx, VF, L2, MAC, Linux ...) 116d448efa2SSean MorrisseyIFS=' 117d448efa2SSean Morrissey' 118d448efa2SSean Morrisseywords="$selfdir/words-case.txt" 119d448efa2SSean Morrisseyfor word in $(cat $words); do 1208b51fbc0SThomas Monjalon bad=$(echo "$headlines" | grep -iw $word | grep -vw $word) 121d448efa2SSean Morrissey if [ "$word" = "Tx" ]; then 122d448efa2SSean Morrissey bad=$(echo $bad | grep -v 'OCTEON\ TX') 123d448efa2SSean Morrissey fi 124d448efa2SSean Morrissey for bad_line in $bad; do 1258b51fbc0SThomas Monjalon bad_word=$(echo $bad_line | cut -d":" -f2 | grep -iwo $word) 126a8354c99SCiara Power [ -z "$bad_word" ] || { printf "Wrong headline case:\n\ 127a8354c99SCiara Power \"$bad_line\": $bad_word --> $word\n" && failure=true;} 128d448efa2SSean Morrissey done 129d448efa2SSean Morrisseydone 1309a98f50eSThomas Monjalon 1319a98f50eSThomas Monjalon# check headline length (60 max) 1329a98f50eSThomas Monjalonbad=$(echo "$headlines" | 1339a98f50eSThomas Monjalon awk 'length>60 {print}' | 1349a98f50eSThomas Monjalon sed 's,^,\t,') 135a8354c99SCiara Power[ -z "$bad" ] || { printf "Headline too long:\n$bad\n" && failure=true;} 1369a98f50eSThomas Monjalon 1379a98f50eSThomas Monjalon# check body lines length (75 max) 1389a98f50eSThomas Monjalonbad=$(echo "$bodylines" | grep -v '^Fixes:' | 1399a98f50eSThomas Monjalon awk 'length>75 {print}' | 1409a98f50eSThomas Monjalon sed 's,^,\t,') 141a8354c99SCiara Power[ -z "$bad" ] || { printf "Line too long:\n$bad\n" && failure=true;} 1429a98f50eSThomas Monjalon 1439a98f50eSThomas Monjalon# check starting commit message with "It" 1449a98f50eSThomas Monjalonbad=$(for commit in $commits ; do 1459a98f50eSThomas Monjalon firstbodyline=$(git log --format='%b' -1 $commit | head -n1) 1469a98f50eSThomas Monjalon echo "$firstbodyline" | grep --color=always -ie '^It ' 1479a98f50eSThomas Monjalondone | sed 's,^,\t,') 148a8354c99SCiara Power[ -z "$bad" ] || { printf "Wrong beginning of commit message:\n$bad\n"\ 149a8354c99SCiara Power && failure=true;} 1509a98f50eSThomas Monjalon 1519a98f50eSThomas Monjalon# check tags spelling 1529a98f50eSThomas Monjalonbad=$(echo "$tags" | 1539a98f50eSThomas Monjalon grep -v "^$bytag [^,]* <.*@.*>$" | 1549a98f50eSThomas Monjalon grep -v '^Fixes: [0-9a-f]\{7\}[0-9a-f]* (".*")$' | 1559a98f50eSThomas Monjalon sed 's,^.,\t&,') 156a8354c99SCiara Power[ -z "$bad" ] || { printf "Wrong tag:\n$bad\n" && failure=true;} 1579a98f50eSThomas Monjalon 1587a8735e2SDavid Marchand# check missing Coverity issue: tag 1597a8735e2SDavid Marchandbad=$(for commit in $commits; do 1607a8735e2SDavid Marchand body=$(git log --format='%b' -1 $commit) 1617a8735e2SDavid Marchand echo "$body" | grep -qi coverity || continue 1627a8735e2SDavid Marchand echo "$body" | grep -q '^Coverity issue:' && continue 1637a8735e2SDavid Marchand git log --format='\t%s' -1 $commit 1647a8735e2SDavid Marchanddone) 165a8354c99SCiara Power[ -z "$bad" ] || { printf "Missing 'Coverity issue:' tag:\n$bad\n"\ 166a8354c99SCiara Power && failure=true;} 1677a8735e2SDavid Marchand 1687a8735e2SDavid Marchand# check missing Bugzilla ID: tag 1697a8735e2SDavid Marchandbad=$(for commit in $commits; do 1707a8735e2SDavid Marchand body=$(git log --format='%b' -1 $commit) 1717a8735e2SDavid Marchand echo "$body" | grep -qi bugzilla || continue 1727a8735e2SDavid Marchand echo "$body" | grep -q '^Bugzilla ID:' && continue 1737a8735e2SDavid Marchand git log --format='\t%s' -1 $commit 1747a8735e2SDavid Marchanddone) 175a8354c99SCiara Power[ -z "$bad" ] || { printf "Missing 'Bugzilla ID:' tag:\n$bad\n"\ 176a8354c99SCiara Power && failure=true;} 1777a8735e2SDavid Marchand 1789a98f50eSThomas Monjalon# check missing Fixes: tag 1799a98f50eSThomas Monjalonbad=$(for fix in $fixes ; do 1809a98f50eSThomas Monjalon git log --format='%b' -1 $fix | grep -q '^Fixes: ' || 1819a98f50eSThomas Monjalon git log --format='\t%s' -1 $fix 1829a98f50eSThomas Monjalondone) 183a8354c99SCiara Power[ -z "$bad" ] || { printf "Missing 'Fixes' tag:\n$bad\n" && failure=true;} 1849a98f50eSThomas Monjalon 1859a98f50eSThomas Monjalon# check Fixes: reference 1869a98f50eSThomas Monjalonfixtags=$(echo "$tags" | grep '^Fixes: ') 1879a98f50eSThomas Monjalonbad=$(for fixtag in $fixtags ; do 1889a98f50eSThomas Monjalon hash=$(echo "$fixtag" | sed 's,^Fixes: \([0-9a-f]*\).*,\1,') 1899a98f50eSThomas Monjalon if git branch --contains $hash 2>&- | grep -q '^\*' ; then 1909a98f50eSThomas Monjalon good="Fixes: $hash "$(git log --format='("%s")' -1 $hash 2>&-) 1919a98f50eSThomas Monjalon else 1929a98f50eSThomas Monjalon good="reference not in current branch" 1939a98f50eSThomas Monjalon fi 1949a98f50eSThomas Monjalon printf "$fixtag" | grep -v "^$good$" 1959a98f50eSThomas Monjalondone | sed 's,^,\t,') 196a8354c99SCiara Power[ -z "$bad" ] || { printf "Wrong 'Fixes' reference:\n$bad\n" && failure=true;} 1979a98f50eSThomas Monjalon 19892fdc232SThomas Monjalon# check Cc: [email protected] for fixes 1999a98f50eSThomas Monjalonbad=$(for fix in $stablefixes ; do 20092fdc232SThomas Monjalon git log --format='%b' -1 $fix | grep -qi '^Cc: *[email protected]' || 2019a98f50eSThomas Monjalon git log --format='\t%s' -1 $fix 2029a98f50eSThomas Monjalondone) 203a8354c99SCiara Power[ -z "$bad" ] || { printf "Is it candidate for Cc: [email protected] backport?\n$bad\n"\ 204a8354c99SCiara Power && failure=true;} 205a8354c99SCiara Power 206a8354c99SCiara Powertotal=$(echo "$commits" | wc -l) 207a8354c99SCiara Powerif $failure ; then 208a8354c99SCiara Power printf "\nInvalid patch(es) found - checked $total patch" 209a8354c99SCiara Powerelse 210a8354c99SCiara Power printf "\n$total/$total valid patch" 211a8354c99SCiara Powerfi 212a8354c99SCiara Power[ $total -le 1 ] || printf 'es' 213a8354c99SCiara Powerprintf '\n' 214a8354c99SCiara Power$failure && exit 1 || exit 0 215