Git hooks can be very convenient to keep your commit log clean. Here’s a way to install and share git hooks across your team when you are working in a dockerized environment
TL;DR: a demo project on github is available
1. Git hooks in a nutshell
Git provides a way to run commands before you do something. A common usage would be to run your linters before you try to commit something, for instance. If the linter detects any errors, then you are asked to fix it before you actually commit your work. This would typically clean your git log from commits such as “fixed lint”.
If you are new to this concept, you should at least know that:
- git hooks are script that are automatically found by git if present under the
.git/hooks
directory - a git hook is run locally by your computer
- When a git hook fails, then your git command is not run.
You might want to take a look at git samples that are already present in any git project:
What you get after any `git init`
Simply copy pre-commit.sample
to pre-commit,
make it executable, and here you are, you just installed your first working git hook !
2. Using githooks as a team
Now you probably know that if you put some scripts in your .git/hooks
directory, then your teammates won’t be able to take advantage of it. You’re basically the only one to know about your script, and you can’t guarantee than any commit of your team passes the githook test you just wrote.
Tools such as husky can help you to automatically install githooks when working directly with npm/yarn. But this is 2017 and we want to work with docker, don’t we ?
Here’s what we are going to do:
- Create a
hooks
directory at the root of the project, put some scripts inside, and commit them. - Create a docker container whose purpose is to create a symlink of the official hook script into your local
.git/hooks
directory - Declare this container in your main
docker-compose.yml.
- When anyone run
docker-compose up,
the hook container will ensure that the official hooks are properly installed in your configuration.
Now let’s do this.
First, we create a docker container, defined by some Dockerfile.githook:
The idea behind this is quite simple. It basically creates a container that will:
cd /tmp/hooks && ls | xargs chmod +x
: go to some /tmp/hooks (explanations about this particular directory will come further), find scripts, and make them executablecd /tmp/.git/hooks && find ../../hooks -type f -exec ln -sf {} /tmp/.git/hooks/\;
Now go to some/tmp/.git/hooks
dir, find the executables, and create symlinks to this directory
So this container is basically creating symlinks from its own /tmp/hooks
to its own /tmp/.git/hooks.
The last thing we need is to share our own .git/hooks
and hooks
directories with the container. This way, when the container creates its own symlinks, then it will actually be doing this on our computer. Which is quite exactly what we are looking for.
This can be achieved by using volumes in your docker-compose.yml
file:
This will:
- declare a
githook_installer
container in your main project. - use busybox: a tiny image that know about basic unix commands such as
ls, xargs, find, ln...
- Mount your local
.git
directory into the container’s/tmp.git
directory - Mount your local
./hooks
directory into the container’s/tmp/hooks
directory - boot everytime you run
docker-compose up.
Note that this will actually create the symlinks every single time you run your project. But creating a bunch of symlinks that already exist is absolutely not an issue; you won’t even notice it.
3. Now let’s have a demo, shall we ?
I’ve created a demo on github for those who are interested by the result
Let’s create a lunatic pre-commit script in the ./hooks
directory:
Now run docker-compose up,
as anyone would do when working with docker.
And try to commit something. You’ll either get:
pre-commit hook startingOkay, I will accept your commitOn branch masternothing to commit, working directory clean
Or
pre-commit hook startingMeh. Maybe another time
Not that if your commit is rejected, then the git log remains untouched.
Perhaps you’ll want to write a more useful pre-hook now. docker-compose run linter
could surely be a good idea, provided you created some linter
container in your configuration !