Continuing my series of opinionated local development environment implementations, today I will document my git hooks configuration.
The two main contenders are pre-commit and git-hooks. I’ve settled on git-hooks, mostly because I prefer to assemble my own wrappers.
Installation
brew install git-hooks-go
Configuration
Hook scripts should be installed into each workspace that requires hooks:
git-hooks install
Templates can also be set for future clone
or init
use:
git config --global init.templatedir '<path>'
Where <path>
is a copy of the git-hooks scripts. ~/.git-templates
is a popular location. Existing repositories (without hooks) can have these template hooks applied via git init
.
Hooks
Hooks can exist at several scopes:
- Local hooks are executed from
$REPO/githooks/<hook>
- Global hooks are executed from
~/.githooks/<hook>
where <hook>
is a git action, such as pre-commit
Entrypoints in these paths must be executable. Here’s an example git diff-index whitespace check from my global pre-commit hooks:
$ cat ~/.githooks/pre-commit/whitespace.sh
#!/bin/sh
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
git diff-index --check --cached $against --
Here’s a similarly convoluted example for wrapping tools that are not git-aware:
$ cat githooks/pre-commit/shellcheck.sh
#!/usr/bin/env bash
set -efu -o pipefail
if git rev-parse --verify HEAD &> /dev/null
then
against=HEAD
else
against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
fi
( git diff-index --name-only $against | grep '\.sh$' || true ) | while read -r file ; do
if [[ -f "${file}" ]] ; then
shellcheck "${file}"
fi
done
Or something simpler, like golangci-lint
which is significantly more context-aware out of the box than shellcheck
:
$ cat githooks/pre-commit/go
#!/bin/sh
golangci-lint run
In both of the above cases, the paths referenced are symlinks to a common hook storage path, rather than copies.