33

As many of you probably know, there can be only one hook type in git. If two update hooks need to be evaluated. The git admin is left with two unmanageable solutions:

  1. Merge the hook scripts together
  2. Manually chain them with an exec

I am looking for an elegant solution (written in BASH),something like a folder hooks/update.d or hooks/post-receive.d that will allow the loosely coupling of hook evaluations. The chaining should stop as soon as a hook fails.

I actually found an acceptable solution written in perl at this URL http://blog.bluefeet.net/2011/08/chained-git-hooks

The problem: my server runs different versions of perl and I am getting perllib version mismatches. It fails.

Sebastian Paaske Tørholm
  • 49,493
  • 11
  • 100
  • 118
Olivier Refalo
  • 50,287
  • 22
  • 91
  • 122
  • 1
    Related answer: http://stackoverflow.com/a/3464399/119963 The focus there was on tracking the hooks, not chaining them, but chaining is basically a trivial extension: wrap a loop around the hook execution (e.g. `for hook in hooks/update.d/*; do ...`) – Cascabel Jan 04 '12 at 18:03

3 Answers3

33

After further investigation and testing, here is a working solution:

create file .git/hooks/hook-chain as follows

#!/bin/bash
#
# author: orefalo

hookname=`basename $0`


FILE=`mktemp`
trap 'rm -f $FILE' EXIT
cat - > $FILE

for hook in $GIT_DIR/hooks/$hookname.*
do
    if test -x "$hook"; then
#       echo $hook
        cat $FILE | $hook "$@"
        status=$?

        if test $status -ne 0; then
            echo Hook $hook failed with error code $status
            exit $status
        fi
    fi
done

Now link any hook that requires chaining, for instance

  • ln -s hook-chain update
  • ln -s hook-chain post-receive

finally, create the chains by renaming them as hookname.action

 -rwxr-xr-x. 1 git  git  6710  functions
 -rwxr-xr-x. 1 git  git   280  hook-chain
 -rwxr-xr-x. 1 git  git  1524  post-mirror
 lrwxrwxrwx. 1 root root   10  post-receive -> hook-chain
 -rwxr-xr-x. 1 git  git  8763  post-receive.1email
 -rwxr-xr-x. 1 git  git  1745  post-receive.2github
 -rwxr-xr-x. 1 git  git   473  post-upload-pack
 -rwxr-xr-x. 1 git  git   346  pre-receive
 lrwxrwxrwx. 1 root root   10  update -> hook-chain
 -rwxr-xr-x. 1 git  git  2975  update.1acl
 -rwxr-xr-x. 1 git  git   328  update.2github

for instance, in the sample above, the update hook will run update.1acl followed by update.2github.

The post-receive hook with run post-receive.1email followed by post-receive.2github

Olivier Refalo
  • 50,287
  • 22
  • 91
  • 122
  • 4
    This isn't a code review site, but....rather than explictly removing the tmpfile, remove it automatically by adding the following line before the call to mktemp: trap 'rm -f $FILE' 0 – William Pursell Jan 04 '12 at 22:48
  • 1
    In [this mail](http://osdir.com/ml/git/2009-01/msg00308.html), instead of a temp file someone does `data=$(cat)` and then `echo "$data" | "$hook"`. – Henrik N Jun 19 '12 at 20:11
  • 1
    I have [a version of this script](https://github.com/henrik/dotfiles/blob/master/git_template/hooks/pre-commit) based on [the mail I mentioned](http://osdir.com/ml/git/2009-01/msg00308.html). The major diff is that mine runs *all* hooks before exiting, so you know exactly what you're skipping if you choose to bypass the hooks. – Henrik N Jun 19 '12 at 20:16
  • 1
    I like your version, please post the complete solution as an answer. – Olivier Refalo Jun 20 '12 at 02:06
3

For those who're not willing to click on every link in comments below other answer, here's a practically unmodified version of the script by @HenrikN:

#!/bin/bash

# Runs all executable hookname-* hooks and exits after,
# if any of them was not successful.
#
# Based on
# http://osdir.com/ml/git/2009-01/msg00308.html

data=$(cat)
exitcodes=()
hookname=$(basename $0)

# Run each hook, passing through STDIN and storing the exit code.
# We don't want to bail at the first failure, as the user might
# then bypass the hooks without knowing about additional issues.

for hook in $GIT_DIR/hooks/$hookname-*; do
  test -x "$hook" || continue
  echo "$data" | "$hook"
  exitcodes+=($?)
done

# If any exit code isn't 0, bail.

for i in "${exitcodes[@]}"; do
  [ "$i" == 0 ] || exit $i
done
Community
  • 1
  • 1
sanmai
  • 29,083
  • 12
  • 64
  • 76
0

I created a shell script based on OP and Olivier Refalo posts (with some modifications):

https://gist.github.com/amirbawab/e9f42ef8d441316707d9b90777e5718b

The script will generate a hook files that will execute scripts inside $hook_file_name.d/$hook_file_name.*

For example: for commit-msg hook, the script will generate commit-msg file under .git/hooks that will execute all script under commit-msg.d/.

Files under commit-msg.d should match the pattern commit-msg.* to allow placing helper shell files in those folder without executing them.

Script usage: ./extend_git_hooks.sh [REPO_PATH]

CMPS
  • 7,733
  • 4
  • 28
  • 53