1#! /bin/sh 2# SPDX-License-Identifier: BSD-3-Clause 3# Copyright 2015 6WIND S.A. 4 5# Load config options: 6# - DPDK_CHECKPATCH_PATH 7# - DPDK_CHECKPATCH_CODESPELL 8# - DPDK_CHECKPATCH_LINE_LENGTH 9# - DPDK_CHECKPATCH_OPTIONS 10. $(dirname $(readlink -f $0))/load-devel-config 11 12VALIDATE_NEW_API=$(dirname $(readlink -f $0))/check-symbol-change.sh 13 14# Enable codespell by default. This can be overwritten from a config file. 15# Codespell can also be enabled by setting DPDK_CHECKPATCH_CODESPELL to a valid path 16# to a dictionary.txt file if dictionary.txt is not in the default location. 17codespell=${DPDK_CHECKPATCH_CODESPELL:-enable} 18length=${DPDK_CHECKPATCH_LINE_LENGTH:-100} 19 20# override default Linux options 21options="--no-tree" 22if [ "$codespell" = "enable" ] ; then 23 options="$options --codespell" 24elif [ -f "$codespell" ] ; then 25 options="$options --codespell" 26 options="$options --codespellfile $codespell" 27fi 28options="$options --max-line-length=$length" 29options="$options --show-types" 30options="$options --ignore=LINUX_VERSION_CODE,ENOSYS,\ 31FILE_PATH_CHANGES,MAINTAINERS_STYLE,SPDX_LICENSE_TAG,\ 32VOLATILE,PREFER_PACKED,PREFER_ALIGNED,PREFER_PRINTF,STRLCPY,\ 33PREFER_KERNEL_TYPES,PREFER_FALLTHROUGH,BIT_MACRO,CONST_STRUCT,\ 34SPLIT_STRING,LONG_LINE_STRING,C99_COMMENT_TOLERANCE,\ 35LINE_SPACING,PARENTHESIS_ALIGNMENT,NETWORKING_BLOCK_COMMENT_STYLE,\ 36NEW_TYPEDEFS,COMPARISON_TO_NULL,AVOID_BUG" 37options="$options $DPDK_CHECKPATCH_OPTIONS" 38 39print_usage () { 40 cat <<- END_OF_HELP 41 usage: $(basename $0) [-h] [-q] [-v] [-nX|-r range|patch1 [patch2] ...] 42 43 Run Linux kernel checkpatch.pl with DPDK options. 44 The environment variable DPDK_CHECKPATCH_PATH can be set, if not we will 45 try to find the script in the sources of the currently running kernel. 46 47 The patches to check can be from stdin, files specified on the command line, 48 latest git commits limited with -n option, or commits in the git range 49 specified with -r option (default: "origin/main.."). 50 END_OF_HELP 51} 52 53check_forbidden_additions() { # <patch> 54 res=0 55 56 # refrain from new calls to RTE_LOG in libraries 57 awk -v FOLDERS="lib" \ 58 -v EXPRESSIONS="RTE_LOG\\\(" \ 59 -v RET_ON_FAIL=1 \ 60 -v MESSAGE='Prefer RTE_LOG_LINE' \ 61 -f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \ 62 "$1" || res=1 63 64 # refrain from new calls to RTE_LOG in drivers (but leave some leeway for base drivers) 65 awk -v FOLDERS="drivers" \ 66 -v SKIP_FILES='osdep.h$' \ 67 -v EXPRESSIONS="RTE_LOG\\\( RTE_LOG_DP\\\( rte_log\\\(" \ 68 -v RET_ON_FAIL=1 \ 69 -v MESSAGE='Prefer RTE_LOG_LINE/RTE_LOG_DP_LINE' \ 70 -f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \ 71 "$1" || res=1 72 73 # no output on stdout or stderr 74 awk -v FOLDERS="lib drivers" \ 75 -v EXPRESSIONS="\\\<printf\\\> \\\<fprintf\\\(stdout, \\\<fprintf\\\(stderr," \ 76 -v RET_ON_FAIL=1 \ 77 -v MESSAGE='Writing to stdout or stderr' \ 78 -f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \ 79 "$1" || res=1 80 81 # refrain from new additions of rte_panic() and rte_exit() 82 # multiple folders and expressions are separated by spaces 83 awk -v FOLDERS="lib drivers" \ 84 -v EXPRESSIONS="rte_panic\\\( rte_exit\\\(" \ 85 -v RET_ON_FAIL=1 \ 86 -v MESSAGE='Using rte_panic/rte_exit' \ 87 -f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \ 88 "$1" || res=1 89 90 # refrain from using compiler attribute without defining a common macro 91 awk -v FOLDERS="lib drivers app examples" \ 92 -v SKIP_FILES='lib/eal/include/rte_common.h' \ 93 -v EXPRESSIONS="__attribute__" \ 94 -v RET_ON_FAIL=1 \ 95 -v MESSAGE='Using compiler attribute directly' \ 96 -f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \ 97 "$1" || res=1 98 99 # check %l or %ll format specifier 100 awk -v FOLDERS='lib drivers app examples' \ 101 -v EXPRESSIONS='%ll*[xud]' \ 102 -v RET_ON_FAIL=1 \ 103 -v MESSAGE='Using %l format, prefer %PRI*64 if type is [u]int64_t' \ 104 -f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \ 105 "$1" || res=1 106 107 # refrain from new additions of 16/32/64 bits rte_atomicNN_xxx() 108 awk -v FOLDERS="lib drivers app examples" \ 109 -v EXPRESSIONS="rte_atomic[0-9][0-9]_.*\\\(" \ 110 -v RET_ON_FAIL=1 \ 111 -v MESSAGE='Using rte_atomicNN_xxx' \ 112 -f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \ 113 "$1" || res=1 114 115 # refrain from new additions of rte_smp_[r/w]mb() 116 awk -v FOLDERS="lib drivers app examples" \ 117 -v EXPRESSIONS="rte_smp_(r|w)?mb\\\(" \ 118 -v RET_ON_FAIL=1 \ 119 -v MESSAGE='Using rte_smp_[r/w]mb' \ 120 -f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \ 121 "$1" || res=1 122 123 # refrain from using compiler __sync_xxx builtins 124 awk -v FOLDERS="lib drivers app examples" \ 125 -v EXPRESSIONS="__sync_.*\\\(" \ 126 -v RET_ON_FAIL=1 \ 127 -v MESSAGE='Using __sync_xxx builtins' \ 128 -f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \ 129 "$1" || res=1 130 131 # refrain from using compiler __rte_atomic_thread_fence() 132 # It should be avoided on x86 for SMP case. 133 awk -v FOLDERS="lib drivers app examples" \ 134 -v EXPRESSIONS="__rte_atomic_thread_fence\\\(" \ 135 -v RET_ON_FAIL=1 \ 136 -v MESSAGE='Using __rte_atomic_thread_fence, prefer rte_atomic_thread_fence' \ 137 -f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \ 138 "$1" || res=1 139 140 # refrain from using compiler __atomic_xxx builtins 141 awk -v FOLDERS="lib drivers app examples" \ 142 -v EXPRESSIONS="__atomic_.*\\\( __ATOMIC_(RELAXED|CONSUME|ACQUIRE|RELEASE|ACQ_REL|SEQ_CST)" \ 143 -v RET_ON_FAIL=1 \ 144 -v MESSAGE='Using __atomic_xxx/__ATOMIC_XXX built-ins, prefer rte_atomic_xxx/rte_memory_order_xxx' \ 145 -f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \ 146 "$1" || res=1 147 148 # refrain from using some pthread functions 149 awk -v FOLDERS="lib drivers app examples" \ 150 -v EXPRESSIONS="pthread_(create|join|detach|set(_?name_np|affinity_np)|attr_set(inheritsched|schedpolicy))\\\(" \ 151 -v RET_ON_FAIL=1 \ 152 -v MESSAGE='Using pthread functions, prefer rte_thread' \ 153 -f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \ 154 "$1" || res=1 155 156 # forbid use of __reserved which is a reserved keyword in Windows system headers 157 awk -v FOLDERS="lib drivers app examples" \ 158 -v EXPRESSIONS='\\<__reserved\\>' \ 159 -v RET_ON_FAIL=1 \ 160 -v MESSAGE='Using __reserved' \ 161 -f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \ 162 "$1" || res=1 163 164 # forbid use of __alignof__ 165 awk -v FOLDERS="lib drivers app examples" \ 166 -v EXPRESSIONS='\\<__alignof__\\>' \ 167 -v RET_ON_FAIL=1 \ 168 -v MESSAGE='Using __alignof__, prefer C11 alignof' \ 169 -f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \ 170 "$1" || res=1 171 172 # forbid use of __typeof__ 173 awk -v FOLDERS="lib drivers app examples" \ 174 -v EXPRESSIONS='\\<__typeof__\\>' \ 175 -v RET_ON_FAIL=1 \ 176 -v MESSAGE='Using __typeof__, prefer typeof' \ 177 -f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \ 178 "$1" || res=1 179 180 # forbid use of compiler __builtin_* 181 awk -v FOLDERS="lib drivers app examples" \ 182 -v SKIP_FILES='lib/eal/ drivers/.*/base/ drivers/.*osdep.h$' \ 183 -v EXPRESSIONS='\\<__builtin_' \ 184 -v RET_ON_FAIL=1 \ 185 -v MESSAGE='Using __builtin helpers, prefer EAL macros' \ 186 -f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \ 187 "$1" || res=1 188 189 # forbid inclusion of Linux header for PCI constants 190 awk -v FOLDERS="lib drivers app examples" \ 191 -v EXPRESSIONS='include.*linux/pci_regs\\.h' \ 192 -v RET_ON_FAIL=1 \ 193 -v MESSAGE='Using linux/pci_regs.h, prefer rte_pci.h' \ 194 -f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \ 195 "$1" || res=1 196 197 # forbid use of variadic argument pack extension in macros 198 awk -v FOLDERS="lib drivers app examples" \ 199 -v EXPRESSIONS='#[[:space:]]*define.*[^(,[:space:]]\\.\\.\\.[[:space:]]*)' \ 200 -v RET_ON_FAIL=1 \ 201 -v MESSAGE='Do not use variadic argument pack in macros' \ 202 -f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \ 203 "$1" || res=1 204 205 # forbid use of __rte_packed_begin with enums 206 awk -v FOLDERS='lib drivers app examples' \ 207 -v EXPRESSIONS='enum.*__rte_packed_begin' \ 208 -v RET_ON_FAIL=1 \ 209 -v MESSAGE='Using __rte_packed_begin with enum is not allowed' \ 210 -f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \ 211 "$1" || res=1 212 213 # forbid use of experimental build flag except in examples 214 awk -v FOLDERS='lib drivers app' \ 215 -v EXPRESSIONS='-DALLOW_EXPERIMENTAL_API allow_experimental_apis' \ 216 -v RET_ON_FAIL=1 \ 217 -v MESSAGE='Using experimental build flag for in-tree compilation' \ 218 -f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \ 219 "$1" || res=1 220 221 # refrain from using RTE_LOG_REGISTER for drivers and libs 222 awk -v FOLDERS='lib drivers' \ 223 -v EXPRESSIONS='\\<RTE_LOG_REGISTER\\>' \ 224 -v RET_ON_FAIL=1 \ 225 -v MESSAGE='Using RTE_LOG_REGISTER, prefer RTE_LOG_REGISTER_(DEFAULT|SUFFIX)' \ 226 -f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \ 227 "$1" || res=1 228 229 # forbid non-internal thread in drivers and libs 230 awk -v FOLDERS='lib drivers' \ 231 -v EXPRESSIONS="rte_thread_(set_name|create_control)\\\(" \ 232 -v RET_ON_FAIL=1 \ 233 -v MESSAGE='Prefer rte_thread_(set_prefixed_name|create_internal_control)' \ 234 -f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \ 235 "$1" || res=1 236 237 # forbid rte_ symbols in cnxk base driver 238 awk -v FOLDERS='drivers/common/cnxk/roc_*' \ 239 -v SKIP_FILES='roc_platform*' \ 240 -v EXPRESSIONS="rte_ RTE_" \ 241 -v RET_ON_FAIL=1 \ 242 -v MESSAGE='Use plt_ symbols instead of rte_ API in cnxk base driver' \ 243 -f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \ 244 "$1" || res=1 245 246 # forbid inclusion of driver specific headers in apps and examples 247 awk -v FOLDERS='app examples' \ 248 -v EXPRESSIONS='include.*_driver\\.h include.*_pmd\\.h' \ 249 -v RET_ON_FAIL=1 \ 250 -v MESSAGE='Using driver specific headers in applications' \ 251 -f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \ 252 "$1" || res=1 253 254 # prevent addition of tests not in one of our test suites 255 awk -v FOLDERS='app/test' \ 256 -v EXPRESSIONS='REGISTER_TEST_COMMAND' \ 257 -v RET_ON_FAIL=1 \ 258 -v MESSAGE='Using REGISTER_TEST_COMMAND instead of REGISTER_<suite_name>_TEST' \ 259 -f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \ 260 "$1" || res=1 261 262 # SVG must be included with wildcard extension to allow conversion 263 awk -v FOLDERS='doc' \ 264 -v EXPRESSIONS='::[[:space:]]*[^[:space:]]*\\.svg' \ 265 -v RET_ON_FAIL=1 \ 266 -v MESSAGE='Using explicit .svg extension instead of .*' \ 267 -f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \ 268 "$1" || res=1 269 270 # links must prefer https over http 271 awk -v FOLDERS='doc' \ 272 -v EXPRESSIONS='http://.*dpdk.org' \ 273 -v RET_ON_FAIL=1 \ 274 -v MESSAGE='Using non https link to dpdk.org' \ 275 -f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \ 276 "$1" || res=1 277 278 # prefer Sphinx references for internal documentation 279 awk -v FOLDERS='doc' \ 280 -v EXPRESSIONS='//doc.dpdk.org/guides/' \ 281 -v RET_ON_FAIL=1 \ 282 -v MESSAGE='Using explicit URL to doc.dpdk.org, prefer :ref: or :doc:' \ 283 -f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \ 284 "$1" || res=1 285 286 # '// XXX is not set' must be preferred over '#undef XXX' 287 awk -v FOLDERS='config/rte_config.h' \ 288 -v EXPRESSIONS='#undef' \ 289 -v RET_ON_FAIL=1 \ 290 -v MESSAGE='Using "#undef XXX", prefer "// XXX is not set"' \ 291 -f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \ 292 "$1" || res=1 293 294 return $res 295} 296 297check_experimental_tags() { # <patch> 298 res=0 299 300 cat "$1" |awk ' 301 BEGIN { 302 current_file = ""; 303 ret = 0; 304 } 305 /^+++ b\// { 306 current_file = $2; 307 } 308 /^+.*__rte_experimental/ { 309 if (current_file ~ ".c$" ) { 310 print "Please only put __rte_experimental tags in " \ 311 "headers ("current_file")"; 312 ret = 1; 313 } 314 if ($1 != "+__rte_experimental" || $2 != "") { 315 print "__rte_experimental must appear alone on the line" \ 316 " immediately preceding the return type of a function." 317 ret = 1; 318 } 319 } 320 END { 321 exit ret; 322 }' || res=1 323 324 return $res 325} 326 327check_internal_tags() { # <patch> 328 res=0 329 330 cat "$1" |awk ' 331 BEGIN { 332 current_file = ""; 333 ret = 0; 334 } 335 /^+++ b\// { 336 current_file = $2; 337 } 338 /^+.*__rte_internal/ { 339 if (current_file ~ ".c$" ) { 340 print "Please only put __rte_internal tags in " \ 341 "headers ("current_file")"; 342 ret = 1; 343 } 344 if ($1 != "+__rte_internal" || $2 != "") { 345 print "__rte_internal must appear alone on the line" \ 346 " immediately preceding the return type of" \ 347 " a function." 348 ret = 1; 349 } 350 } 351 END { 352 exit ret; 353 }' || res=1 354 355 return $res 356} 357 358check_aligned_attributes() { # <patch> 359 res=0 360 361 for token in __rte_aligned __rte_cache_aligned __rte_cache_min_aligned; do 362 if [ $(grep -E '^\+.*\<'$token'\>' "$1" | \ 363 grep -vE '\<(struct|union)[[:space:]]*'$token'\>' | \ 364 wc -l) != 0 ]; then 365 echo "Please use $token only for struct or union types alignment." 366 res=1 367 fi 368 done 369 370 return $res 371} 372 373check_packed_attributes() { # <patch> 374 res=0 375 376 if [ $(grep -E '^\+.*__rte_packed_begin' "$1" | \ 377 grep -vE '\<struct[[:space:]]*__rte_packed_begin\>' | \ 378 grep -vE '\<union[[:space:]]*__rte_packed_begin\>' | \ 379 grep -vE '\<__rte_cache_aligned[[:space:]]*__rte_packed_begin\>' | \ 380 grep -vE '\<__rte_cache_min_aligned[[:space:]]*__rte_packed_begin\>' | \ 381 grep -vE '\<__rte_aligned\(.*\)[[:space:]]*__rte_packed_begin\>' | \ 382 wc -l) != 0 ]; then 383 echo "Use __rte_packed_begin only after struct, union or alignment attributes." 384 res=1 385 fi 386 387 begin_count=$(grep '__rte_packed_begin' "$1" | wc -l) 388 end_count=$(grep '__rte_packed_end' "$1" | wc -l) 389 if [ $begin_count != $end_count ]; then 390 echo "__rte_packed_begin and __rte_packed_end should always be used in pairs." 391 res=1 392 fi 393 394 return $res 395} 396 397check_release_notes() { # <patch> 398 rel_notes_prefix=doc/guides/rel_notes/release_ 399 IFS=. read year month release < VERSION 400 current_rel_notes=${rel_notes_prefix}${year}_${month}.rst 401 402 ! grep -e '^--- a/'$rel_notes_prefix -e '^+++ b/'$rel_notes_prefix "$1" | 403 grep -v $current_rel_notes 404} 405 406number=0 407range='origin/main..' 408quiet=false 409verbose=false 410while getopts hn:qr:v ARG ; do 411 case $ARG in 412 n ) number=$OPTARG ;; 413 q ) quiet=true ;; 414 r ) range=$OPTARG ;; 415 v ) verbose=true ;; 416 h ) print_usage ; exit 0 ;; 417 ? ) print_usage ; exit 1 ;; 418 esac 419done 420shift $(($OPTIND - 1)) 421 422if [ ! -f "$DPDK_CHECKPATCH_PATH" ] || [ ! -x "$DPDK_CHECKPATCH_PATH" ] ; then 423 default_path="/lib/modules/$(uname -r)/source/scripts/checkpatch.pl" 424 if [ -f "$default_path" ] && [ -x "$default_path" ] ; then 425 DPDK_CHECKPATCH_PATH="$default_path" 426 else 427 print_usage >&2 428 echo 429 echo 'Cannot execute DPDK_CHECKPATCH_PATH' >&2 430 exit 1 431 fi 432fi 433 434print_headline() { # <title> 435 printf '\n### %s\n\n' "$1" 436 headline_printed=true 437} 438 439total=0 440status=0 441 442check () { # <patch-file> <commit> 443 local ret=0 444 local subject='' 445 headline_printed=false 446 447 total=$(($total + 1)) 448 if [ -n "$1" ] ; then 449 tmpinput=$1 450 else 451 tmpinput=$(mktemp -t dpdk.checkpatches.XXXXXX) 452 trap "rm -f '$tmpinput'" INT 453 454 if [ -n "$2" ] ; then 455 git format-patch --find-renames \ 456 --no-stat --stdout -1 $commit > "$tmpinput" 457 else 458 cat > "$tmpinput" 459 fi 460 fi 461 462 # Subject can be on 2 lines 463 subject=$(sed '/^Subject: */!d;s///;N;s,\n[[:space:]]\+, ,;s,\n.*,,;q' "$tmpinput") 464 ! $verbose || print_headline "$subject" 465 466 ! $verbose || printf 'Running checkpatch.pl:\n' 467 report=$($DPDK_CHECKPATCH_PATH $options "$tmpinput" 2>/dev/null) 468 if [ $? -ne 0 ] ; then 469 $headline_printed || print_headline "$subject" 470 printf '%s\n' "$report" | sed -n '1,/^total:.*lines checked$/p' 471 ret=1 472 fi 473 474 ! $verbose || printf '\nChecking API additions/removals:\n' 475 report=$($VALIDATE_NEW_API "$tmpinput") 476 if [ $? -ne 0 ] ; then 477 $headline_printed || print_headline "$subject" 478 printf '%s\n' "$report" 479 ret=1 480 fi 481 482 ! $verbose || printf '\nChecking forbidden tokens additions:\n' 483 report=$(check_forbidden_additions "$tmpinput") 484 if [ $? -ne 0 ] ; then 485 $headline_printed || print_headline "$subject" 486 printf '%s\n' "$report" 487 ret=1 488 fi 489 490 ! $verbose || printf '\nChecking __rte_experimental tags:\n' 491 report=$(check_experimental_tags "$tmpinput") 492 if [ $? -ne 0 ] ; then 493 $headline_printed || print_headline "$subject" 494 printf '%s\n' "$report" 495 ret=1 496 fi 497 498 ! $verbose || printf '\nChecking __rte_internal tags:\n' 499 report=$(check_internal_tags "$tmpinput") 500 if [ $? -ne 0 ] ; then 501 $headline_printed || print_headline "$subject" 502 printf '%s\n' "$report" 503 ret=1 504 fi 505 506 ! $verbose || printf '\nChecking alignment attributes:\n' 507 report=$(check_aligned_attributes "$tmpinput") 508 if [ $? -ne 0 ] ; then 509 $headline_printed || print_headline "$subject" 510 printf '%s\n' "$report" 511 ret=1 512 fi 513 514 ! $verbose || printf '\nChecking packed attributes:\n' 515 report=$(check_packed_attributes "$tmpinput") 516 if [ $? -ne 0 ] ; then 517 $headline_printed || print_headline "$subject" 518 printf '%s\n' "$report" 519 ret=1 520 fi 521 522 ! $verbose || printf '\nChecking release notes updates:\n' 523 report=$(check_release_notes "$tmpinput") 524 if [ $? -ne 0 ] ; then 525 $headline_printed || print_headline "$subject" 526 printf '%s\n' "$report" 527 ret=1 528 fi 529 530 if [ "$tmpinput" != "$1" ]; then 531 rm -f "$tmpinput" 532 trap - INT 533 fi 534 [ $ret -eq 0 ] && return 0 535 536 status=$(($status + 1)) 537} 538 539if [ -n "$1" ] ; then 540 for patch in "$@" ; do 541 check "$patch" '' 542 done 543elif [ ! -t 0 ] ; then # stdin 544 check '' '' 545else 546 if [ $number -eq 0 ] ; then 547 commits=$(git rev-list --reverse $range) 548 else 549 commits=$(git rev-list --reverse --max-count=$number HEAD) 550 fi 551 for commit in $commits ; do 552 check '' $commit 553 done 554fi 555pass=$(($total - $status)) 556$quiet || printf '\n%d/%d valid patch' $pass $total 557$quiet || [ $pass -le 1 ] || printf 'es' 558$quiet || printf '\n' 559exit $status 560