How to chain multiple git hooks with bash

If you spend any amount of time with git hooks, you learn pretty quickly that There Can Be Only One. Git doesn’t provide a way to call multiple scripts from a single hook.

Don’t get me wrong. I’m not complaining about any lack of functionality. In fact I think it’s a great example of clearly defined scope. Git provides a basic hook mechanism. What you do with it is your business. End of story.

Still, the problem you eventually run in to is that you either have to choose between which 3rd party hook script you require more, or you need to try to lace multiple scripts together into a single file. This can be tough if you’re not familiar with the language used in the hook script, or even worse, if the two scripts you need to combine are written in different languages.

But with a little bit of bash and some cleverly placed symlinks, you can run as many pre-receive, post-receive, or whatever type of hook scripts you need. This allows hooks from different authors in different languages to co-exist peacefully and independently.  It also allows you to keep your hook scripts small and dedicated to a particular purpose, which can in turn make them highly reusable.

This article will get you up and running with what’s called Hook Chaining in less than 10 minutes.

The first thing to do (if you haven’t already done so) is to remove all the sample scripts created by git. They will cause problems if you leave them in place with what we’re about to do:

~/myrepo/hooks$ rm *.sample

If you want to see the sample scripts again, just create a new repo. They get created automatically.

Rename your exiting hook script from hook-type to hook-type.purpose. For example, if you have a server side hook that’s sending out emails after new pushes, you’d rename it like so:

~/myrepo/hooks$ mv post-receive post-receive.notify-developers

Download the contents of the gist I created to a new file called hook-chain, and set it as executable:

~/myrepo/hooks$ wget -O hook-chain
~/myrepo/hooks$ set +x hook-chain

I’ve shortened the URL to the real gist above for the sake of readability here. The gist has lots of inline comments that will step you through what’s happening, if you have the time or  interested to look under the hood.

The last step is to create a symbolic link from where your old hook was to the new hook-chain script:

~/myrepo/hooks$ ln -s hook-chain post-receive

At any point, you can bring in the other hook(s) you’ve been wanting to use. Name them using the same scheme: the original git hook name, plus a dot, plus a meaningful string.

Your hooks directory should now look like this:

2016-05-06 23_42_49

And that’s all there is to it. The next time a hook gets called, all of its corresponding sub-scripts (in this case, the two post-receive scripts) will be run in alphabetical order, provided they are also marked as executable.

If any of the sub-scripts fail or return an error code, the parent hook-chain script will remember that error code, and after all the rest of the sub-scripts have finished, it will return the failed code back to git. You could just as easily modify hook-chain to skip the rest of the sub-scripts if one fails, but that’s up to you.

The same hook-chain script can be used to impersonate any valid git hook (server-side, or client-side). Just go through the same steps again for each hook you need to run multiple scripts for. Rename your hook to hook-type.purpose, and then create a symbolic link to hook-chain using the original hook name. You’ll be able to stack up however many separate scripts as you like for each hook type.