Revision 11e8869836a20f39871134035fdb50096f4d7dc4 authored by Paul Zimmermann on 20 February 2014, 14:53:40 UTC, committed by Paul Zimmermann on 20 February 2014, 14:53:40 UTC
post-receive
#!/usr/bin/perl -Tw
# This is an example git post-receive hook script. It can be used to
# automatically send emails listing new revisions to the repository
# introduced by the change being reported.
#
# Copyright 2011 Emmanuel Thomé -- This script is placed in the public domain.
#
# Usage: copy this to $GIT_DIR/hooks/post-receive (executable) in the
# repository receiving pushes.
#
# The file $GIT_DIR/config should contain the following keys (these are
# examples).
#
# hooks.emailprefix=
# hooks.mailinglist=Cado-NFS commits <cado-nfs-commits@lists.gforge.inria.fr>
# hooks.envelopesender=Cado-NFS commits <cado-nfs-commits@lists.gforge.inria.fr>
# hooks.gitweb=http://tomato.elliptic.org/gitweb/?p=cado-nfs.git;
use strict;
use warnings;
use File::Basename;
use POSIX;
# gforge's nsswitch.conf maps pgsql, which causes problems.
BEGIN {
open SAVED_STDERR, ">&STDERR";
open STDERR, ">/dev/null";
}
use Net::SMTP;
open STDERR, ">&SAVED_STDERR";
$ENV{'PATH'}="/bin:/usr/bin:/usr/sbin";
my $git_dir="$ENV{'GIT_DIR'}" or die 'no $GIT_DIR set';
my $repo = basename($git_dir, ".git");
sub config_get {
my $key = shift;
my $x = `git config $key 2>/dev/null`;
if ($x =~ /^([^'`]*)$/) {
# A positive answer with empty content still reaches here.
$x=$1;
chomp($x);
return $x;
}
my $default = shift;
return '' unless $default;
die "Mandatory config key $key is missing" if $default eq '!';
return $default;
}
my $gitweb=config_get('hooks.gitweb');
chomp($gitweb);
my $recipient=config_get('hooks.mailinglist', '!');
my $sender=config_get('hooks.envelopesender', '!');
if(!defined($sender) || $sender eq '' || $sender eq '!') {
$sender = $ENV{USER};
if(defined(my $domain = config_get('hooks.maildomain'))) {
$sender .= "@" . $domain;
}
}
my $subj_prefix=config_get('hooks.emailprefix', "[$repo-commits] ");
sub commit_link {
my $commit = shift;
return $commit unless $gitweb;
my $url = $gitweb . "a=commitdiff;h=$commit";
return "<a href=\"$url\">$commit</a>";
}
my @branches_touched=();
my $total_text_plain='';
my $total_text_html='';
my $total_nc = 0;
my $last_subject;
sub boundary {
my $x = join("", map { int(rand()*100000) } (0..6));
return $x;
}
my $mainbound = boundary();
my $patches_mboxes='';
while (defined($_=<STDIN>)) {
chomp($_);
# print STDERR "INPUT: $_\n";
s/^\s*//;
next if /^$/;
s/^([0-9a-f]{40})\s+// or die "Bad input line: $_";
my $old = $1;
s/^([0-9a-f]{40})\s+// or die "Bad input line: $_";
my $new = $1;
# For new branches, the question is how we can infer the branch point...
# Could even be quite far. And there's no clear way to guess where this
# was branched from. We're using a kludge which excludes the history
# of all other branches.
# next if $old =~ /^0+$/;
if ($new =~ /^0+$/) {
# print STDERR "Deleted branch $_ ?\n";
next;
}
m{^(refs/heads/[\w/.-]*)$} or die "$_ ?"; $_=$1;
my $ref = $_;
my $fullref=$_;
$ref =~ s,^refs/heads/,,;
push @branches_touched, $ref;
my $format = '%h %ae %s';
if ($old =~ /^0+$/) {
my $cmd = qq{git log --pretty='%h %ae %s' $fullref};
my @all_heads = `git for-each-ref --format='%(refname)' "refs/heads/*"`;
for my $h (@all_heads) {
chomp($h);
$h=~m{^(refs/heads/[\w/.-]*)$} or die "$h ?";
$h=$1;
next if $h eq $fullref;
$cmd .= " ^$1";
}
# print STDERR "Command: $cmd\n";
open F, "$cmd |";
} else {
open F, "git log --pretty=\"$format\" --abbrev $old..$new |";
}
my $text_plain='';
my $text_html='';
my $author='';
my $nc=0;
my @newbranch_commits=();
while (defined(my $line = <F>)) {
# print STDERR "Got: $line\n";
my ($commit, $who, $subject) = split(' ', $line, 3);
$commit=~/^([0-9a-f]+)$/ or die;
$commit=$1;
chomp($subject);
if ($who ne $author) {
$text_html .= "</ul>\n" if $author;
$author = $who;
$text_plain .= "\n$who\n";
$text_html .= "<p>$who</p>\n<ul>\n";
}
$text_plain .= " $commit $subject\n";
push @newbranch_commits, $commit if $old =~ /^0+$/;
my $link = commit_link($commit);
my $local_subject = $subject;
if ($gitweb) {
$local_subject =~ s/commit ([0-9a-f]{7}|[0-9a-f]{40})/"commit " . &commit_link($1)/eg;
}
$text_html .= "<li>$link $local_subject</li>\n";
$last_subject = $subject;
$nc++;
}
close F;
$text_html .= "</ul>\n" if $author;
$text_plain .= "\n\n";
my $x = "Branch: $ref";
$text_plain = "$x, $nc new commits\n" . ('=' x length($x)) . "\n" . $text_plain;
$text_html = "<h1>Branch: $ref, $nc new commits</h1>\n" . $text_html;;
$total_text_plain .= $text_plain;
$total_text_html .= $text_html;
$total_nc += $nc;
my $bound = boundary();
# We need to work somewhat differently for new branches
if (@newbranch_commits == 0) {
open PATCH, "git format-patch --stdout $old..$new |";
$x = eval { local $/=undef; <PATCH>; };
close PATCH;
} else {
$x='';
for my $c (@newbranch_commits) {
open PATCH, "git format-patch --stdout $c -1 |";
$x .= eval { local $/=undef; <PATCH>; };
$x .= "\n";
close PATCH;
}
}
$x =~ s/^(From (\w{7})\w{33})/--$bound
Content-Type: message\/rfc822; charset=us-ascii
Content-Disposition: attachment; filename="$2.patch"
$1/gm;
my $now = POSIX::strftime( "%Y%m%d%H%M%S", localtime);
$patches_mboxes .= <<EOF;
--$mainbound
Content-Type: multipart/mixed; boundary=$bound
Content-Disposition: attachment; filename="$repo-$ref-$now.mbox"
$x
--$bound--
EOF
}
my $refs = join(', ', @branches_touched);
exit 0 unless $total_nc;
my $subject = "$total_nc new commits";
if ($total_nc == 1) {
$subject = $last_subject;
}
my $header = <<EOF;
To: $recipient
From: $sender
Subject: $subj_prefix($refs) $subject
EOF
$header=~s/\n$//m;
$header .= "\n";
my $textbound = boundary();
my $body = <<EOF;
--$mainbound
Content-Type: multipart/alternative; boundary=$textbound
--$textbound
Content-Type: text/plain; charset=utf-8
$total_text_plain
--$textbound
Content-Type: text/html; charset=utf-8
$total_text_html
--$textbound--
$patches_mboxes
--$mainbound--
EOF
my $bodylength = length($body);
my $bodylines = eval { @_=split(/^/m, $body);scalar @_; };
$header .= <<EOF;
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary=$mainbound
Content-Transfer-Encoding: 8bit
Content-Disposition: inline
Content-Length: $bodylength
Lines: $bodylines
EOF
# We know of several distinct ways to send the mail. The first one works
# fine if we have access to a shell MTA (sendmail, exim)
# if (@ARGV && $ARGV[0] eq '-d') {
# open SMTP, ">&STDOUT";
# my $now=strftime("%a %b %d %H:%M:%S %Y", localtime);
# print SMTP "From root $now\n";
# } else {
# open SMTP, "| exim -f '$sender' '$recipient'";
# }
#
# print SMTP <<EOF;
# $header
#
# $body
# EOF
# close SMTP;
# This second method connects to 127.0.0.1 port 25, which happens to be
# ok on gforge.
my $smtp = Net::SMTP->new('127.0.0.1');
print STDERR "$sender --> $recipient: $subject\n";
sub handle_smtp_error
{
my ($smtp, $retval) = @_;
if (not $retval)
{
die "$0: SMTP Error: " . $smtp->message() . "\n";
}
}
handle_smtp_error($smtp, $smtp->mail($sender));
handle_smtp_error($smtp, $smtp->to($recipient));
handle_smtp_error($smtp, $smtp->data());
handle_smtp_error($smtp, $smtp->datasend($header));
handle_smtp_error($smtp, $smtp->datasend("\n"));
handle_smtp_error($smtp, $smtp->datasend($body));
handle_smtp_error($smtp, $smtp->dataend());
handle_smtp_error($smtp, $smtp->quit());
Computing file changes ...