1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297
|
#! /bin/sh
# SPDX-License-Identifier: BSD-3-Clause
# Copyright 2016 6WIND S.A.
# Check commit logs (headlines and references)
#
# If any doubt about the formatting, please check in the most recent history:
# git log --format='%>|(15)%cr %s' --reverse | grep -i <pattern>
print_usage () {
cat <<- END_OF_HELP
usage: $(basename $0) [-h] [-nX|-r range]
Check commit log formatting.
The git commits to be checked can be specified as a "git log" option,
by latest git commits limited with -n option, or commits in the git
range specified with -r option.
e.g. To check only the last commit, ‘-n1’ or ‘-r@~..’ is used.
If no range provided, default is origin/main..HEAD.
END_OF_HELP
}
selfdir=$(dirname $(readlink -f $0))
# The script caters for two formats, the new preferred format, and the old
# format to ensure backward compatibility.
# The new format is aligned with the format of the checkpatches script,
# and allows for specifying the patches to check by passing -nX or -r range.
# The old format allows for specifying patches by passing -X or range
# as the first argument.
range=${1:-origin/main..}
if [ "$range" = '--help' ] ; then
print_usage
exit 0
# convert -N to HEAD~N.. in order to comply with git-log-fixes.sh getopts
elif printf -- "$range" | grep -q '^-[0-9]\+' ; then
range="HEAD$(printf -- "$range" | sed 's,^-,~,').."
else
while getopts hr:n: ARG ; do
case $ARG in
n ) range="HEAD~$OPTARG.." ;;
r ) range=$OPTARG ;;
h ) print_usage ; exit 0 ;;
? ) print_usage ; exit 1 ;;
esac
done
shift $(($OPTIND - 1))
fi
commits=$(git log --format='%h' --reverse $range)
headlines=$(git log --format='%s' --reverse $range)
bodylines=$(git log --format='%b' --reverse $range)
fixes=$(git log --format='%h %s' --reverse $range | grep -i ': *fix' | cut -d' ' -f1)
stablefixes=$($selfdir/git-log-fixes.sh $range | sed '/(N\/A)$/d' | cut -d' ' -f2)
tags=$(git log --format='%b' --reverse $range | grep -i -e 'by *:' -e 'fix.*:')
bytag='\(Reported\|Suggested\|Signed-off\|Acked\|Reviewed\|Tested\)-by:'
reltag='Coverity issue:\|Bugzilla ID:\|Fixes:\|Cc:'
failure=false
# check headline format (spacing, no punctuation, no code)
bad=$(echo "$headlines" | grep --color=always \
-e ' ' \
-e '^ ' \
-e ' $' \
-e '\.$' \
-e '[,;!?&|]' \
-e ':.*_' \
-e '^[^:]\+$' \
-e ':[^ ]' \
-e ' :' \
| sed 's,^,\t,')
[ -z "$bad" ] || { printf "Wrong headline format:\n$bad\n" && failure=true;}
# check headline prefix when touching only drivers, e.g. net/<driver name>
bad=$(for commit in $commits ; do
headline=$(git log --format='%s' -1 $commit)
files=$(git diff-tree --no-commit-id --name-only -r $commit)
[ -z "$(echo "$files" | grep -v '^\(drivers\|doc\|config\)/')" ] ||
continue
drv=$(echo "$files" | grep '^drivers/' | cut -d "/" -f 2,3 | sort -u)
drvgrp=$(echo "$drv" | cut -d "/" -f 1 | uniq)
if [ "$drv" = "net/intel" ] ; then
drvgrp=$drv
drv=$(echo "$files" | grep '^drivers/' | cut -d "/" -f 2,4 | sort -u)
if [ $(echo "$drv" | wc -l) -ne 1 ] ; then
drv='net/intel'
elif [ "$drv" = "net/common" ] ; then
drv='net/intel/common'
fi
fi
if [ $(echo "$drvgrp" | wc -l) -gt 1 ] ; then
prefix='drivers:'
elif [ $(echo "$drv" | wc -l) -gt 1 ] ; then
prefix="drivers/$drvgrp:"
else
prefix="$drv"
fi
echo "$headline (expected prefix \"$prefix\")" | grep -v "^$prefix"
done | sed 's,^,\t,')
[ -z "$bad" ] || { printf "Wrong headline prefix:\n$bad\n" && failure=true;}
# check headline prefix for libraries
bad=$(echo "$headlines" | grep --color=always \
-e '^lib/' \
| sed 's,^,\t,')
[ -z "$bad" ] || { printf "Wrong headline prefix:\n$bad\n" && failure=true;}
# check headline label for common typos
bad=$(echo "$headlines" | grep --color=always \
-e '^example[:/]' \
-e '^apps/' \
-e '^testpmd' \
-e 'test-pmd' \
-e '^bond:' \
| sed 's,^,\t,')
[ -z "$bad" ] || { printf "Wrong headline label:\n$bad\n" && failure=true;}
# check headline lowercase for first words
bad=$(echo "$headlines" | grep --color=always \
-e '^.*[[:upper:]].*:' \
-e ': *[[:upper:]]' \
| sed 's,^,\t,')
[ -z "$bad" ] || { printf "Wrong headline uppercase:\n$bad\n" && failure=true;}
# check headline case (Rx/Tx, VF, L2, MAC, Linux ...)
IFS='
'
words="$selfdir/words-case.txt"
for word in $(cat $words); do
bad=$(echo "$headlines" | grep -iw $word | grep -vw $word)
if [ "$word" = "Tx" ]; then
bad=$(echo $bad | grep -v 'OCTEON TX')
fi
for bad_line in $bad; do
bad_word=$(echo $bad_line | cut -d":" -f2 | grep -iwo $word)
[ -z "$bad_word" ] || { printf "Wrong headline case:\n\
\"$bad_line\": $bad_word --> $word\n" && failure=true;}
done
done
# check headline length (60 max)
bad=$(echo "$headlines" |
awk 'length>60 {print}' |
sed 's,^,\t,')
[ -z "$bad" ] || { printf "Headline too long:\n$bad\n" && failure=true;}
# check body lines length (75 max)
bad=$(echo "$bodylines" | grep -v '^Fixes:' |
awk 'length>75 {print}' |
sed 's,^,\t,')
[ -z "$bad" ] || { printf "Line too long:\n$bad\n" && failure=true;}
# check starting commit message with "It"
bad=$(for commit in $commits ; do
firstbodyline=$(git log --format='%b' -1 $commit | head -n1)
echo "$firstbodyline" | grep --color=always -ie '^It '
done | sed 's,^,\t,')
[ -z "$bad" ] || { printf "Wrong beginning of commit message:\n$bad\n"\
&& failure=true;}
# check tags spelling
bad=$(echo "$tags" |
grep -v "^$bytag [^,]* <.*@.*>$" |
grep -v '^Fixes: [0-9a-f]\{7\}[0-9a-f]* (".*")$' |
sed 's,^.,\t&,')
[ -z "$bad" ] || { printf "Wrong tag:\n$bad\n" && failure=true;}
# check missing Coverity issue: tag
bad=$(for commit in $commits; do
body=$(git log --format='%b' -1 $commit)
echo "$body" | grep -qi coverity || continue
echo "$body" | grep -q '^Coverity issue:' && continue
git log --format='\t%s' -1 $commit
done)
[ -z "$bad" ] || { printf "Missing 'Coverity issue:' tag:\n$bad\n"\
&& failure=true;}
# check missing Bugzilla ID: tag
bad=$(for commit in $commits; do
body=$(git log --format='%b' -1 $commit)
echo "$body" | grep -qi bugzilla || continue
echo "$body" | grep -q '^Bugzilla ID:' && continue
git log --format='\t%s' -1 $commit
done)
[ -z "$bad" ] || { printf "Missing 'Bugzilla ID:' tag:\n$bad\n"\
&& failure=true;}
# check missing Fixes: tag
bad=$(for fix in $fixes ; do
git log --format='%b' -1 $fix | grep -q '^Fixes: ' ||
git log --format='\t%s' -1 $fix
done)
[ -z "$bad" ] || { printf "Missing 'Fixes' tag:\n$bad\n" && failure=true;}
# check Fixes: reference
fixtags=$(echo "$tags" | grep '^Fixes: ')
bad=$(for fixtag in $fixtags ; do
hash=$(echo "$fixtag" | sed 's,^Fixes: \([0-9a-f]*\).*,\1,')
if git branch --contains $hash 2>&- | grep -q '^\*' ; then
good="Fixes: $hash "$(git log --format='("%s")' -1 $hash 2>&-)
else
good="reference not in current branch"
fi
printf "$fixtag" | grep -v "^$good$"
done | sed 's,^,\t,')
[ -z "$bad" ] || { printf "Wrong 'Fixes' reference:\n$bad\n" && failure=true;}
# check Cc: stable@dpdk.org for fixes
bad=$(for fix in $stablefixes ; do
git log --format='%b' -1 $fix | grep -qi '^Cc: *stable@dpdk.org' ||
git log --format='\t%s' -1 $fix
done)
[ -z "$bad" ] || { printf "Is it candidate for Cc: stable@dpdk.org backport?\n$bad\n"\
&& failure=true;}
# check tag sequence
bad=$(for commit in $commits; do
body=$(git log --format='%b' -1 $commit)
echo "$body" |
grep -o -e "$reltag\|^[[:blank:]]*$\|$bytag" |
# retrieve tags only
cut -f1 -d":" |
# it is okay to have several tags of the same type
# but for processing we need to squash them
uniq |
# make sure the tags are in the proper order as presented in SEQ
awk -v subject="$(git log --format='\t%s' -1 $commit)" 'BEGIN{
SEQ[0] = "Coverity issue";
SEQ[1] = "Bugzilla ID";
SEQ[2] = "Fixes";
SEQ[3] = "Cc";
SEQ[4] = "^$";
SEQ[5] = "Reported-by";
SEQ[6] = "Suggested-by";
SEQ[7] = "Signed-off-by";
SEQ[8] = "Acked-by";
SEQ[9] = "Reviewed-by";
SEQ[10] = "Tested-by";
latest = 0;
chronological = 0;
}
{
for (seq = 0; seq < length(SEQ); seq++) {
if (chronological == 1)
continue;
if (match($0, SEQ[seq])) {
if (seq < latest) {
print subject " (" $0 ":)";
break;
} else {
latest = seq;
}
}
}
if (match($0, "Signed-off-by"))
chronological = 1;
}'
done)
[ -z "$bad" ] || { printf "Wrong tag order: \n$bad\n"\
&& failure=true;}
# check required tag
bad=$(for commit in $commits; do
body=$(git log --format='%b' -1 $commit)
echo $body | grep -q "Signed-off-by:" ||
git log --format='\t%s' -1 $commit
done)
[ -z "$bad" ] || { printf "Missing 'Signed-off-by:' tag: \n$bad\n"\
&& failure=true;}
# check names
names=$(git log --format='From: %an <%ae>%n%b' --reverse $range |
sed -rn 's,.*: (.*<.*@.*>),\1,p' |
sort -u)
bad=$(for contributor in $names ; do
contributor=$(echo $contributor | sed 's,(,\\(,')
! grep -qE "^$contributor($| <)" $selfdir/../.mailmap || continue
name=${contributor%% <*}
if grep -q "^$name <" $selfdir/../.mailmap ; then
printf "\t$contributor is not the primary email address\n"
else
printf "\t$contributor is unknown in .mailmap\n"
fi
done)
[ -z "$bad" ] || { printf "Contributor name/email mismatch with .mailmap: \n$bad\n"\
&& failure=true;}
total=$(echo "$commits" | wc -l)
if $failure ; then
printf "\nInvalid patch(es) found - checked $total patch"
else
printf "\n$total/$total valid patch"
fi
[ $total -le 1 ] || printf 'es'
printf '\n'
$failure && exit 1 || exit 0
|