From 5110818e0325e41108a795a6ed836a871cabfebb Mon Sep 17 00:00:00 2001 From: "Kyle J. McKay" Date: Mon, 1 Jan 2018 03:28:52 -0800 Subject: [PATCH] Notify.pm: support running custom notify hook Allow a custom notify hook to be configured (by default with the new $Girocco::Config::default_notifyhook config) and/or per-repository with the new girocco.notifyHook config. If configured, the hook will be run immediately before all other notify processing takes place for that particular batch of ref changes. All errors and non-0 exit status from running the notify hook are completely ignored and do not prevent the normal notifications that would come after it from being performed. Hook semantics are basically the same as for Git's post-receive hook with the addition of four command line arguments and extra "context" lines. The hook value gets interpreted and "run" similarly to how Git "runs" the value of the GIT_EDITOR and similar environment variables. Signed-off-by: Kyle J. McKay --- Girocco/Config.pm | 14 ++++++++++++++ Girocco/Notify.pm | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ Girocco/Project.pm | 13 ++++++++++++- jailsetup.sh | 16 +++++++++++++--- 4 files changed, 91 insertions(+), 4 deletions(-) diff --git a/Girocco/Config.pm b/Girocco/Config.pm index d5ffa65..2af0f9a 100644 --- a/Girocco/Config.pm +++ b/Girocco/Config.pm @@ -914,6 +914,20 @@ our $autogchack = 0; # the case. our $localhooks = 0; +# If this is set to a non-empty value it will become the default value for +# all repositories' girocco.notifyHook value. +# Whenever taskd.pl receives a batch of ref changes for processing, it first +# sends them off to any configured "girocco.notifyHook" (same semantics as +# a post-receive hook except it also gets four command-line arguments like +# so: cat ref-changes | notifyhook $projname $user $linecount $contextlinecount +# There is no default notify hook, but each repository may set its own by +# setting the `girocco.notifyHook` config value which will be eval'd by the +# shell (like $GIT_EDITOR is) with the current directory set to the +# repository's git-dir and the changes on standard input. +# Note that normal notification processing does not take place until after +# this command (if it's not null) gets run (regardless of its result code). +our $default_notifyhook = undef; + # UNIX group owning the repositories' htmlcache subdirectory # If not defined defaults to $owning_group # If gitweb access is provided but only on a read-only basis, then setting diff --git a/Girocco/Notify.pm b/Girocco/Notify.pm index 1087967..421fc5c 100644 --- a/Girocco/Notify.pm +++ b/Girocco/Notify.pm @@ -299,6 +299,58 @@ sub ref_changes { return if !@_ || ref($_[0]) ne 'ARRAY' || @{$_[0]} != 3 || !${$_[0]}[0] || !${$_[0]}[1] || !${$_[0]}[2]; + # run custom notify if present + # hook protocol is the same as for Git's post-receive hook where each + # line sent to the hook's stdin has the form: + # oldhash newhash fullrefname + # with the following additions: + # * four command line arguments are passed: + # 1. project name (e.g. "proj" "proj/fork" "proj/fork/sub" etc.) + # 2. "user" responsible for the changes + # 3. total number of lines coming on stdin + # 4. how many of those lines are "context" lines (sent first) + # * "context" lines are sent first before any actual change lines + # * "context" lines have the same format except oldhash equals newhash + # * "context" lines give the value unchanged refs had at the time + # the changes to the non-"context" refs were made + # * currently "context" lines are only provided for "refs/heads/..." + # * current directory will always be the repository's top-level $GIT_DIR + # * the PATH is guaranteed to find the correct Git, utils and $basedir/bin + # * the hook need not consume all (or any) of stdin + # * the exit code for the hook is ignored (just like Git's post-receive) + my $customhook; + if (($customhook = $proj->_has_notifyhook)) {{ + my @argv = (); + my $argv0; + if (is_shellish($customhook)) { + $argv0 = $Girocco::Config::posix_sh_bin || "/bin/sh"; + push(@argv, $argv0, "-c", $customhook.' "$@"'); + } else { + -f $customhook && -x _ or last; + $argv0 = $customhook; + } + my $username = ""; + $username = $user->{name} if $user && defined($user->{name}); + $username ne "" or $username = "-"; + push(@argv, $argv0, $proj->{name}, $username); + my @mod = grep({$$_[2] =~ m{^refs/.} && $$_[1] ne "" && $$_[2] ne "" && $$_[1] ne $$_[2]} @_); + my %modref = map({($$_[2] => 1)} @mod); + my %same = (); + do {do {$same{$_} = $$oldrefs{$_} if !exists($modref{$_})} foreach keys %$oldrefs} if $oldrefs; + my @same = (); + push(@same, [$same{$_}, $same{$_}, $_]) foreach sort keys %same; + push(@argv, @same + @mod, @same + 0); + chdir($proj->{path}) or last; + local $ENV{PATH} = util_path; + if (open my $hookpipe, '|-', @argv) { + local $SIG{'PIPE'} = sub {}; + print $hookpipe map("$$_[0] $$_[1] $$_[2]\n", @same, @mod); + close $hookpipe; + } else { + print STDERR "$proj->{name}: failed to run notifyhook \"$customhook\": $!\n"; + } + }} + # don't even try the fancy stuff if there are too many heads my $maxheads = int(100000 / (1 + length(${$_[0]}[0]))); my $newheadsdone = 0; diff --git a/Girocco/Project.pm b/Girocco/Project.pm index 61b14ad..dc93b91 100644 --- a/Girocco/Project.pm +++ b/Girocco/Project.pm @@ -105,6 +105,7 @@ our %propmapro = ( lastrefresh => ':lastrefresh', creationtime => '%girocco.creationtime', reposizek => '%girocco.reposizek', + notifyhook => '%girocco.notifyhook:undef', origurl => ':baseurl', ); @@ -1269,11 +1270,21 @@ sub has_bundle { return scalar($self->bundles); } +sub _has_notifyhook { + my $self = shift; + my $val = $Girocco::Config::default_notifyhook; + defined($self->{'notifyhook'}) and $val = $self->{'notifyhook'}; + return (defined($val) && $val ne "") ? $val : undef; +} + # returns true if any of the notify fields are non-empty sub has_notify { my $self = shift; # We do not ckeck notifycia since it's defunct - return $self->{'notifymail'} || $self->{'notifytag'} || $self->{'notifyjson'}; + return + $self->{'notifymail'} || $self->{'notifytag'} || + $self->{'notifyjson'} || + !!$self->_has_notifyhook; } sub is_empty { diff --git a/jailsetup.sh b/jailsetup.sh index 39aafcc..056730d 100755 --- a/jailsetup.sh +++ b/jailsetup.sh @@ -148,18 +148,27 @@ if ! [ -s etc/girocco/.gitconfig ]; then EOT fi # $1 => name, $2 => value, $3 => overwrite_flag +# if $3 is "2" and $2 is "" value will be unset update_config_item() { _existsnot= _oldval= _oldval="$(git config --file etc/girocco/.gitconfig --get "$1")" || _existsnot=1 - if [ -z "$_existsnot" ]; then + if [ -n "$_existsnot" ]; then + [ -n "$2" ] || [ "$3" != "2" ] || return 0 + else [ -n "$3" ] || return 0 - [ "$_oldval" != "$2" ] || return 0 + [ "$_oldval" != "$2" ] || { [ "$3" = "2" ] && [ -z "$2" ]; } || return 0 fi [ -n "$didchmod" ] || { chmod u+w etc/girocco; didchmod=1; } - git config --file etc/girocco/.gitconfig "$1" "$2" + if [ "$3" = "2" ] && [ -z "$2" ]; then + git config --file etc/girocco/.gitconfig --unset "$1" + else + git config --file etc/girocco/.gitconfig "$1" "$2" + fi if [ -n "$_existsnot" ]; then echo "chroot: etc/girocco/.gitconfig: config $1: (created) \"$2\"" + elif [ "$3" = "2" ] && [ -z "$2" ]; then + echo "chroot: etc/girocco/.gitconfig: config $1: (removed)" else echo "chroot: etc/girocco/.gitconfig: config $1: \"$_oldval\" -> \"$2\"" fi @@ -184,6 +193,7 @@ update_config_item http.lowSpeedLimit 1 update_config_item http.lowSpeedTime 600 update_config_item receive.advertisePushOptions false 1 update_config_item receive.maxInputSize "${cfg_max_receive_size:-0}" 1 +update_config_item girocco.notifyHook "${cfg_default_notifyhook}" 2 [ -z "$didchmod" ] || chmod a-w etc/girocco mkdir -p etc/sshkeys etc/sshcerts etc/sshactive -- 2.11.4.GIT