tor

The Tor anonymity network
git clone https://git.dasho.dev/tor.git
Log | Files | Refs | README | LICENSE

code-format.sh (6777B)


      1 #!/usr/bin/env bash
      2 # Copyright 2020, The Tor Project, Inc.
      3 # See LICENSE for licensing information.
      4 
      5 #
      6 # DO NOT COMMIT OR MERGE CODE THAT IS RUN THROUGH THIS TOOL YET.
      7 #
      8 # WE ARE STILL DISCUSSING OUR DESIRED STYLE AND ITERATING ON IT.
      9 #     (12 Feb 2020)
     10 #
     11 
     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.
     15 #
     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.
     19 #
     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.
     23 #
     24 # The "-a" option tells us to run over every Tor source file.
     25 # The "-v" option tells us to be verbose.
     26 
     27 set -e
     28 
     29 ALL=0
     30 GITDIFF=0
     31 GITIDX=0
     32 DIFFMODE=0
     33 CHECKMODE=0
     34 CHANGEMODE=0
     35 
     36 SCRIPT_NAME=$(basename "$0")
     37 SCRIPT_DIR=$(dirname "$0")
     38 SRC_DIR="${SCRIPT_DIR}/../../src"
     39 
     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."
     63 }
     64 
     65 FILEARGS_OK=1
     66 
     67 while getopts "acdgGhiv" opt; do
     68    case "$opt" in
     69        h) usage
     70           exit 0
     71           ;;
     72        a) ALL=1
     73           FILEARGS_OK=0
     74           ;;
     75        g) GITDIFF=1
     76           FILEARGS_OK=0
     77           ;;
     78        G) GITIDX=1
     79           FILEARGS_OK=0
     80           ;;
     81        c) CHECKMODE=1
     82           ;;
     83        d) DIFFMODE=1
     84           ;;
     85        i) CHANGEMODE=1
     86           ;;
     87        v) VERBOSE=1
     88           ;;
     89        *) echo
     90           usage
     91           exit 1
     92           ;;
     93    esac
     94 done
     95 # get rid of the flags; keep the filenames.
     96 shift $((OPTIND - 1))
     97 
     98 # Define a verbose function.
     99 if [[ $VERBOSE = 1 ]]; then
    100    function note()
    101    {
    102        echo "$@"
    103    }
    104 else
    105    function note()
    106    {
    107        true
    108    }
    109 fi
    110 
    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
    116 fi
    117 
    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
    123 fi
    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
    128 fi
    129 
    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
    137    fi
    138 fi
    139 
    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.
    151    #
    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]$' )
    154 fi
    155 
    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,SC2329
    166    function restoregit() {
    167        note "Restoring git state"
    168        git stash pop -q
    169    }
    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,SC2329
    174    function restoregit() {
    175        true
    176    }
    177 fi
    178 
    179 ANY_CHANGED=0
    180 
    181 tmpfname=""
    182 
    183 #
    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)
    186 #
    187 trap 'if [ -n "${tmpfname}" ]; then rm -f "${tmpfname}"; fi; restoregit' 0
    188 
    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}"
    195 
    196    changed=not_set
    197 
    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
    205        fi
    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
    212        fi
    213    fi
    214 
    215    if [[ $changed = 1 ]]; then
    216        note "Found a change in $fname"
    217        ANY_CHANGED=1
    218 
    219        if [[ $CHANGEMODE = 1 ]]; then
    220            mv "${tmpfname}" "${fname}"
    221        fi
    222    fi
    223 
    224    rm -f "${tmpfname}"
    225 done
    226 
    227 exitcode=0
    228 
    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."
    235    fi
    236 fi
    237 
    238 exit $exitcode