Merge branch 'maint-0.4.8'
[tor.git] / scripts / maint / code-format.sh
blob5998e96f6810ce8e47ab43b43eb675a9faf988c4
1 #!/usr/bin/env bash
2 # Copyright 2020, The Tor Project, Inc.
3 # See LICENSE for licensing information.
6 # DO NOT COMMIT OR MERGE CODE THAT IS RUN THROUGH THIS TOOL YET.
8 # WE ARE STILL DISCUSSING OUR DESIRED STYLE AND ITERATING ON IT.
9 # (12 Feb 2020)
12 # This script runs "clang-format" and "codetool" in sequence over each of its
13 # arguments. It either replaces the original, or says whether anything has
14 # changed, depending on its arguments.
16 # We can't just use clang-format directly, since we also want to use codetool
17 # to reformat a few things back to how we want them, and we want avoid changing
18 # the mtime on files that didn't actually change.
20 # Use "-i" to edit the file in-place.
21 # Use "-c" to exit with a nonzero exit status if any file needs to change.
22 # Use "-d" to emit diffs.
24 # The "-a" option tells us to run over every Tor source file.
25 # The "-v" option tells us to be verbose.
27 set -e
29 ALL=0
30 GITDIFF=0
31 GITIDX=0
32 DIFFMODE=0
33 CHECKMODE=0
34 CHANGEMODE=0
36 SCRIPT_NAME=$(basename "$0")
37 SCRIPT_DIR=$(dirname "$0")
38 SRC_DIR="${SCRIPT_DIR}/../../src"
40 function usage() {
41 echo "$SCRIPT_NAME [-h] [-c|-d|-i] [-v] [-a|-G|files...]"
42 echo
43 echo " flags:"
44 echo " -h: show this help text"
45 echo " -c: check whether files are correctly formatted"
46 echo " -d: print a diff for the changes that would be applied"
47 echo " -i: change files in-place"
48 echo " -a: run over all the C files in Tor"
49 echo " -v: verbose mode"
50 echo " -g: look at the files that have changed in git."
51 echo " -G: look at the files that are staged for the git commit."
52 echo
53 echo "EXAMPLES"
54 echo
55 echo " $SCRIPT_NAME -a -i"
56 echo " rewrite every file in place, whether it has changed or not."
57 echo " $SCRIPT_NAME -a -d"
58 echo " as above, but only display the changes."
59 echo " $SCRIPT_NAME -g -i"
60 echo " update every file that you have changed in the git working tree."
61 echo " $SCRIPT_NAME -G -c"
62 echo " exit with an error if any staged changes are not well-formatted."
65 FILEARGS_OK=1
67 while getopts "acdgGhiv" opt; do
68 case "$opt" in
69 h) usage
70 exit 0
72 a) ALL=1
73 FILEARGS_OK=0
75 g) GITDIFF=1
76 FILEARGS_OK=0
78 G) GITIDX=1
79 FILEARGS_OK=0
81 c) CHECKMODE=1
83 d) DIFFMODE=1
85 i) CHANGEMODE=1
87 v) VERBOSE=1
89 *) echo
90 usage
91 exit 1
93 esac
94 done
95 # get rid of the flags; keep the filenames.
96 shift $((OPTIND - 1))
98 # Define a verbose function.
99 if [[ $VERBOSE = 1 ]]; then
100 function note()
102 echo "$@"
104 else
105 function note()
107 true
111 # We have to be in at least one mode, or we can't do anything
112 if [[ $CHECKMODE = 0 && $DIFFMODE = 0 && $CHANGEMODE = 0 ]]; then
113 echo "Nothing to do. You need to specify -c, -d, or -i."
114 echo "Try $SCRIPT_NAME -h for more information."
115 exit 0
118 # We don't want to "give an error if anything would change" if we're
119 # actually trying to change things.
120 if [[ $CHECKMODE = 1 && $CHANGEMODE = 1 ]]; then
121 echo "It doesn't make sense to use -c and -i together."
122 exit 0
124 # It doesn't make sense to look at "all files" and "git files"
125 if [[ $((ALL + GITIDX + GITDIFF)) -gt 1 ]]; then
126 echo "It doesn't make sense to use more than one of -a, -g, or -G together."
127 exit 0
130 if [[ $FILEARGS_OK = 1 ]]; then
131 # The filenames are on the command-line.
132 INPUTS=("${@}")
133 else
134 if [[ "$#" != 0 ]]; then
135 echo "Can't use -a, -g, or -G with additional command-line arguments."
136 exit 1
140 if [[ $ALL = 1 ]]; then
141 # We're in "all" mode -- use find(1) to find the filenames.
142 mapfile -d '' INPUTS < <(find "${SRC_DIR}"/{lib,core,feature,app,test,tools} -name '[^.]*.[ch]' -print0)
143 elif [[ $GITIDX = 1 ]]; then
144 # We're in "git index" mode -- use git diff --cached to find the filenames
145 # that are changing in the index, then strip out the ones that
146 # aren't C.
147 mapfile INPUTS < <(git diff --name-only --cached --diff-filter=AMCR | grep '\.[ch]$')
148 elif [[ $GITDIFF = 1 ]]; then
149 # We are in 'git diff' mode -- we want everything that changed, including
150 # the index and the working tree.
152 # TODO: There might be a better way to do this.
153 mapfile INPUTS < <(git diff --name-only --cached --diff-filter=AMCR | grep '\.[ch]$'; git diff --name-only --diff-filter=AMCR | grep '\.[ch]$' )
156 if [[ $GITIDX = 1 ]]; then
157 # If we're running in git mode, we need to stash all the changes that
158 # we don't want to look at. This is necessary even though we're only
159 # looking at the changed files, since we might have the file only
160 # partially staged.
161 note "Stashing unstaged changes"
162 git stash -q --keep-index
163 # For some reasons, shellcheck is not seeing that we can call this
164 # function from the trap below.
165 # shellcheck disable=SC2317
166 function restoregit() {
167 note "Restoring git state"
168 git stash pop -q
170 else
171 # For some reasons, shellcheck is not seeing that we can call this
172 # function from the trap below.
173 # shellcheck disable=SC2317
174 function restoregit() {
175 true
179 ANY_CHANGED=0
181 tmpfname=""
184 # Set up a trap handler to make sure that on exit, we remove our
185 # tmpfile and un-stash the git environment (if appropriate)
187 trap 'if [ -n "${tmpfname}" ]; then rm -f "${tmpfname}"; fi; restoregit' 0
189 for fname in "${INPUTS[@]}"; do
190 note "Inspecting $fname..."
191 tmpfname="${fname}.$$.clang_fmt.tmp"
192 rm -f "${tmpfname}"
193 clang-format --style=file "${fname}" > "${tmpfname}"
194 "${SCRIPT_DIR}/codetool.py" "${tmpfname}"
196 changed=not_set
198 if [[ $DIFFMODE = 1 ]]; then
199 # If we're running diff for its output, we can also use it
200 # to compare the files.
201 if diff -u "${fname}" "${tmpfname}"; then
202 changed=0
203 else
204 changed=1
206 else
207 # We aren't running diff, so we have to compare the files with cmp.
208 if cmp "${fname}" "${tmpfname}" >/dev/null 2>&1; then
209 changed=0
210 else
211 changed=1
215 if [[ $changed = 1 ]]; then
216 note "Found a change in $fname"
217 ANY_CHANGED=1
219 if [[ $CHANGEMODE = 1 ]]; then
220 mv "${tmpfname}" "${fname}"
224 rm -f "${tmpfname}"
225 done
227 exitcode=0
229 if [[ $CHECKMODE = 1 ]]; then
230 if [[ $ANY_CHANGED = 1 ]]; then
231 note "Found at least one misformatted file; check failed"
232 exitcode=1
233 else
234 note "No changes found."
238 exit $exitcode