xref: /f-stack/dpdk/devtools/check-git-log.sh (revision 2d9fd380)
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