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.

1
  • 1
    Related answer: 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
    Commented Jan 4, 2012 at 18:03

3 Answers 3

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

4
  • 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 Commented Jan 4, 2012 at 22:48
  • 1
    In this mail, instead of a temp file someone does data=$(cat) and then echo "$data" | "$hook".
    – Henrik N
    Commented Jun 19, 2012 at 20:11
  • 1
    I have a version of this script based on the mail I mentioned. 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
    Commented Jun 19, 2012 at 20:16
  • 1
    I like your version, please post the complete solution as an answer. Commented Jun 20, 2012 at 2: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
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]

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.