summaryrefslogtreecommitdiffstats
path: root/git-hooks/pre-commit.sh
diff options
context:
space:
mode:
Diffstat (limited to 'git-hooks/pre-commit.sh')
-rwxr-xr-xgit-hooks/pre-commit.sh125
1 files changed, 125 insertions, 0 deletions
diff --git a/git-hooks/pre-commit.sh b/git-hooks/pre-commit.sh
new file mode 100755
index 0000000..5cab632
--- /dev/null
+++ b/git-hooks/pre-commit.sh
@@ -0,0 +1,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
+