Home | History | Annotate | Download | only in tools
      1 #!/bin/sh
      2 self="$0"
      3 dirname_self=$(dirname "$self")
      4 
      5 usage() {
      6   cat <<EOF >&2
      7 Usage: $self [option]
      8 
      9 This script applies a whitespace transformation to the commit at HEAD. If no
     10 options are given, then the modified files are left in the working tree.
     11 
     12 Options:
     13   -h, --help     Shows this message
     14   -n, --dry-run  Shows a diff of the changes to be made.
     15   --amend        Squashes the changes into the commit at HEAD
     16                      This option will also reformat the commit message.
     17   --commit       Creates a new commit containing only the whitespace changes
     18   --msg-only     Reformat the commit message only, ignore the patch itself.
     19 
     20 EOF
     21   rm -f ${CLEAN_FILES}
     22   exit 1
     23 }
     24 
     25 
     26 log() {
     27   echo "${self##*/}: $@" >&2
     28 }
     29 
     30 
     31 vpx_style() {
     32   for f; do
     33     case "$f" in
     34       *.h|*.c|*.cc)
     35         clang-format -i --style=file "$f"
     36         ;;
     37     esac
     38   done
     39 }
     40 
     41 
     42 apply() {
     43   [ $INTERSECT_RESULT -ne 0 ] && patch -p1 < "$1"
     44 }
     45 
     46 
     47 commit() {
     48   LAST_CHANGEID=$(git show | awk '/Change-Id:/{print $2}')
     49   if [ -z "$LAST_CHANGEID" ]; then
     50     log "HEAD doesn't have a Change-Id, unable to generate a new commit"
     51     exit 1
     52   fi
     53 
     54   # Build a deterministic Change-Id from the parent's
     55   NEW_CHANGEID=${LAST_CHANGEID}-styled
     56   NEW_CHANGEID=I$(echo $NEW_CHANGEID | git hash-object --stdin)
     57 
     58   # Commit, preserving authorship from the parent commit.
     59   git commit -a -C HEAD > /dev/null
     60   git commit --amend -F- << EOF
     61 Cosmetic: Fix whitespace in change ${LAST_CHANGEID:0:9}
     62 
     63 Change-Id: ${NEW_CHANGEID}
     64 EOF
     65 }
     66 
     67 
     68 show_commit_msg_diff() {
     69   if [ $DIFF_MSG_RESULT -ne 0 ]; then
     70     log "Modified commit message:"
     71     diff -u "$ORIG_COMMIT_MSG" "$NEW_COMMIT_MSG" | tail -n +3
     72   fi
     73 }
     74 
     75 
     76 amend() {
     77   show_commit_msg_diff
     78   if [ $DIFF_MSG_RESULT -ne 0 ] || [ $INTERSECT_RESULT -ne 0 ]; then
     79     git commit -a --amend -F "$NEW_COMMIT_MSG"
     80   fi
     81 }
     82 
     83 
     84 diff_msg() {
     85   git log -1 --format=%B > "$ORIG_COMMIT_MSG"
     86   "${dirname_self}"/wrap-commit-msg.py \
     87       < "$ORIG_COMMIT_MSG" > "$NEW_COMMIT_MSG"
     88   cmp -s "$ORIG_COMMIT_MSG" "$NEW_COMMIT_MSG"
     89   DIFF_MSG_RESULT=$?
     90 }
     91 
     92 
     93 # Temporary files
     94 ORIG_DIFF=orig.diff.$$
     95 MODIFIED_DIFF=modified.diff.$$
     96 FINAL_DIFF=final.diff.$$
     97 ORIG_COMMIT_MSG=orig.commit-msg.$$
     98 NEW_COMMIT_MSG=new.commit-msg.$$
     99 CLEAN_FILES="${ORIG_DIFF} ${MODIFIED_DIFF} ${FINAL_DIFF}"
    100 CLEAN_FILES="${CLEAN_FILES} ${ORIG_COMMIT_MSG} ${NEW_COMMIT_MSG}"
    101 
    102 # Preconditions
    103 [ $# -lt 2 ] || usage
    104 
    105 if ! clang-format -version >/dev/null 2>&1; then
    106   log "clang-format not found"
    107   exit 1
    108 fi
    109 
    110 if ! git diff --quiet HEAD; then
    111   log "Working tree is dirty, commit your changes first"
    112   exit 1
    113 fi
    114 
    115 # Need to be in the root
    116 cd "$(git rev-parse --show-toplevel)"
    117 
    118 # Collect the original diff
    119 git show > "${ORIG_DIFF}"
    120 
    121 # Apply the style guide on new and modified files and collect its diff
    122 for f in $(git diff HEAD^ --name-only -M90 --diff-filter=AM); do
    123   case "$f" in
    124     third_party/*) continue;;
    125   esac
    126   vpx_style "$f"
    127 done
    128 git diff --no-color --no-ext-diff > "${MODIFIED_DIFF}"
    129 
    130 # Intersect the two diffs
    131 "${dirname_self}"/intersect-diffs.py \
    132     "${ORIG_DIFF}" "${MODIFIED_DIFF}" > "${FINAL_DIFF}"
    133 INTERSECT_RESULT=$?
    134 git reset --hard >/dev/null
    135 
    136 # Fixup the commit message
    137 diff_msg
    138 
    139 # Handle options
    140 if [ -n "$1" ]; then
    141   case "$1" in
    142     -h|--help) usage;;
    143     -n|--dry-run) cat "${FINAL_DIFF}"; show_commit_msg_diff;;
    144     --commit) apply "${FINAL_DIFF}"; commit;;
    145     --amend) apply "${FINAL_DIFF}"; amend;;
    146     --msg-only) amend;;
    147     *) usage;;
    148   esac
    149 else
    150   apply "${FINAL_DIFF}"
    151   if ! git diff --quiet; then
    152     log "Formatting changes applied, verify and commit."
    153     log "See also: http://www.webmproject.org/code/contribute/conventions/"
    154     git diff --stat
    155   fi
    156 fi
    157 
    158 rm -f ${CLEAN_FILES}
    159