12bfe3f2eSlogwang#! /bin/sh 2d30ea906Sjfb8856606# SPDX-License-Identifier: BSD-3-Clause 32bfe3f2eSlogwang# Copyright 2016 6WIND S.A. 42bfe3f2eSlogwang 52bfe3f2eSlogwang# Check commit logs (headlines and references) 62bfe3f2eSlogwang# 72bfe3f2eSlogwang# If any doubt about the formatting, please check in the most recent history: 82bfe3f2eSlogwang# git log --format='%>|(15)%cr %s' --reverse | grep -i <pattern> 92bfe3f2eSlogwang 10*2d9fd380Sjfb8856606print_usage () { 112bfe3f2eSlogwang cat <<- END_OF_HELP 12*2d9fd380Sjfb8856606 usage: $(basename $0) [-h] [-nX|-r range] 132bfe3f2eSlogwang 142bfe3f2eSlogwang Check commit log formatting. 15*2d9fd380Sjfb8856606 The git commits to be checked can be specified as a "git log" option, 16*2d9fd380Sjfb8856606 by latest git commits limited with -n option, or commits in the git 17*2d9fd380Sjfb8856606 range specified with -r option. 18*2d9fd380Sjfb8856606 e.g. To check only the last commit, ‘-n1’ or ‘-r@~..’ is used. 19*2d9fd380Sjfb8856606 If no range provided, default is origin/main..HEAD. 202bfe3f2eSlogwang END_OF_HELP 21*2d9fd380Sjfb8856606} 222bfe3f2eSlogwang 232bfe3f2eSlogwangselfdir=$(dirname $(readlink -f $0)) 24*2d9fd380Sjfb8856606# The script caters for two formats, the new preferred format, and the old 25*2d9fd380Sjfb8856606# format to ensure backward compatibility. 26*2d9fd380Sjfb8856606# The new format is aligned with the format of the checkpatches script, 27*2d9fd380Sjfb8856606# and allows for specifying the patches to check by passing -nX or -r range. 28*2d9fd380Sjfb8856606# The old format allows for specifying patches by passing -X or range 29*2d9fd380Sjfb8856606# as the first argument. 30*2d9fd380Sjfb8856606range=${1:-origin/main..} 31*2d9fd380Sjfb8856606 32*2d9fd380Sjfb8856606if [ "$range" = '--help' ] ; then 33*2d9fd380Sjfb8856606 print_usage 34*2d9fd380Sjfb8856606 exit 0 352bfe3f2eSlogwang# convert -N to HEAD~N.. in order to comply with git-log-fixes.sh getopts 36*2d9fd380Sjfb8856606elif printf -- "$range" | grep -q '^-[0-9]\+' ; then 37*2d9fd380Sjfb8856606 range="HEAD$(printf -- "$range" | sed 's,^-,~,').." 38*2d9fd380Sjfb8856606else 39*2d9fd380Sjfb8856606 while getopts hr:n: ARG ; do 40*2d9fd380Sjfb8856606 case $ARG in 41*2d9fd380Sjfb8856606 n ) range="HEAD~$OPTARG.." ;; 42*2d9fd380Sjfb8856606 r ) range=$OPTARG ;; 43*2d9fd380Sjfb8856606 h ) print_usage ; exit 0 ;; 44*2d9fd380Sjfb8856606 ? ) print_usage ; exit 1 ;; 45*2d9fd380Sjfb8856606 esac 46*2d9fd380Sjfb8856606 done 47*2d9fd380Sjfb8856606 shift $(($OPTIND - 1)) 482bfe3f2eSlogwangfi 492bfe3f2eSlogwang 502bfe3f2eSlogwangcommits=$(git log --format='%h' --reverse $range) 512bfe3f2eSlogwangheadlines=$(git log --format='%s' --reverse $range) 522bfe3f2eSlogwangbodylines=$(git log --format='%b' --reverse $range) 532bfe3f2eSlogwangfixes=$(git log --format='%h %s' --reverse $range | grep -i ': *fix' | cut -d' ' -f1) 542bfe3f2eSlogwangstablefixes=$($selfdir/git-log-fixes.sh $range | sed '/(N\/A)$/d' | cut -d' ' -f2) 552bfe3f2eSlogwangtags=$(git log --format='%b' --reverse $range | grep -i -e 'by *:' -e 'fix.*:') 562bfe3f2eSlogwangbytag='\(Reported\|Suggested\|Signed-off\|Acked\|Reviewed\|Tested\)-by:' 572bfe3f2eSlogwang 58*2d9fd380Sjfb8856606failure=false 59*2d9fd380Sjfb8856606 602bfe3f2eSlogwang# check headline format (spacing, no punctuation, no code) 612bfe3f2eSlogwangbad=$(echo "$headlines" | grep --color=always \ 622bfe3f2eSlogwang -e ' ' \ 632bfe3f2eSlogwang -e '^ ' \ 642bfe3f2eSlogwang -e ' $' \ 652bfe3f2eSlogwang -e '\.$' \ 662bfe3f2eSlogwang -e '[,;!?&|]' \ 672bfe3f2eSlogwang -e ':.*_' \ 682bfe3f2eSlogwang -e '^[^:]\+$' \ 692bfe3f2eSlogwang -e ':[^ ]' \ 702bfe3f2eSlogwang -e ' :' \ 712bfe3f2eSlogwang | sed 's,^,\t,') 72*2d9fd380Sjfb8856606[ -z "$bad" ] || { printf "Wrong headline format:\n$bad\n" && failure=true;} 732bfe3f2eSlogwang 742bfe3f2eSlogwang# check headline prefix when touching only drivers, e.g. net/<driver name> 752bfe3f2eSlogwangbad=$(for commit in $commits ; do 762bfe3f2eSlogwang headline=$(git log --format='%s' -1 $commit) 772bfe3f2eSlogwang files=$(git diff-tree --no-commit-id --name-only -r $commit) 782bfe3f2eSlogwang [ -z "$(echo "$files" | grep -v '^\(drivers\|doc\|config\)/')" ] || 792bfe3f2eSlogwang continue 802bfe3f2eSlogwang drv=$(echo "$files" | grep '^drivers/' | cut -d "/" -f 2,3 | sort -u) 812bfe3f2eSlogwang drvgrp=$(echo "$drv" | cut -d "/" -f 1 | uniq) 822bfe3f2eSlogwang if [ $(echo "$drvgrp" | wc -l) -gt 1 ] ; then 832bfe3f2eSlogwang echo "$headline" | grep -v '^drivers:' 842bfe3f2eSlogwang elif [ $(echo "$drv" | wc -l) -gt 1 ] ; then 852bfe3f2eSlogwang echo "$headline" | grep -v "^drivers/$drvgrp" 862bfe3f2eSlogwang else 872bfe3f2eSlogwang echo "$headline" | grep -v "^$drv" 882bfe3f2eSlogwang fi 892bfe3f2eSlogwangdone | sed 's,^,\t,') 90*2d9fd380Sjfb8856606[ -z "$bad" ] || { printf "Wrong headline prefix:\n$bad\n" && failure=true;} 912bfe3f2eSlogwang 922bfe3f2eSlogwang# check headline label for common typos 932bfe3f2eSlogwangbad=$(echo "$headlines" | grep --color=always \ 942bfe3f2eSlogwang -e '^example[:/]' \ 952bfe3f2eSlogwang -e '^apps/' \ 962bfe3f2eSlogwang -e '^testpmd' \ 972bfe3f2eSlogwang -e 'test-pmd' \ 982bfe3f2eSlogwang -e '^bond:' \ 992bfe3f2eSlogwang | sed 's,^,\t,') 100*2d9fd380Sjfb8856606[ -z "$bad" ] || { printf "Wrong headline label:\n$bad\n" && failure=true;} 1012bfe3f2eSlogwang 1022bfe3f2eSlogwang# check headline lowercase for first words 1032bfe3f2eSlogwangbad=$(echo "$headlines" | grep --color=always \ 104d30ea906Sjfb8856606 -e '^.*[[:upper:]].*:' \ 105d30ea906Sjfb8856606 -e ': *[[:upper:]]' \ 1062bfe3f2eSlogwang | sed 's,^,\t,') 107*2d9fd380Sjfb8856606[ -z "$bad" ] || { printf "Wrong headline uppercase:\n$bad\n" && failure=true;} 1082bfe3f2eSlogwang 109*2d9fd380Sjfb8856606# check headline case (Rx/Tx, VF, L2, MAC, Linux ...) 110*2d9fd380Sjfb8856606IFS=' 111*2d9fd380Sjfb8856606' 112*2d9fd380Sjfb8856606words="$selfdir/words-case.txt" 113*2d9fd380Sjfb8856606for word in $(cat $words); do 114*2d9fd380Sjfb8856606 bad=$(echo "$headlines" | grep -iw $word | grep -v $word) 115*2d9fd380Sjfb8856606 if [ "$word" = "Tx" ]; then 116*2d9fd380Sjfb8856606 bad=$(echo $bad | grep -v 'OCTEON\ TX') 117*2d9fd380Sjfb8856606 fi 118*2d9fd380Sjfb8856606 for bad_line in $bad; do 119*2d9fd380Sjfb8856606 bad_word=$(echo $bad_line | cut -d":" -f2 | grep -io $word) 120*2d9fd380Sjfb8856606 [ -z "$bad_word" ] || { printf "Wrong headline case:\n\ 121*2d9fd380Sjfb8856606 \"$bad_line\": $bad_word --> $word\n" && failure=true;} 122*2d9fd380Sjfb8856606 done 123*2d9fd380Sjfb8856606done 1242bfe3f2eSlogwang 1252bfe3f2eSlogwang# check headline length (60 max) 1262bfe3f2eSlogwangbad=$(echo "$headlines" | 1272bfe3f2eSlogwang awk 'length>60 {print}' | 1282bfe3f2eSlogwang sed 's,^,\t,') 129*2d9fd380Sjfb8856606[ -z "$bad" ] || { printf "Headline too long:\n$bad\n" && failure=true;} 1302bfe3f2eSlogwang 1312bfe3f2eSlogwang# check body lines length (75 max) 1322bfe3f2eSlogwangbad=$(echo "$bodylines" | grep -v '^Fixes:' | 1332bfe3f2eSlogwang awk 'length>75 {print}' | 1342bfe3f2eSlogwang sed 's,^,\t,') 135*2d9fd380Sjfb8856606[ -z "$bad" ] || { printf "Line too long:\n$bad\n" && failure=true;} 1362bfe3f2eSlogwang 1372bfe3f2eSlogwang# check starting commit message with "It" 1382bfe3f2eSlogwangbad=$(for commit in $commits ; do 1392bfe3f2eSlogwang firstbodyline=$(git log --format='%b' -1 $commit | head -n1) 1402bfe3f2eSlogwang echo "$firstbodyline" | grep --color=always -ie '^It ' 1412bfe3f2eSlogwangdone | sed 's,^,\t,') 142*2d9fd380Sjfb8856606[ -z "$bad" ] || { printf "Wrong beginning of commit message:\n$bad\n"\ 143*2d9fd380Sjfb8856606 && failure=true;} 1442bfe3f2eSlogwang 1452bfe3f2eSlogwang# check tags spelling 1462bfe3f2eSlogwangbad=$(echo "$tags" | 1472bfe3f2eSlogwang grep -v "^$bytag [^,]* <.*@.*>$" | 1482bfe3f2eSlogwang grep -v '^Fixes: [0-9a-f]\{7\}[0-9a-f]* (".*")$' | 1492bfe3f2eSlogwang sed 's,^.,\t&,') 150*2d9fd380Sjfb8856606[ -z "$bad" ] || { printf "Wrong tag:\n$bad\n" && failure=true;} 1512bfe3f2eSlogwang 1524418919fSjohnjiang# check missing Coverity issue: tag 1534418919fSjohnjiangbad=$(for commit in $commits; do 1544418919fSjohnjiang body=$(git log --format='%b' -1 $commit) 1554418919fSjohnjiang echo "$body" | grep -qi coverity || continue 1564418919fSjohnjiang echo "$body" | grep -q '^Coverity issue:' && continue 1574418919fSjohnjiang git log --format='\t%s' -1 $commit 1584418919fSjohnjiangdone) 159*2d9fd380Sjfb8856606[ -z "$bad" ] || { printf "Missing 'Coverity issue:' tag:\n$bad\n"\ 160*2d9fd380Sjfb8856606 && failure=true;} 1614418919fSjohnjiang 1624418919fSjohnjiang# check missing Bugzilla ID: tag 1634418919fSjohnjiangbad=$(for commit in $commits; do 1644418919fSjohnjiang body=$(git log --format='%b' -1 $commit) 1654418919fSjohnjiang echo "$body" | grep -qi bugzilla || continue 1664418919fSjohnjiang echo "$body" | grep -q '^Bugzilla ID:' && continue 1674418919fSjohnjiang git log --format='\t%s' -1 $commit 1684418919fSjohnjiangdone) 169*2d9fd380Sjfb8856606[ -z "$bad" ] || { printf "Missing 'Bugzilla ID:' tag:\n$bad\n"\ 170*2d9fd380Sjfb8856606 && failure=true;} 1714418919fSjohnjiang 1722bfe3f2eSlogwang# check missing Fixes: tag 1732bfe3f2eSlogwangbad=$(for fix in $fixes ; do 1742bfe3f2eSlogwang git log --format='%b' -1 $fix | grep -q '^Fixes: ' || 1752bfe3f2eSlogwang git log --format='\t%s' -1 $fix 1762bfe3f2eSlogwangdone) 177*2d9fd380Sjfb8856606[ -z "$bad" ] || { printf "Missing 'Fixes' tag:\n$bad\n" && failure=true;} 1782bfe3f2eSlogwang 1792bfe3f2eSlogwang# check Fixes: reference 1802bfe3f2eSlogwangfixtags=$(echo "$tags" | grep '^Fixes: ') 1812bfe3f2eSlogwangbad=$(for fixtag in $fixtags ; do 1822bfe3f2eSlogwang hash=$(echo "$fixtag" | sed 's,^Fixes: \([0-9a-f]*\).*,\1,') 1832bfe3f2eSlogwang if git branch --contains $hash 2>&- | grep -q '^\*' ; then 1842bfe3f2eSlogwang good="Fixes: $hash "$(git log --format='("%s")' -1 $hash 2>&-) 1852bfe3f2eSlogwang else 1862bfe3f2eSlogwang good="reference not in current branch" 1872bfe3f2eSlogwang fi 1882bfe3f2eSlogwang printf "$fixtag" | grep -v "^$good$" 1892bfe3f2eSlogwangdone | sed 's,^,\t,') 190*2d9fd380Sjfb8856606[ -z "$bad" ] || { printf "Wrong 'Fixes' reference:\n$bad\n" && failure=true;} 1912bfe3f2eSlogwang 1922bfe3f2eSlogwang# check Cc: [email protected] for fixes 1932bfe3f2eSlogwangbad=$(for fix in $stablefixes ; do 1942bfe3f2eSlogwang git log --format='%b' -1 $fix | grep -qi '^Cc: *[email protected]' || 1952bfe3f2eSlogwang git log --format='\t%s' -1 $fix 1962bfe3f2eSlogwangdone) 197*2d9fd380Sjfb8856606[ -z "$bad" ] || { printf "Is it candidate for Cc: [email protected] backport?\n$bad\n"\ 198*2d9fd380Sjfb8856606 && failure=true;} 199*2d9fd380Sjfb8856606 200*2d9fd380Sjfb8856606total=$(echo "$commits" | wc -l) 201*2d9fd380Sjfb8856606if $failure ; then 202*2d9fd380Sjfb8856606 printf "\nInvalid patch(es) found - checked $total patch" 203*2d9fd380Sjfb8856606else 204*2d9fd380Sjfb8856606 printf "\n$total/$total valid patch" 205*2d9fd380Sjfb8856606fi 206*2d9fd380Sjfb8856606[ $total -le 1 ] || printf 'es' 207*2d9fd380Sjfb8856606printf '\n' 208*2d9fd380Sjfb8856606$failure && exit 1 || exit 0 209