#!/usr/bin/perl # Some aspects of this approach are due to: # http://blog.bluefeet.net/2011/08/chained-git-hooks/ # see: http://web.archive.org/web/20111231092040/http://blog.bluefeet.net/2011/08/chained-git-hooks/ # # This implementation using IO::Pipe is # Copyright (C) 2020 Sean Whitton # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or (at # your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # Use by symlinking this hook to .git/hooks/pre-push (or whichever # hook you want to chain) and then add your hooks as # .git/hooks/pre-push_01foo, .git/hooks/pre-push_02bar etc. use strict; use warnings; use IO::Pipe; use File::Spec::Functions qw(catfile); (my $hook_type = $0) =~ s{^.+/}{}; my $config_hooks_path = `git config core.hooksPath`; chomp(my $hook_dir = $config_hooks_path || `git rev-parse --git-path hooks`); opendir(my $dirh, $hook_dir); my @hooks = sort grep /^${hook_type}_/, readdir $dirh; my @stdin = ; foreach my $hook (@hooks) { my $pipe = IO::Pipe->new; my $pid = fork; die "fork() failed: $!" unless defined $pid; if ($pid) { $pipe->writer; print $pipe $_ for @stdin; $pipe->close; wait; # give up as soon as one hook fails; since we sort the hooks, # we can put more expensive hooks later in the sequence die "hook $hook exited non-zero" if $?; } else { $pipe->reader; open STDIN, "<&=" . $pipe->fileno or die "couldn't open child's STDIN"; exec catfile($hook_dir, $hook), @ARGV; } }