|
| 1 | +#!/bin/bash |
| 2 | +# run clang-format as a pre-commit hook. |
| 3 | +# |
| 4 | +# requires a specific path to clang-format be provided via git-config. |
| 5 | +# simply runs given clang-format with -style=file, expecting a .clang-format file |
| 6 | +# in the root of the repository. Format changes are automatically applied, but |
| 7 | +# any errors in this script result in commit failure. |
| 8 | +# |
| 9 | +# If reformatting the code undoes all the changes in the commit, then the commit will be blocked. |
| 10 | +# The only way around it is to use --no-verify. --allow-empty doesn't work because that |
| 11 | +# check happens prior to git calling the hook, and I don't know how to interrogate |
| 12 | +# the state of --allow-empty from inside the hook. |
| 13 | +# |
| 14 | +# this hook can be force-run on a segment of commits via rebase using exec. For example |
| 15 | +# this will replay and format all the commits on the current branch since commit c77fa657. |
| 16 | +# git rebase --strategy-option=theirs -x "git reset --soft HEAD~1 && git commit -C HEAD@{1}" --onto c77fa657 c77fa657 |
| 17 | +# |
| 18 | +# this trick suggested by: # https://www.dlyr.fr/stuff/2021/03/magic-rebase-and-format/ |
| 19 | +# |
| 20 | +# This hook has only been tested on Windows, and on Windows the path to clang-format should be a |
| 21 | +# Windows, not Linux format path, for example: |
| 22 | +# |
| 23 | +# >git config --local --add hooks.clangformat.path "c:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Tools\Llvm\bin\clang-format.exe" |
| 24 | +# |
| 25 | +# This should work on Windows and Linux (not-verified) if hooks.clangformat.path is set to "clang-format" |
| 26 | +# with clang-format already on your path. |
| 27 | +# |
| 28 | +# Redirect output to stderr. |
| 29 | +exec 1>&2 |
| 30 | +# fail commit if hook fails |
| 31 | +set -e |
| 32 | + |
| 33 | +CLANG_FORMAT=$(git config --get hooks.clangformat.path) |
| 34 | +if [ -z "${CLANG_FORMAT}" ]; then |
| 35 | + echo A path to clang-format must be set in hooks.clangformat.path |
| 36 | + exit 1 |
| 37 | +fi |
| 38 | + |
| 39 | +format_file() { |
| 40 | + file="${1}" |
| 41 | + echo "formatting ${file}" |
| 42 | + if [ -f $file ]; then |
| 43 | + # move working dir file out of the way |
| 44 | + mv ${file} ${file}.working |
| 45 | + # unstage the changes to be committed from the index |
| 46 | + git restore --worktree ${file} |
| 47 | + # and format it. |
| 48 | + "${CLANG_FORMAT}" -i --style=file ${file} |
| 49 | + # add back to index |
| 50 | + git add ${file} |
| 51 | + # replace pending worktree changes |
| 52 | + mv ${file}.working ${file} |
| 53 | + fi |
| 54 | +} |
| 55 | + |
| 56 | +for file in `git diff-index --cached --name-only HEAD | grep -iE '\.(cpp|cc|c|h|hpp|inl)$' ` ; do |
| 57 | + format_file "${file}" |
| 58 | +done |
| 59 | + |
| 60 | +# after formatting there may be no remaining (staged) changes |
| 61 | +# so check and abort commit if nothing remains. |
| 62 | +set +e |
| 63 | +# Assume something remains |
| 64 | +EXIT_CODE=0 |
| 65 | +# sets $? to 1 if anything is different |
| 66 | +git diff-index --cached --exit-code HEAD |
| 67 | +if [ $? -eq 0 ]; then |
| 68 | + # nothing remains, fail hook |
| 69 | + echo No changes remain after auto-format hook. Aborting commit... |
| 70 | + EXIT_CODE=1 |
| 71 | +fi |
| 72 | +exit ${EXIT_CODE} |
0 commit comments