summaryrefslogtreecommitdiffstats
path: root/git-hooks/pre-commit.sh
blob: 5cab632be330d8d530455a94041a1f9dbddf9b55 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
#!/bin/bash
set -eEu

# Path to clang-format. If it's in your PATH, "clang-format" is fine.
# If you do not have it (or the version is too old), see USE_DOCKER.
CLANG_FORMAT="/usr/local/bin/clang-format"

# Set to "yes" if you want to create and use a docker container
# for clang-format.
USE_DOCKER="no"

# Set this to "yes" if you typically commit from the command line
# and wish to be prompted on whether to apply any patches
# automatically.
INTERACTIVE="yes"

# Leave this stuff
STASH_NAME="pre-commit-$(date +%s)"
CONTAINER_NAME="clang-format-$(date +%s)"
CLANG_FORMAT_VERSION="11.1.0"

apply_patch() {
  local patchfile=$1
  git apply --index "$patchfile"
  rm -f "$patchfile"
  exit 0
}

stash() {
  git stash save -q --keep-index "$STASH_NAME"
}

unstash() {
  if git stash list | head -n 1 | grep -Fq "$STASH_NAME"; then
    git stash pop -q || true
  fi
}

cleanup() {
  ec=$?
  unstash
  if [ $USE_DOCKER == "yes" ]; then
    docker kill "$CONTAINER_NAME" >/dev/null 2>&1 || true
  fi
  if [ $ec -ne 0 ]; then
    echo Aborted.
  fi
  exit $ec
}

if [ $USE_DOCKER == "yes" ]; then
  if [ ! "$(docker images clang-format --format '{{.Repository}}')" ]; then
    echo "Creating docker image for clang-format (one time only)..."
    docker run --name=clang-format alpine:latest apk --no-cache add clang >/dev/null 2>&1
    docker commit clang-format clang-format >/dev/null 2>&1
    docker rm clang-format
  fi
  CLANG_FORMAT="docker exec $CONTAINER_NAME clang-format"
  docker run --rm --name "$CONTAINER_NAME" -t -d -v "$(git rev-parse --show-toplevel)":/src -w /src clang-format tail -f /dev/null >/dev/null 2>&1
  # alternatively (slower for multiple files)
  #CLANG_FORMAT="docker run --rm -v $(git rev-parse --show-toplevel):/src -w /src clang-format clang-format"
fi

if git rev-parse --verify HEAD >/dev/null 2>&1
then
	against=HEAD
else
	# Initial commit: diff against an empty tree object
	against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
fi

exec 1>&2

$CLANG_FORMAT -version | grep -Fq "$CLANG_FORMAT_VERSION" || (echo Incorrect version of clang-format.; exit 1)

patchfile=$(mktemp -t git-clang-format.XXXXXX.patch)
stash
trap "cleanup" SIGHUP SIGINT SIGTERM EXIT ERR

git diff-index --cached --diff-filter=ACMR --name-only $against -- | grep "\.[ch]$" | while read -r file;
do
  $CLANG_FORMAT -style=file "$file" |     \
    diff -u "$file" - |                     \
    sed -e "1s|--- |--- a/|" -e "2s|+++ -|+++ b/$file|" >> "$patchfile"

  # cat is just here to ignore the exit status of diff
  $CLANG_FORMAT -style=file "$file" | diff -u "$file" - | cat
done
unstash

if [ ! -s "$patchfile" ]; then
  rm -f "$patchfile"
  exit 0
fi

echo
echo Formatting changes requested.
echo "See $patchfile"
echo

if ! git apply --index --check "$patchfile"; then
  echo You may have unstaged changes to files that require formatting updates.
  echo It is not safe for this script to apply the patch automatically.
  exit 1
fi

if [ $INTERACTIVE == "yes" ]; then
  while true; do
    exec < /dev/tty
    read -rp "Apply now (y/n)? " yn
    exec <&-
    case $yn in
      [Yy]* ) apply_patch "$patchfile"; exit 0;;
      [Nn]* ) break;;
      * ) echo Please answer yes or no. ;;
    esac
  done
fi

echo
echo You should manually apply the patch with:
echo "git apply --index \"$patchfile\""
echo "Or use another method, and then restage."
exit 1