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
|
#!/bin/bash
# Copyright 2020-2024 The OpenSSL Project Authors. All Rights Reserved.
#
# Licensed under the Apache License 2.0 (the "License").
# You may not use this file except in compliance with the License.
# You can obtain a copy in the file LICENSE in the source distribution
# or at https://www.openssl.org/source/license.html
#
# This script is a wrapper around check-format.pl.
# It accepts the same commit revision range as 'git diff' as arguments,
# or just a single commit id, and uses it to identify the files and line ranges
# that were changed in that commit range, filtering check-format.pl output
# only to lines that fall into the change ranges of the changed files.
# examples:
# check-format-commit.sh # check unstaged changes
# check-format-commit.sh HEAD
# check-format-commit.sh @~3..
# check-format-commit.sh f5981c9629667a5a5d6
# check-format-commit.sh f5981c9629667a5a5d6..ee0bf38e8709bf71888
# Allowlist of files to scan
# Currently this is any .c or .h file (with an optional .in suffix)
FILE_NAME_END_ALLOWLIST=("\.[ch]\(.in\)\?")
# Global vars
# TEMPDIR is used to hold any files this script creates
# And is cleaned on EXIT with a trap function
TEMPDIR=$(mktemp -d /tmp/checkformat.XXXXXX)
# TOPDIR always points to the root of the git tree we are working in
# used to locate the check-format.pl script
TOPDIR=$(git rev-parse --show-toplevel)
# cleanup handler function, returns us to the root of the git tree
# and erases our temp directory
cleanup() {
rm -rf $TEMPDIR
cd $TOPDIR
}
trap cleanup EXIT
# Get the list of ids of the commits we are checking,
# or empty for unstaged changes.
# This lets us pass in symbolic ref names like master/etc and
# resolve them to commit ids easily
COMMIT_RANGE="$@"
[ -n $COMMIT_RANGE ] && COMMIT_LAST=$(git rev-parse $COMMIT_RANGE)
# Fail gracefully if git rev-parse doesn't produce a valid commit
if [ $? -ne 0 ]
then
echo "$1 is not a valid commit range or commit id"
exit 1
fi
# If the commit range is exactly one revision,
# git rev-parse will output just the commit id of that one alone.
# In that case, we must manipulate a little to get a desirable result,
# as 'git diff' has a slightly different interpretation of a single commit id:
# it takes that to mean all commits up to HEAD, plus any unstaged changes.
if [ $(echo -n "$COMMIT_LAST" | wc -w) -ne 1 ]; then
COMMIT_LAST=$(echo "$COMMIT_LAST" | head -1)
else
# $COMMIT_RANGE is just one commit, make it an actual range
COMMIT_RANGE=$COMMIT_RANGE^..$COMMIT_RANGE
fi
# Create an iterable list of files to check formatting on,
# including the line ranges that are changed by the commits
# It produces output of this format:
# <file name> <change start line>, <change line count>
git diff -U0 $COMMIT_RANGE | awk '
BEGIN {myfile=""}
/^\+\+\+/ { sub(/^b./,"",$2); file=$2 }
/^@@/ { sub(/^\+/,"",$3); range=$3; printf file " " range "\n" }
' > $TEMPDIR/ranges.txt
# filter in anything that matches on a filter regex
for i in ${FILE_NAME_END_ALLOWLIST[@]}
do
# Note the space after the $i below. This is done because we want
# to match on file name suffixes, but the input file is of the form
# <commit> <file path> <range start>, <range length>
# So we can't just match on end of line. The additional space
# here lets us match on suffixes followed by the expected space
# in the input file
grep "$i " $TEMPDIR/ranges.txt >> $TEMPDIR/ranges.filter || true
done
REMAINING_FILES=$(wc -l <$TEMPDIR/ranges.filter)
if [ $REMAINING_FILES -eq 0 ]
then
echo "The given commit range has no C source file changes that require checking"
exit 0
fi
# unless checking the format of unstaged changes,
# check out the files from the commit range.
if [ -n "$COMMIT_RANGE" ]
then
# For each file name in ranges, we show that file at the commit range
# we are checking, and redirect it to the same path,
# relative to $TEMPDIR/check-format.
# This give us the full file path to run check-format.pl on
# with line numbers matching the ranges in the $TEMPDIR/ranges.filter file
for j in $(awk '{print $1}' $TEMPDIR/ranges.filter | sort -u)
do
FDIR=$(dirname $j)
mkdir -p $TEMPDIR/check-format/$FDIR
git show $COMMIT_LAST:$j > $TEMPDIR/check-format/$j
done
fi
# Now for each file in $TEMPDIR/ranges.filter, run check-format.pl
for j in $(awk '{print $1}' $TEMPDIR/ranges.filter | sort -u)
do
range_start=()
range_end=()
# Get the ranges for this file. Create 2 arrays. range_start contains
# the start lines for valid ranges from the commit. the range_end array
# contains the corresponding end line. Note, since diff output gives us
# a line count for a change, the range_end[k] entry is actually
# range_start[k]+line count
for k in $(grep ^$j $TEMPDIR/ranges.filter | awk '{print $2}')
do
RSTART=$(echo $k | awk -F',' '{print $1}')
RLEN=$(echo $k | awk -F',' '{print $2}')
# when the hunk is just one line, its length is implied
if [ -z "$RLEN" ]; then RLEN=1; fi
let REND=$RSTART+$RLEN
range_start+=($RSTART)
range_end+=($REND)
done
# Go to our checked out tree, unless checking unstaged changes
[ -n "$COMMIT_RANGE" ] && cd $TEMPDIR/check-format
# Actually run check-format.pl on the file, capturing the output
# in a temporary file. Note the format of check-format.pl output is
# <file path>:<line number>:<error text>:<offending line contents>
$TOPDIR/util/check-format.pl $j > $TEMPDIR/results.txt
# Now we filter the check-format.pl output based on the changed lines
# captured in the range_start/end arrays
let maxidx=${#range_start[@]}-1
for k in $(seq 0 1 $maxidx)
do
RSTART=${range_start[$k]}
REND=${range_end[$k]}
# field 2 of check-format.pl output is the offending line number
# Check here if any line in that output falls between any of the
# start/end ranges defined in the range_start/range_end array.
# If it does fall in that range, print the entire line to stdout
awk -v rstart=$RSTART -v rend=$REND -F':' '
/:/ { if (rstart <= $2 && $2 <= rend) print $0 }
' $TEMPDIR/results.txt >>$TEMPDIR/results-filtered.txt
done
done
cat $TEMPDIR/results-filtered.txt
# If any findings were in range, exit with a different error code
if [ -s $TEMPDIR/results-filtered.txt ]
then
exit 2
fi
|