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
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.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 https://goo.gl/WSz3Jt -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
~/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:
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.
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.