So I’ve be wrestling with getting chained git hooks working. I’m surprised that Git doesn’t support this out-of-the-box as it seems very short-sighted. Here’s a Perl script I implemented to get this working:
#!/usr/bin/env perl
use strict;
use warnings;
use autodie;
use File::Temp qw( tempfile );
use IPC::Cmd qw( run );
if (@ARGV and $ARGV[0] eq 'wrapper') {
shift( @ARGV );
my $hook_filename = shift( @ARGV );
my $temp_filename = shift( @ARGV );
open( STDIN, '<', $temp_filename );
exec($hook_filename, @ARGV);
}
my $hook_type = $0;
$hook_type =~ s{^.+/}{};
my $git_dir = $ENV{GIT_DIR} || `git rev-parse --git-dir`;
chomp( $git_dir );
my $hook_dir = $git_dir . "/hooks";
opendir( my $dh, $hook_dir );
my @hooks = sort grep { /^${hook_type}_/ } readdir( $dh );
closedir( $dh );
my ($temp_fh, $temp_filename) = tempfile(UNLINK => 1);
while (my $line = <STDIN>) {
print $temp_fh $line;
}
close( $temp_fh );
foreach my $hook (@hooks) {
my ($success, $error) = run( command => [
$0, 'wrapper',
"$hook_dir/$hook", $temp_filename,
@ARGV,
]);
print join('', @$full_buf);
if (!$success) {
die "Error running hook: " . $hook . ": $error\n";
}
}
So, what you do is put this somewhere you can get at (perhaps just drop it in your hooks directory with the name “chained_hook”), chmod 755 it, and then symlink all your hooks to it, for example:
ln -s chained_hook applypatch-msg
ln -s chained_hook commit-msg
ln -s chained_hook post-commit
ln -s chained_hook post-receive
ln -s chained_hook post-update
ln -s chained_hook pre-applypatch
ln -s chained_hook pre-commit
ln -s chained_hook pre-rebase
ln -s chained_hook prepare-commit-msg
ln -s chained_hook update
Each of these chained hooks will now look for sub-hooks with the name “hookname_subhookname”. So, you could add a “post-receive_email” and “post-receive_foo” hooks and both will be called, in sorted order, by the post-receive hook.
So, I’m wondering - do people think this is a good solution? Are there better solutions? Is there a simpler way to write this script?