Keeping Git Hooks in Sync
Git hooks let developers automate expectations and workflows–terrific news for supporting ease-of-use and consistency across a development team. Unfortunately, git doesn’t automatically synchronize hooks between project contributors. I can set up git hooks for my own workflow, but I need some extra help to share them with the rest of my team.
Fortunately, git hook synchronization is an easy (and in later versions of git, almost trivial) problem to solve.
Bootstrapping
First, we’ll need an easy way for other developers to configure their
checked-out copy of the project. I keep project-specific tooling in a ./dev
directory as a matter of convention, and one of the first files to go in itis a
./dev/bootstrap.sh
script. This script ensures that the remotes, secrets, and
services the project depends on are all set up correctly. It’s also a good place
to set up our hooks:
#!/bin/bash
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd "$DIR/.."
log() {
echo "$(basename ${BASH_SOURCE[0]}): $@"
}
install_hooks() {
# See alternative installs below
}
log 're/configuring hooks...'
install_hooks
Fair enough: a bit of bash
boilerplate and an install_hooks
stub for us to start filling in.
The Easy Way
If you can count on all of the project’s contributors to use git
>= 2.9, git
itself includes a built-in solution to our problem.
install_hooks() {
git config core.hooksPath \
|| git config core.hooksPath ./dev/hooks
}
In other words, point
core.hooksPath
at ./dev/hooks
and skip the rest of this article. If there’s the tiniest
chance that someone might have an older version of git, however, read on.
The Slightly Less Easy Way
Our next option is to symlink the ./dev/hooks
directory into .git/hooks
and
let the operating system keep them in sync. At this point the repository should
follow:
$ tree dev
dev
├── bootstrap.sh
└── hooks
└── ...
We can install ./dev/hooks
by clobbering .git/hooks
and linking our version
in.
install_hooks() {
rm -rf ./.git/hooks
ln -s ./hooks ./.git/hooks
}
The catch is that symlink behavior–less than obvious at the best of times–may end tragically for development teams working across platforms. Tread carefully.
Brute Force
The final option is to copy around the hooks ourselves. This time, we’ll warm
up ./dev/hooks
with a script that re-applies ./dev/bootstrap.sh
when the git
index changes. This can happen at various stages of the workflow, but I
typically use post-checkout
:
#!/bin/bash
#
# ./dev/hooks/post-checkout
$(git rev-parse --show-toplevel)/dev/bootstrap.sh $@
The final variation on install_hooks()
takes a hammer to the entire works by
copying changes to our custom hooks over the top of .git/hooks
:
install_hooks() {
git diff --quiet $1 $2 './dev/hooks' 2> /dev/null || {
rm -rf ./.git/hooks
cp -r ./dev/hooks ./.git/hooks
}
}
All that’s left is to install the scripts for the first time:
$ ./dev/bootstrap.sh
bootstrap.sh: re/configuring hooks...
Now we’re cooking with gas! Better yet, other contributors will now get to join in the fun. Add new hooks or edit the ones you’ve got, commit them, and watch them propagate out to the rest of the team.