From 829ed8564c6faf93fac627f0f39c5065a45f0130 Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Thu, 5 Nov 2009 00:41:03 +0100 Subject: [PATCH] Girocco::Notify: Introduce, add ref-change support to taskd Currently, the mail.sh and json notifications are supported. There is no web interface to set them up yet. --- Girocco/Notify.pm | 148 +++++++++++++++++++++++++++++++++++++++++++++++++++++ Girocco/Project.pm | 6 +++ TODO | 7 +-- taskd/mail.sh | 8 ++- taskd/taskd.pl | 25 +++++++-- 5 files changed, 185 insertions(+), 9 deletions(-) create mode 100644 Girocco/Notify.pm diff --git a/Girocco/Notify.pm b/Girocco/Notify.pm new file mode 100644 index 0000000..00c08de --- /dev/null +++ b/Girocco/Notify.pm @@ -0,0 +1,148 @@ +package Girocco::Notify; + +use strict; +use warnings; + +BEGIN { + use Girocco::Config; + use JSON; + use LWP::UserAgent; +} + + +# This Perl code creates json payload within post-receive hook. + +sub json_commit { + my ($proj, $commit) = @_; + + my @gcmd = ($Girocco::Config::git_bin, '--git-dir='.$proj->{path}); + my $fd; + + open $fd, '-|', @gcmd, 'log', '-1', '--pretty=format:%ae %an%n%ai%n%s%n%n%b', $commit + or die "cannot do git log: $! $?"; + my @l = <$fd>; + chomp @l; + close $fd; + my ($ae, $an) = ($l[0] =~ /^(.*?) (.*)$/); + my ($ai) = $l[1]; + my $msg = join("\n", splice(@l, 2)); + # Up to three trailing newlines in case of no body. + chomp $msg; chomp $msg; chomp $msg; + + my ($rf, $af, $mf) = ([], [], []); + open $fd, '-|', @gcmd, 'diff-tree', '--name-status', '-r', "$commit^", $commit + or die "cannot do git diff-tree: $! $?"; + while (<$fd>) { + chomp; + my ($s, $file) = split(/\t/, $_); + if ($s eq 'M') { + push @$mf, $file; + } elsif ($s eq 'A') { + push @$af, $file; + } elsif ($s eq 'R') { + push @$rf, $file; + } + } + close $fd; + + return { + "removed" => $rf, + "message" => $msg, + "added" => $af, + "timestamp" => $ai, + "modified" => $mf, + "url" => $Girocco::Config::gitweburl."/".$proj->{name}.".git/commit/".$commit, + "author" => { "name" => $an, "email" => $ae }, + "id" => $commit + }; +} + +sub json { + my ($url, $proj, $ref, $oldrev, $newrev) = @_; + + my $commits = []; + + foreach my $commit (get_commits($proj, $ref, $oldrev, $newrev)) { + push @$commits, json_commit($proj, $commit); + } + + my $payload = encode_json { + "before" => $oldrev, + "after" => $newrev, + "ref" => $ref, + + "repository" => { + "name" => $proj->{name}, + # Girocco extension: full_name is full project name, + # equivalent to GitHub's "owner[name]/name". + "full_name" => $proj->{name}.".git", + + "url" => $Girocco::Config::gitweburl.'/'.$proj->{name}.".git", + # Girocco extension: Pull URL. + "pull_url" => $Girocco::Config::gitpullurl.'/'.$proj->{name}.".git", + + "owner" => { "name" => "", "email" => $proj->{email} } + }, + + "commits" => $commits + }; + + my $ua = LWP::UserAgent->new; + $ua->timeout(5); + $ua->post($url, { payload => $payload }); +} + + +sub get_commits { + my ($proj, $ref, $oldrev, $newrev) = @_; + + my @gcmd = ($Girocco::Config::git_bin, '--git-dir='.$proj->{path}); + my $fd; + + open $fd, '-|', @gcmd, 'for-each-ref', '--format=%(refname)', 'refs/heads/' + or die "cannot do git for-each-ref: $! $?"; + my @refs = grep { $_ ne $ref } <$fd>; + chomp @refs; + close $fd; + + my @revlims; + if (@refs) { + print "--not @refs\n"; + open $fd, '-|', @gcmd, 'rev-list', '--not', @refs + or die "cannot do git rev-list for revlims: $! $?"; + @revlims = <$fd>; + chomp @revlims; + close $fd; + } + + my $revspec = (($oldrev =~ /^0+$/) ? $newrev : "$oldrev..$newrev"); + open $fd, '-|', @gcmd, 'rev-list', @revlims, $revspec + or die "cannot do git rev-list: $! $?"; + my @revs = <$fd>; + chomp @revs; + close $fd; + + return @revs; +} + + +sub ref_change { + my ($proj, $ref, $oldrev, $newrev) = @_; + + chdir($proj->{path}); + + # First, possibly send out various mails + if ($proj->{notifymail}) { + system($Girocco::Config::basedir.'/taskd/mail.sh', + "$ref", "$oldrev", "$newrev") + and warn "mail.sh failed"; + } + + # Next, send JSON packet to given URL if enabled. + if ($proj->{notifyjson}) { + json($proj->{notifyjson}, $proj, $ref, $oldrev, $newrev); + } +} + + +1; diff --git a/Girocco/Project.pm b/Girocco/Project.pm index 5a69969..9ac71c0 100644 --- a/Girocco/Project.pm +++ b/Girocco/Project.pm @@ -36,6 +36,8 @@ our %propmap = ( desc => 'description', README => 'README.html', hp => ':homepage', + notifymail => '%hooks.mailinglist', + notifyjson => '%hooks.jsonurl', ); sub _property_path { @@ -51,6 +53,8 @@ sub _property_fget { $pname or die "unknown property: $name"; if ($pname =~ s/^://) { return `git --git-dir="$self->{path}" config "gitweb.$pname"` + } elsif ($pname =~ s/^%//) { + return `git --git-dir="$self->{path}" config "$pname"` } open P, $self->_property_path($pname) or return undef; @@ -68,6 +72,8 @@ sub _property_fput { $value ||= ''; if ($pname =~ s/^://) { return `git --git-dir="$self->{path}" config "gitweb.$pname" "$value"` + } elsif ($pname =~ s/^%//) { + return `git --git-dir="$self->{path}" config "$pname" "$value"` } my $P = lock_file($self->_property_path($pname)); diff --git a/TODO b/TODO index cf340b3..d1d509b 100644 --- a/TODO +++ b/TODO @@ -1,8 +1,3 @@ -* GitHub Hooks: - - GitHub uses a general pluggable scheme of hooks, providing mail and - CIA notifications and such. We could reuse their codebase. - * Ratelimiting mail notifications * Captcha instead of the Sun-check @@ -12,6 +7,8 @@ * Fixup daemon instead of cronjob +* Add GitHub hooks suite to the available notifications + [Maybe?] diff --git a/taskd/mail.sh b/taskd/mail.sh index 2a66063..27b7b17 100755 --- a/taskd/mail.sh +++ b/taskd/mail.sh @@ -7,6 +7,12 @@ # change being reported. The rule is that (for branch updates) each commit # will appear on one email and one email only. # +# ================= +# This is Girocco-customized version. No matter what is said below, it has +# following changes: +# * Calling with arguments is same as giving them on stdin. +# ================= +# # This hook is stored in the contrib/hooks directory. Your distribution # will have put this somewhere standard. You should make this script # executable then link to it in the repository you would like to use it in. @@ -680,7 +686,7 @@ if [ -n "$1" -a -n "$2" -a -n "$3" ]; then # Output to the terminal in command line mode - if someone wanted to # resend an email; they could redirect the output to sendmail # themselves - PAGER= generate_email $2 $3 $1 + generate_email $2 $3 $1 | send_mail else while read oldrev newrev refname do diff --git a/taskd/taskd.pl b/taskd/taskd.pl index 264ac84..35dafd6 100755 --- a/taskd/taskd.pl +++ b/taskd/taskd.pl @@ -10,21 +10,28 @@ # to .clonelog within the repository. In case the clone fails, # .clone_failed is touched and .clone_in_progress is removed. -# Protocol: +# Clone protocol: # Alice sets up repository and touches .cloning # Alice opens connection to Bob # Alice sends project name through the connection -# Bob opens the repository and sends back 0 if ok, error code otherwise +# Bob opens the repository and sends error code if there is a problem # Bob closes connection -# Alice polls .clonelog in case of 0. +# Alice polls .clonelog in case of success. # If Alice reads "@OVER@" from .clonelog, it stops polling. +# Ref-change protocol: +# Alice opens connection to Bob +# Alice sends ref-change command for each changed ref +# Alice closes connection +# Bob sends out notifications + # Based on perlipc example. use strict; use warnings; use Girocco::Config; +use Girocco::Notify; use Girocco::Project; use Socket; @@ -84,6 +91,16 @@ sub clone { exec $Girocco::Config::basedir.'/taskd/clone.sh', "$name.git" or die "exec failed: $!"; } +sub ref_change { + my ($arg) = @_; + my ($name, $oldrev, $newrev, $ref) = split(/\s+/, $arg); + my $proj = Girocco::Project->load($name); + $proj or die "failed to load project $name"; + print STDERR "ref-change $name ($ref: $oldrev -> $newrev)\n"; + open STDIN, "