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. It accepts the same commit 10# revision range as 'git diff' as arguments, and uses it to identify the files 11# and ranges that were changed in that range, filtering check-format.pl output 12# only to lines that fall into the change ranges of the changed files. 13# 14 15# Allowlist of files to scan 16# Currently this is any .c or .h file (with an optional .in suffix 17FILE_ALLOWLIST=("\.[ch]\(.in\)\?") 18 19# Exit code for the script 20EXIT_CODE=0 21 22# Global vars 23 24# TEMPDIR is used to hold any files this script creates 25# And is cleaned on EXIT with a trap function 26TEMPDIR=$(mktemp -d /tmp/checkformat.XXXXXX) 27 28# TOPDIR always points to the root of the git tree we are working in 29# used to locate the check-format.pl script 30TOPDIR=$(git rev-parse --show-toplevel) 31 32 33# cleanup handler function, returns us to the root of the git tree 34# and erases our temp directory 35cleanup() { 36 rm -rf $TEMPDIR 37 cd $TOPDIR 38} 39 40trap cleanup EXIT 41 42# Get the canonical sha256 sum for the commits we are checking 43# This lets us pass in symbolic ref names like master/etc and 44# resolve them to sha256 sums easily 45COMMIT_RANGE="$@" 46COMMIT_LAST=$(git rev-parse $COMMIT_RANGE) 47 48# Fail gracefully if git rev-parse doesn't produce a valid 49# commit 50if [ $? -ne 0 ] 51then 52 echo "$1 is not a valid revision" 53 exit 1 54fi 55 56# If the commit range was just one single revision, git rev-parse 57# will output jut commit id of that one alone. In that case, we 58# must manipulate a little to get a desirable result, 'cause git 59# diff has a slightly different interpretation of a single commit 60# id, and takes that to mean all commits up to HEAD. 61if [ $(echo "$COMMIT_LAST" | wc -l) -gt 1 ]; then 62 COMMIT_LAST=$(echo "$COMMIT_LAST" | head -1) 63else 64 # $COMMIT_RANGE is just one commit, make it an actual range 65 COMMIT_RANGE=$COMMIT_RANGE^..$COMMIT_RANGE 66fi 67 68# Create an iterable list of files to check formatting on, 69# including the line ranges that are changed by the commits 70# It produces output of this format: 71# <file name> <change start line>, <change line count> 72touch $TEMPDIR/ranges.txt 73git diff -U0 $COMMIT_RANGE | awk ' 74 BEGIN {myfile=""} 75 /+{3}/ { 76 gsub(/b\//,"",$2); 77 myfile=$2 78 } 79 /@@/ { 80 gsub(/+/,"",$3); 81 printf myfile " " $3 "\n" 82 }' >> $TEMPDIR/ranges.txt || true 83 84# filter in anything that matches on a filter regex 85for i in ${FILE_ALLOWLIST[@]} 86do 87 touch $TEMPDIR/ranges.filter 88 # Note the space after the $i below. This is done because we want 89 # to match on file suffixes, but the input file is of the form 90 # <commit> <file> <range start>, <range length> 91 # So we can't just match on end of line. The additional space 92 # here lets us match on suffixes followed by the expected space 93 # in the input file 94 grep "$i " $TEMPDIR/ranges.txt >> $TEMPDIR/ranges.filter || true 95done 96cp $TEMPDIR/ranges.filter $TEMPDIR/ranges.txt 97REMAINING_FILES=$(wc -l $TEMPDIR/ranges.filter | awk '{print $1}') 98if [ $REMAINING_FILES -eq 0 ] 99then 100 echo "This commit has no files that require checking" 101 exit 0 102fi 103 104# check out the files from the commit level. 105# For each file name in ranges, we show that file at the commit 106# level we are checking, and redirect it to the same path, relative 107# to $TEMPDIR/check-format. This give us the full file to run 108# check-format.pl on with line numbers matching the ranges in the 109# $TEMPDIR/ranges.txt file 110for j in $(cat $TEMPDIR/ranges.txt | awk '{print $1}' | sort | uniq) 111do 112 FDIR=$(dirname $j) 113 mkdir -p $TEMPDIR/check-format/$FDIR 114 git show $COMMIT_LAST:$j > $TEMPDIR/check-format/$j 115done 116 117# Now for each file in $TEMPDIR/ranges.txt, run check-format.pl 118for j in $(cat $TEMPDIR/ranges.txt | awk '{print $1}' | sort | uniq) 119do 120 range_start=() 121 range_end=() 122 123 # Get the ranges for this file. Create 2 arrays. range_start contains 124 # the start lines for valid ranges from the commit. the range_end array 125 # contains the corresponding end line (note, since diff output gives us 126 # a line count for a change, the range_end[k] entry is actually 127 # range_start[k]+line count 128 for k in $(grep ^$j $TEMPDIR/ranges.txt | awk '{print $2}') 129 do 130 RANGE=$k 131 RSTART=$(echo $RANGE | awk -F',' '{print $1}') 132 RLEN=$(echo $RANGE | awk -F',' '{print $2}') 133 # when the hunk is just one line, its length is implied 134 if [ -z "$RLEN" ]; then RLEN=1; fi 135 let REND=$RSTART+$RLEN 136 range_start+=($RSTART) 137 range_end+=($REND) 138 done 139 140 # Go to our checked out tree 141 cd $TEMPDIR/check-format 142 143 # Actually run check-format.pl on the file, capturing the output 144 # in a temporary file. Note the format of check-patch.pl output is 145 # <file name>:<line number>:<error text>:<offending line contents> 146 $TOPDIR/util/check-format.pl $j > $TEMPDIR/format-results.txt 147 148 # Now we filter the check-format.pl output based on the changed lines 149 # captured in the range_start/end arrays 150 let maxidx=${#range_start[@]}-1 151 for k in $(seq 0 1 $maxidx) 152 do 153 RSTART=${range_start[$k]} 154 REND=${range_end[$k]} 155 156 # field 2 of check-format.pl output is the offending line number 157 # Check here if any line in that output falls between any of the 158 # start/end ranges defined in the range_start/range_end array. 159 # If it does fall in that range, print the entire line to stdout 160 # If anything is printed, have awk exit with a non-zero exit code 161 awk -v rstart=$RSTART -v rend=$REND -F':' ' 162 BEGIN {rc=0} 163 /:/ { 164 if (($2 >= rstart) && ($2 <= rend)) { 165 print $0; 166 rc=1 167 } 168 } 169 END {exit rc;} 170 ' $TEMPDIR/format-results.txt 171 172 # If awk exited with a non-zero code, this script will also exit 173 # with a non-zero code 174 if [ $? -ne 0 ] 175 then 176 EXIT_CODE=1 177 fi 178 done 179done 180 181# Exit with the recorded exit code above 182exit $EXIT_CODE 183