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 a commit sha 10# value as input, and uses it to identify the files and ranges that were 11# changed in that commit, filtering check-format.pl output only to lines that 12# fall into the commits change ranges. 13# 14 15 16# List of Regexes to use when running check-format.pl. 17# Style checks don't apply to any of these 18EXCLUDED_FILE_REGEX=("\.pod" \ 19 "\.pl" \ 20 "\.pm" \ 21 "\.t" \ 22 "\.yml" \ 23 "\.sh") 24 25# Exit code for the script 26EXIT_CODE=0 27 28# Global vars 29 30# TEMPDIR is used to hold any files this script creates 31# And is cleaned on EXIT with a trap function 32TEMPDIR=$(mktemp -d /tmp/checkformat.XXXXXX) 33 34# TOPDIR always points to the root of the git tree we are working in 35# used to locate the check-format.pl script 36TOPDIR=$(git rev-parse --show-toplevel) 37 38 39# cleanup handler function, returns us to the root of the git tree 40# and erases our temp directory 41cleanup() { 42 rm -rf $TEMPDIR 43 cd $TOPDIR 44} 45 46trap cleanup EXIT 47 48# Get the canonical sha256 sum for the commit we are checking 49# This lets us pass in symbolic ref names like master/etc and 50# resolve them to sha256 sums easily 51COMMIT=$(git rev-parse $1) 52 53# Fail gracefully if git rev-parse doesn't produce a valid 54# commit 55if [ $? -ne 0 ] 56then 57 echo "$1 is not a valid revision" 58 exit 1 59fi 60 61# Create a iteratable list of files to check for a 62# given commit. It produces output of the format 63# <commit id> <file name> <change start line>, <change line count> 64touch $TEMPDIR/ranges.txt 65git show $COMMIT | awk -v mycmt=$COMMIT ' 66 BEGIN {myfile=""} 67 /+{3}/ { 68 gsub(/b\//,"",$2); 69 myfile=$2 70 } 71 /@@/ { 72 gsub(/+/,"",$3); 73 printf mycmt " " myfile " " $3 "\n" 74 }' >> $TEMPDIR/ranges.txt || true 75 76# filter out anything that matches on a filter regex 77for i in ${EXCLUDED_FILE_REGEX[@]} 78do 79 touch $TEMPDIR/ranges.filter 80 grep -v "$i" $TEMPDIR/ranges.txt >> $TEMPDIR/ranges.filter || true 81 REMAINING_FILES=$(wc -l $TEMPDIR/ranges.filter | awk '{print $1}') 82 if [ $REMAINING_FILES -eq 0 ] 83 then 84 echo "This commit has no files that require checking" 85 exit 0 86 fi 87 mv $TEMPDIR/ranges.filter $TEMPDIR/ranges.txt 88done 89 90# check out the files from the commit level. 91# For each file name in ranges, we show that file at the commit 92# level we are checking, and redirect it to the same path, relative 93# to $TEMPDIR/check-format. This give us the full file to run 94# check-format.pl on with line numbers matching the ranges in the 95# $TEMPDIR/ranges.txt file 96for j in $(grep $COMMIT $TEMPDIR/ranges.txt | awk '{print $2}') 97do 98 FDIR=$(dirname $j) 99 mkdir -p $TEMPDIR/check-format/$FDIR 100 git show $COMMIT:$j > $TEMPDIR/check-format/$j 101done 102 103# Now for each file in $TEMPDIR/check-format run check-format.pl 104# Note that we use the %P formatter in the find utilty. This strips 105# off the $TEMPDIR/check-format path prefix, leaving $j with the 106# path to the file relative to the root of the source dir, so that 107# output from check-format.pl looks correct, relative to the root 108# of the git tree. 109for j in $(find $TEMPDIR/check-format -type f -printf "%P\n") 110do 111 range_start=() 112 range_end=() 113 114 # Get the ranges for this file. Create 2 arrays. range_start contains 115 # the start lines for valid ranges from the commit. the range_end array 116 # contains the corresponding end line (note, since diff output gives us 117 # a line count for a change, the range_end[k] entry is actually 118 # range_start[k]+line count 119 for k in $(grep $COMMIT $TEMPDIR/ranges.txt | grep $j | awk '{print $3}') 120 do 121 RANGE=$k 122 RSTART=$(echo $RANGE | awk -F',' '{print $1}') 123 RLEN=$(echo $RANGE | awk -F',' '{print $2}') 124 let REND=$RSTART+$RLEN 125 range_start+=($RSTART) 126 range_end+=($REND) 127 done 128 129 # Go to our checked out tree 130 cd $TEMPDIR/check-format 131 132 # Actually run check-format.pl on the file, capturing the output 133 # in a temporary file. Note the format of check-patch.pl output is 134 # <file name>:<line number>:<error text>:<offending line contents> 135 $TOPDIR/util/check-format.pl $j > $TEMPDIR/format-results.txt 136 137 # Now we filter the check-format.pl output based on the changed lines 138 # captured in the range_start/end arrays 139 let maxidx=${#range_start[@]}-1 140 for k in $(seq 0 1 $maxidx) 141 do 142 RSTART=${range_start[$k]} 143 REND=${range_end[$k]} 144 145 # field 2 of check-format.pl output is the offending line number 146 # Check here if any line in that output falls between any of the 147 # start/end ranges defined in the range_start/range_end array. 148 # If it does fall in that range, print the entire line to stdout 149 # If anything is printed, have awk exit with a non-zero exit code 150 awk -v rstart=$RSTART -v rend=$REND -F':' ' 151 BEGIN {rc=0} 152 /:/ { 153 if (($2 >= rstart) && ($2 <= rend)) { 154 print $0; 155 rc=1 156 } 157 } 158 END {exit rc;} 159 ' $TEMPDIR/format-results.txt 160 161 # If awk exited with a non-zero code, this script will also exit 162 # with a non-zero code 163 if [ $? -ne 0 ] 164 then 165 EXIT_CODE=1 166 fi 167 done 168done 169 170# Exit with the recorded exit code above 171exit $EXIT_CODE 172