1
0

check-format-commit.sh 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. #!/bin/bash
  2. # Copyright 2020-2024 The OpenSSL Project Authors. All Rights Reserved.
  3. #
  4. # Licensed under the Apache License 2.0 (the "License").
  5. # You may not use this file except in compliance with the License.
  6. # You can obtain a copy in the file LICENSE in the source distribution
  7. # or at https://www.openssl.org/source/license.html
  8. #
  9. # This script is a wrapper around check-format.pl.
  10. # It accepts the same commit revision range as 'git diff' as arguments,
  11. # or just a single commit id, and uses it to identify the files and line ranges
  12. # that were changed in that commit range, filtering check-format.pl output
  13. # only to lines that fall into the change ranges of the changed files.
  14. # examples:
  15. # check-format-commit.sh # check unstaged changes
  16. # check-format-commit.sh HEAD
  17. # check-format-commit.sh @~3..
  18. # check-format-commit.sh f5981c9629667a5a5d6
  19. # check-format-commit.sh f5981c9629667a5a5d6..ee0bf38e8709bf71888
  20. # Allowlist of files to scan
  21. # Currently this is any .c or .h file (with an optional .in suffix)
  22. FILE_NAME_END_ALLOWLIST=("\.[ch]\(.in\)\?")
  23. # Global vars
  24. # TEMPDIR is used to hold any files this script creates
  25. # And is cleaned on EXIT with a trap function
  26. TEMPDIR=$(mktemp -d /tmp/checkformat.XXXXXX)
  27. # TOPDIR always points to the root of the git tree we are working in
  28. # used to locate the check-format.pl script
  29. TOPDIR=$(git rev-parse --show-toplevel)
  30. # cleanup handler function, returns us to the root of the git tree
  31. # and erases our temp directory
  32. cleanup() {
  33. rm -rf $TEMPDIR
  34. cd $TOPDIR
  35. }
  36. trap cleanup EXIT
  37. # Get the list of ids of the commits we are checking,
  38. # or empty for unstaged changes.
  39. # This lets us pass in symbolic ref names like master/etc and
  40. # resolve them to commit ids easily
  41. COMMIT_RANGE="$@"
  42. [ -n $COMMIT_RANGE ] && COMMIT_LAST=$(git rev-parse $COMMIT_RANGE)
  43. # Fail gracefully if git rev-parse doesn't produce a valid commit
  44. if [ $? -ne 0 ]
  45. then
  46. echo "$1 is not a valid commit range or commit id"
  47. exit 1
  48. fi
  49. # If the commit range is exactly one revision,
  50. # git rev-parse will output just the commit id of that one alone.
  51. # In that case, we must manipulate a little to get a desirable result,
  52. # as 'git diff' has a slightly different interpretation of a single commit id:
  53. # it takes that to mean all commits up to HEAD, plus any unstaged changes.
  54. if [ $(echo -n "$COMMIT_LAST" | wc -w) -ne 1 ]; then
  55. COMMIT_LAST=$(echo "$COMMIT_LAST" | head -1)
  56. else
  57. # $COMMIT_RANGE is just one commit, make it an actual range
  58. COMMIT_RANGE=$COMMIT_RANGE^..$COMMIT_RANGE
  59. fi
  60. # Create an iterable list of files to check formatting on,
  61. # including the line ranges that are changed by the commits
  62. # It produces output of this format:
  63. # <file name> <change start line>, <change line count>
  64. git diff -U0 $COMMIT_RANGE | awk '
  65. BEGIN {myfile=""}
  66. /^\+\+\+/ { sub(/^b./,"",$2); file=$2 }
  67. /^@@/ { sub(/^\+/,"",$3); range=$3; printf file " " range "\n" }
  68. ' > $TEMPDIR/ranges.txt
  69. # filter in anything that matches on a filter regex
  70. for i in ${FILE_NAME_END_ALLOWLIST[@]}
  71. do
  72. # Note the space after the $i below. This is done because we want
  73. # to match on file name suffixes, but the input file is of the form
  74. # <commit> <file path> <range start>, <range length>
  75. # So we can't just match on end of line. The additional space
  76. # here lets us match on suffixes followed by the expected space
  77. # in the input file
  78. grep "$i " $TEMPDIR/ranges.txt >> $TEMPDIR/ranges.filter || true
  79. done
  80. REMAINING_FILES=$(wc -l <$TEMPDIR/ranges.filter)
  81. if [ $REMAINING_FILES -eq 0 ]
  82. then
  83. echo "The given commit range has no C source file changes that require checking"
  84. exit 0
  85. fi
  86. # unless checking the format of unstaged changes,
  87. # check out the files from the commit range.
  88. if [ -n "$COMMIT_RANGE" ]
  89. then
  90. # For each file name in ranges, we show that file at the commit range
  91. # we are checking, and redirect it to the same path,
  92. # relative to $TEMPDIR/check-format.
  93. # This give us the full file path to run check-format.pl on
  94. # with line numbers matching the ranges in the $TEMPDIR/ranges.filter file
  95. for j in $(awk '{print $1}' $TEMPDIR/ranges.filter | sort -u)
  96. do
  97. FDIR=$(dirname $j)
  98. mkdir -p $TEMPDIR/check-format/$FDIR
  99. git show $COMMIT_LAST:$j > $TEMPDIR/check-format/$j
  100. done
  101. fi
  102. # Now for each file in $TEMPDIR/ranges.filter, run check-format.pl
  103. for j in $(awk '{print $1}' $TEMPDIR/ranges.filter | sort -u)
  104. do
  105. range_start=()
  106. range_end=()
  107. # Get the ranges for this file. Create 2 arrays. range_start contains
  108. # the start lines for valid ranges from the commit. the range_end array
  109. # contains the corresponding end line. Note, since diff output gives us
  110. # a line count for a change, the range_end[k] entry is actually
  111. # range_start[k]+line count
  112. for k in $(grep ^$j $TEMPDIR/ranges.filter | awk '{print $2}')
  113. do
  114. RSTART=$(echo $k | awk -F',' '{print $1}')
  115. RLEN=$(echo $k | awk -F',' '{print $2}')
  116. # when the hunk is just one line, its length is implied
  117. if [ -z "$RLEN" ]; then RLEN=1; fi
  118. let REND=$RSTART+$RLEN
  119. range_start+=($RSTART)
  120. range_end+=($REND)
  121. done
  122. # Go to our checked out tree, unless checking unstaged changes
  123. [ -n "$COMMIT_RANGE" ] && cd $TEMPDIR/check-format
  124. # Actually run check-format.pl on the file, capturing the output
  125. # in a temporary file. Note the format of check-format.pl output is
  126. # <file path>:<line number>:<error text>:<offending line contents>
  127. $TOPDIR/util/check-format.pl $j > $TEMPDIR/results.txt
  128. # Now we filter the check-format.pl output based on the changed lines
  129. # captured in the range_start/end arrays
  130. let maxidx=${#range_start[@]}-1
  131. for k in $(seq 0 1 $maxidx)
  132. do
  133. RSTART=${range_start[$k]}
  134. REND=${range_end[$k]}
  135. # field 2 of check-format.pl output is the offending line number
  136. # Check here if any line in that output falls between any of the
  137. # start/end ranges defined in the range_start/range_end array.
  138. # If it does fall in that range, print the entire line to stdout
  139. awk -v rstart=$RSTART -v rend=$REND -F':' '
  140. /:/ { if (rstart <= $2 && $2 <= rend) print $0 }
  141. ' $TEMPDIR/results.txt >>$TEMPDIR/results-filtered.txt
  142. done
  143. done
  144. cat $TEMPDIR/results-filtered.txt
  145. # If any findings were in range, exit with a different error code
  146. if [ -s $TEMPDIR/results-filtered.txt ]
  147. then
  148. exit 2
  149. fi