format-diff.sh 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. #!/usr/bin/env bash
  2. # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
  3. # If clang_format_diff.py command is not specfied, we assume we are able to
  4. # access directly without any path.
  5. print_usage () {
  6. echo "Usage:"
  7. echo "format-diff.sh [OPTIONS]"
  8. echo "-c: check only."
  9. echo "-h: print this message."
  10. }
  11. while getopts ':ch' OPTION; do
  12. case "$OPTION" in
  13. c)
  14. CHECK_ONLY=1
  15. ;;
  16. h)
  17. print_usage
  18. exit 1
  19. ;;
  20. ?)
  21. print_usage
  22. exit 1
  23. ;;
  24. esac
  25. done
  26. REPO_ROOT="$(git rev-parse --show-toplevel)"
  27. if [ "$CLANG_FORMAT_DIFF" ]; then
  28. echo "Note: CLANG_FORMAT_DIFF='$CLANG_FORMAT_DIFF'"
  29. # Dry run to confirm dependencies like argparse
  30. if $CLANG_FORMAT_DIFF --help >/dev/null < /dev/null; then
  31. true #Good
  32. else
  33. exit 128
  34. fi
  35. else
  36. # First try directly executing the possibilities
  37. if clang-format-diff --help &> /dev/null < /dev/null; then
  38. CLANG_FORMAT_DIFF=clang-format-diff
  39. elif clang-format-diff.py --help &> /dev/null < /dev/null; then
  40. CLANG_FORMAT_DIFF=clang-format-diff.py
  41. elif $REPO_ROOT/clang-format-diff.py --help &> /dev/null < /dev/null; then
  42. CLANG_FORMAT_DIFF=$REPO_ROOT/clang-format-diff.py
  43. else
  44. # This probably means we need to directly invoke the interpreter.
  45. # But first find clang-format-diff.py
  46. if [ -f "$REPO_ROOT/clang-format-diff.py" ]; then
  47. CFD_PATH="$REPO_ROOT/clang-format-diff.py"
  48. elif which clang-format-diff.py &> /dev/null; then
  49. CFD_PATH="$(which clang-format-diff.py)"
  50. else
  51. echo "You didn't have clang-format-diff.py and/or clang-format available in your computer!"
  52. echo "You can download clang-format-diff.py by running: "
  53. echo " curl --location https://raw.githubusercontent.com/llvm/llvm-project/main/clang/tools/clang-format/clang-format-diff.py -o ${REPO_ROOT}/clang-format-diff.py"
  54. echo "You should make sure the downloaded script is not compromised."
  55. echo "You can download clang-format by running:"
  56. echo " brew install clang-format"
  57. echo " Or"
  58. echo " apt install clang-format"
  59. echo " This might work too:"
  60. echo " yum install git-clang-format"
  61. echo "Then make sure clang-format is available and executable from \$PATH:"
  62. echo " clang-format --version"
  63. exit 128
  64. fi
  65. # Check argparse pre-req on interpreter, or it will fail
  66. if echo import argparse | ${PYTHON:-python3}; then
  67. true # Good
  68. else
  69. echo "To run clang-format-diff.py, we'll need the library "argparse" to be"
  70. echo "installed. You can try either of the follow ways to install it:"
  71. echo " 1. Manually download argparse: https://pypi.python.org/pypi/argparse"
  72. echo " 2. easy_install argparse (if you have easy_install)"
  73. echo " 3. pip install argparse (if you have pip)"
  74. exit 129
  75. fi
  76. # Unfortunately, some machines have a Python2 clang-format-diff.py
  77. # installed but only a Python3 interpreter installed. Unfortunately,
  78. # automatic 2to3 migration is insufficient, so suggest downloading latest.
  79. if grep -q "print '" "$CFD_PATH" && \
  80. ${PYTHON:-python3} --version | grep -q 'ython 3'; then
  81. echo "You have clang-format-diff.py for Python 2 but are using a Python 3"
  82. echo "interpreter (${PYTHON:-python3})."
  83. echo "You can download clang-format-diff.py for Python 3 by running: "
  84. echo " curl --location https://raw.githubusercontent.com/llvm/llvm-project/main/clang/tools/clang-format/clang-format-diff.py -o ${REPO_ROOT}/clang-format-diff.py"
  85. echo "You should make sure the downloaded script is not compromised."
  86. exit 130
  87. fi
  88. CLANG_FORMAT_DIFF="${PYTHON:-python3} $CFD_PATH"
  89. # This had better work after all those checks
  90. if $CLANG_FORMAT_DIFF --help >/dev/null < /dev/null; then
  91. true #Good
  92. else
  93. exit 128
  94. fi
  95. fi
  96. fi
  97. # TODO(kailiu) following work is not complete since we still need to figure
  98. # out how to add the modified files done pre-commit hook to git's commit index.
  99. #
  100. # Check if this script has already been added to pre-commit hook.
  101. # Will suggest user to add this script to pre-commit hook if their pre-commit
  102. # is empty.
  103. # PRE_COMMIT_SCRIPT_PATH="`git rev-parse --show-toplevel`/.git/hooks/pre-commit"
  104. # if ! ls $PRE_COMMIT_SCRIPT_PATH &> /dev/null
  105. # then
  106. # echo "Would you like to add this script to pre-commit hook, which will do "
  107. # echo -n "the format check for all the affected lines before you check in (y/n):"
  108. # read add_to_hook
  109. # if [ "$add_to_hook" == "y" ]
  110. # then
  111. # ln -s `git rev-parse --show-toplevel`/build_tools/format-diff.sh $PRE_COMMIT_SCRIPT_PATH
  112. # fi
  113. # fi
  114. set -e
  115. # Exclude third-party from formatting
  116. EXCLUDE=':!third-party/'
  117. uncommitted_code=`git diff HEAD`
  118. # If there's no uncommitted changes, we assume user are doing post-commit
  119. # format check, in which case we'll try to check the modified lines vs. the
  120. # facebook/rocksdb.git main branch. Otherwise, we'll check format of the
  121. # uncommitted code only.
  122. if [ -z "$uncommitted_code" ]
  123. then
  124. # Attempt to get name of facebook/rocksdb.git remote.
  125. [ "$FORMAT_REMOTE" ] || FORMAT_REMOTE="$(LC_ALL=POSIX LANG=POSIX git remote -v | grep 'facebook/rocksdb.git' | head -n 1 | cut -f 1)"
  126. # Fall back on 'origin' if that fails
  127. [ "$FORMAT_REMOTE" ] || FORMAT_REMOTE=origin
  128. # Use main branch from that remote
  129. [ "$FORMAT_UPSTREAM" ] || FORMAT_UPSTREAM="$FORMAT_REMOTE/$(LC_ALL=POSIX LANG=POSIX git remote show $FORMAT_REMOTE | sed -n '/HEAD branch/s/.*: //p')"
  130. # Get the common ancestor with that remote branch. Everything after that
  131. # common ancestor would be considered the contents of a pull request, so
  132. # should be relevant for formatting fixes.
  133. FORMAT_UPSTREAM_MERGE_BASE="$(git merge-base "$FORMAT_UPSTREAM" HEAD)"
  134. # Get the differences
  135. diffs=$(git diff -U0 "$FORMAT_UPSTREAM_MERGE_BASE" -- $EXCLUDE | $CLANG_FORMAT_DIFF -p 1) || true
  136. echo "Checking format of changes not yet in $FORMAT_UPSTREAM..."
  137. else
  138. # Check the format of uncommitted lines,
  139. diffs=$(git diff -U0 HEAD -- $EXCLUDE | $CLANG_FORMAT_DIFF -p 1) || true
  140. echo "Checking format of uncommitted changes..."
  141. fi
  142. if [ -z "$diffs" ]
  143. then
  144. echo "Nothing needs to be reformatted!"
  145. exit 0
  146. elif [ $? -ne 1 ]; then
  147. # CLANG_FORMAT_DIFF will exit on 1 while there is suggested changes.
  148. exit $?
  149. elif [ $CHECK_ONLY ]
  150. then
  151. echo "Your change has unformatted code. Please run make format!"
  152. if [ $VERBOSE_CHECK ]; then
  153. clang-format --version
  154. echo "$diffs"
  155. fi
  156. exit 1
  157. fi
  158. # Highlight the insertion/deletion from the clang-format-diff.py's output
  159. COLOR_END="\033[0m"
  160. COLOR_RED="\033[0;31m"
  161. COLOR_GREEN="\033[0;32m"
  162. echo -e "Detect lines that doesn't follow the format rules:\r"
  163. # Add the color to the diff. lines added will be green; lines removed will be red.
  164. echo "$diffs" |
  165. sed -e "s/\(^-.*$\)/`echo -e \"$COLOR_RED\1$COLOR_END\"`/" |
  166. sed -e "s/\(^+.*$\)/`echo -e \"$COLOR_GREEN\1$COLOR_END\"`/"
  167. echo -e "Would you like to fix the format automatically (y/n): \c"
  168. # Make sure under any mode, we can read user input.
  169. exec < /dev/tty
  170. read to_fix
  171. if [ "$to_fix" != "y" ]
  172. then
  173. exit 1
  174. fi
  175. # Do in-place format adjustment.
  176. if [ -z "$uncommitted_code" ]
  177. then
  178. git diff -U0 "$FORMAT_UPSTREAM_MERGE_BASE" -- $EXCLUDE | $CLANG_FORMAT_DIFF -i -p 1
  179. else
  180. git diff -U0 HEAD -- $EXCLUDE | $CLANG_FORMAT_DIFF -i -p 1
  181. fi
  182. echo "Files reformatted!"
  183. # Amend to last commit if user do the post-commit format check
  184. if [ -z "$uncommitted_code" ]; then
  185. echo -e "Would you like to amend the changes to last commit (`git log HEAD --oneline | head -1`)? (y/n): \c"
  186. read to_amend
  187. if [ "$to_amend" == "y" ]
  188. then
  189. git commit -a --amend --reuse-message HEAD
  190. echo "Amended to last commit"
  191. fi
  192. fi