xref: /dpdk/devtools/check-git-log.sh (revision 807274d5)
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