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