Gitorious: Custom post-receive hook

With version 2.3, Gitorious got an official way to install custom server-side hooks. My new article shows what to do now.

This blog post is outdated and provided for historical purposes only.

Before using Gitorious, we deployed our applications by merging changes into a special sync-branch and pushing it to the server that hosted the git repositories. A post-receive shell script checked if that branch was received and started the deployment to the web server. Now we switched to Gitorious to make repository and user permission administration easier - but lost the ability to use our own post-receive hook scripts, because Gitorious installs its own hook scripts to i.e. get immediately updated when changes are coming in. (Yes, git calls only one post-receive script)

Not having post-receive scripts is no option since deploying applications manually is totally lame and error-prone, so I looked for a solution. Gitorious offers web hooks, but that's not the best solution since it adds 2 new layers to the process: Gitorious to send the hook request, and the remote web server that also needs to run, have the correct permissions etc.

The only acceptable solution left was using an own post-receive hook that calls the different hooks - the gitorious one first and our own hook afterwards. Git pipes information about the received data into the hook script, so it's not hard to fetch that information and distribute it to serveral scripts - stackoverflow had an answer. The script is not optimal and has some flaws, but you get the idea.

Preparing hooks/

The hooks directory in each git repo is just a symlink to $gitorious-repo-dir/.hooks which itself is a symlink to $gitorious-install-dir/data/hooks/. Unfortunately, just making a copy of the hooks directory does not work because Gitorious' post-receive script breaks then - it expects the git repo hooks/ directory to be a symlink. After some hours of investigation, I finally found a solution:

The first thing to note is that Gitorious itself uses three hooks only, the ones that are executable:

$ ls -la hooks/
total 64
drwxr-xr-x 2 git git 4096 Apr  9 21:02 .
drwxr-xr-x 4 git git 4096 Feb 16 22:39 ..
-rw-r--r-- 1 git git  441 Feb 16 22:39 applypatch-msg
-rw-r--r-- 1 git git  781 Feb 16 22:39 commit-msg
lrwxrwxrwx 1 git git    6 Feb 16 22:39 .hooks -> .hooks
-rw-r--r-- 1 git git 1869 Feb 16 22:39 messaging.rb
-rw-r--r-- 1 git git  152 Feb 16 22:39 post-commit
-rwxr-xr-x 1 git git 1382 Feb 16 22:39 post-receive
-rwxr-xr-x 1 git git  207 Feb 16 22:39 post-update
-rw-r--r-- 1 git git  388 Feb 16 22:39 pre-applypatch
-rw-r--r-- 1 git git 1696 Feb 16 22:39 pre-commit
-rw-r--r-- 1 git git 4262 Feb 16 22:39 pre-rebase
-rwxr-xr-x 1 git git 3519 Feb 16 22:39 pre-receive
-rw-r--r-- 1 git git 2346 Feb 16 22:39 pre_receive_guard.rb
-rw-r--r-- 1 git git  233 Feb 16 22:39 rails_env.rb
-rw-r--r-- 1 git git 1949 Feb 16 22:39 update

Yes, it's only post-receive, post-update and pre-receive. Both post-update and pre-receive have no problems being in different directories or not being in the correct symlinked hooks dir, so we can symlink them:

$ mv hooks hooks.original
$ mkdir hooks
$ cd hooks
$ ln -s ../../../../.hooks/pre-receive
$ ln -s ../../../../.hooks/post-update
$ ln -s ../../../../.hooks hooks.gitorious
$ ln -s ../../../../.hooks/pre_receive_guard.rb
$ cp ../../../../.hooks/post-receive post-receive
$ emacs post-receive

The post-receive file needs to be patched a bit so it works with the new directory structure:

$ diff -u0  ../../../../.hooks/post-receive  post-receive
--- ../../../../.hooks/post-receive2011-02-16 22:39:30.000000000 +0100
+++ post-receive.gitorious2011-04-09 22:13:44.000000000 +0200
@@ -20 +20 @@
-incpath = File.dirname(__FILE__)
+incpath = File.dirname(__FILE__)  + "/hooks.gitorious"
@@ -28 +28 @@
-gitdir = File.expand_path(File.join(incpath, ".."))
+gitdir = File.expand_path(File.join(incpath, "../.."))

Pushing should still work now, even after all the changes.

post-receive hub

We localized the hooks but still need a solution to use several post-receive hooks. The idea is that a post-receive hub script is called by git, and it calls every executable post-receive.* script:

$ mv post-receive post-receive.gitorious
$ touch post-receive
$ chmod +x post-receive
$ emacs post-receive

Now input the following:

#!/bin/sh
FILE="`mktemp`"
cat - > "$FILE"
for i in `ls hooks/post-receive.*`; do
    [ -x "$i" ] && cat "$FILE" | "$i"
done
rm "$FILE"

Try to push. It should still work!

Custom demo hook script

To verify that we're really able to use custom scripts, just create one named post-receive.echodate with the following contents:

#!/bin/sh
echo Current date: `date`

Don't forget to make it executable. When all worked, you'll get the following output when pushing:

 Syncing Gitorious... [OK]
...
]]>

Written by Christian Weiske.

Comments? Please send an e-mail.