1#!/usr/bin/env bash 2 3# TODO: 4# - inline replace 5# - clang-format-diff replacement 6# - uncrustify for patches (not git refs) 7# - maybe integrate into travis-ci? 8 9function usage() 10{ 11 cat <<EOL 12$0 [ OPTS ] [ file-or-gitref [ ... ] ] 13 14Example: 15 # Chech HEAD git ref 16 $ $0 -r 17 $ $0 -r HEAD 18 19 # Check patch 20 $ git format-patch --stdout -1 | $0 -p 21 $ git show -1 | $0 -p 22 23 # Or via regular files 24 $ git format-patch --stdout -2 25 $ $0 *.patch 26 27 # Over a file 28 $ $0 -d event.c 29 $ $0 -d < event.c 30 31 # And print the whole file not only summary 32 $ $0 -f event.c 33 $ $0 -f < event.c 34 35OPTS: 36 -p - treat as patch 37 -f - treat as regular file 38 -f - treat as regular file and print diff 39 -r - treat as git revision (default) 40 -C - check using clang-format (default) 41 -U - check with uncrustify 42 -c - config for clang-format/uncrustify 43 -h - print this message 44EOL 45} 46function cfg() 47{ 48 [ -z "${options[cfg]}" ] || { 49 echo "${options[cfg]}" 50 return 51 } 52 53 local dir="$(dirname "${BASH_SOURCE[0]}")" 54 [ "${options[clang]}" -eq 0 ] || { 55 echo "$dir/.clang-format" 56 return 57 } 58 [ "${options[uncrustify]}" -eq 0 ] || { 59 echo "$dir/.uncrustify" 60 return 61 } 62} 63function abort() 64{ 65 local msg="$1" 66 shift 67 68 printf "$msg\n" "$@" >&2 69 exit 1 70} 71function default_arg() 72{ 73 if [ "${options[ref]}" -eq 1 ]; then 74 echo "HEAD" 75 else 76 [ ! -t 0 ] || abort "<stdin> is a tty" 77 echo "/dev/stdin" 78 fi 79} 80function parse_options() 81{ 82 options[patch]=0 83 options[file]=0 84 options[file_diff]=0 85 options[ref]=1 86 options[clang]=1 87 options[uncrustify]=0 88 options[cfg]= 89 90 local OPTARG OPTIND c 91 while getopts "pfrdCUc:h?" c; do 92 case "$c" in 93 p) 94 options[patch]=1 95 options[ref]=0 96 options[file]=0 97 options[file_diff]=0 98 ;; 99 f) 100 options[file]=1 101 options[ref]=0 102 options[patch]=0 103 options[file_diff]=0 104 ;; 105 r) 106 options[ref]=1 107 options[file]=0 108 options[patch]=0 109 options[file_diff]=0 110 ;; 111 d) 112 options[file_diff]=1 113 options[file]=0 114 options[patch]=0 115 options[ref]=0 116 ;; 117 C) 118 options[clang]=1 119 options[uncrustify]=0 120 ;; 121 U) 122 options[uncrustify]=1 123 options[clang]=0 124 ;; 125 c) options[cfg]="$OPTIND" ;; 126 ?|h) 127 usage 128 exit 0 129 ;; 130 *) 131 usage 132 exit 1 133 ;; 134 esac 135 done 136 137 options[cfg]="$(cfg)" 138 139 [ -f "${options[cfg]}" ] || \ 140 abort "Config '%s' does not exist" "${options[cfg]}" 141 142 shift $((OPTIND - 1)) 143 args=( "$@" ) 144 145 if [ ${#args[@]} -eq 0 ]; then 146 # exit on error globally, not only in subshell 147 default_arg > /dev/null 148 args=( "$(default_arg)" ) 149 fi 150 151 if [ "${args[0]}" = "/dev/stdin" ]; then 152 TMP_FILE="/tmp/libevent.checkpatch.$RANDOM" 153 cat > "$TMP_FILE" 154 trap "rm '$TMP_FILE'" EXIT 155 156 args[0]="$TMP_FILE" 157 fi 158} 159 160function diff() { command diff --color=always "$@"; } 161 162function clang_style() 163{ 164 local c="${options[cfg]}" 165 echo "{ $(sed -e 's/#.*//' -e '/---/d' -e '/\.\.\./d' "$c" | tr $'\n' ,) }" 166} 167function clang_format() { clang-format --style="$(clang_style)" "$@"; } 168function clang_format_diff() { clang-format-diff --style="$(clang_style)" "$@"; } 169# for non-bare repo will work 170function clang_format_git() 171{ git format-patch --stdout "$@" -1 | clang_format_diff; } 172 173function uncrustify() { command uncrustify -c "${options[cfg]}" "$@"; } 174function uncrustify_frag() { uncrustify -l C --frag "$@"; } 175function uncrustify_indent_off() { echo '/* *INDENT-OFF* */'; } 176function uncrustify_indent_on() { echo '/* *INDENT-ON* */'; } 177function git_hunk() 178{ 179 local ref=$1 f=$2 180 shift 2 181 git cat-file -p $ref:$f 182} 183function uncrustify_git_indent_hunk() 184{ 185 local start=$1 end=$2 186 shift 2 187 188 # Will be beatier with tee(1), but doh bash async substitution 189 { uncrustify_indent_off; git_hunk "$@" | head -n$((start - 1)); } 190 { uncrustify_indent_on; git_hunk "$@" | head -n$((end - 1)) | tail -n+$start; } 191 { uncrustify_indent_off; git_hunk "$@" | tail -n+$((end + 1)); } 192} 193function strip() 194{ 195 local start=$1 end=$2 196 shift 2 197 198 # seek indent_{on,off}() 199 let start+=2 200 head -n$end | tail -n+$start 201} 202function patch_ranges() 203{ 204 egrep -o '^@@ -[0-9]+(,[0-9]+|) \+[0-9]+(,[0-9]+|) @@' | \ 205 cut -d' ' -f3 206} 207function git_ranges() 208{ 209 local ref=$1 f=$2 210 shift 2 211 212 git diff -W $ref^..$ref -- $f | patch_ranges 213} 214function diff_substitute() 215{ 216 local f="$1" 217 shift 218 219 sed \ 220 -e "s#^--- /dev/fd.*\$#--- a/$f#" \ 221 -e "s#^+++ /dev/fd.*\$#+++ b/$f#" 222} 223function uncrustify_git() 224{ 225 local ref=$1 r f start end length 226 shift 227 228 local files=( $(git diff --name-only $ref^..$ref | egrep "\.(c|h)$") ) 229 for f in "${files[@]}"; do 230 local ranges=( $(git_ranges $ref "$f") ) 231 for r in "${ranges[@]}"; do 232 [[ ! "$r" =~ ^\+([0-9]+)(,([0-9]+)|)$ ]] && continue 233 start=${BASH_REMATCH[1]} 234 [ -n "${BASH_REMATCH[3]}" ] && \ 235 length=${BASH_REMATCH[3]} || \ 236 length=1 237 end=$((start + length)) 238 echo "Range: $start:$end ($length)" >&2 239 240 diff -u \ 241 <(uncrustify_git_indent_hunk $start $end $ref "$f" | strip $start $end) \ 242 <(uncrustify_git_indent_hunk $start $end $ref "$f" | uncrustify_frag | strip $start $end) \ 243 | diff_substitute "$f" 244 done 245 done 246} 247function uncrustify_diff() { abort "Not implemented"; } 248function uncrustify_file() { uncrustify -f "$@"; } 249 250function checker() 251{ 252 local c=$1 u=$2 253 shift 2 254 255 [ "${options[clang]}" -eq 0 ] || { 256 $c "$@" 257 return 258 } 259 [ "${options[uncrustify]}" -eq 0 ] || { 260 $u "$@" 261 return 262 } 263} 264function check_patch() { checker clang_format_diff uncrustify_diff "$@"; } 265function check_file() { checker clang_format uncrustify_file "$@"; } 266function check_ref() { checker clang_format_git uncrustify_git "$@"; } 267 268function check_arg() 269{ 270 [ "${options[patch]}" -eq 0 ] || { 271 check_patch "$@" 272 return 273 } 274 [ "${options[file]}" -eq 0 ] || { 275 check_file "$@" 276 return 277 } 278 [ "${options[file_diff]}" -eq 0 ] || { 279 diff -u "$@" <(check_file "$@") | diff_substitute "$@" 280 return 281 } 282 [ "${options[ref]}" -eq 0 ] || { 283 check_ref "$@" 284 return 285 } 286} 287 288function main() 289{ 290 local a 291 for a in "${args}"; do 292 check_arg "$a" 293 done 294} 295 296declare -A options 297parse_options "$@" 298 299main "$@" | less -FRSX 300