diff --git a/src/lib/Hydra.pm b/src/lib/Hydra.pm
index 56165418..42da5f65 100644
--- a/src/lib/Hydra.pm
+++ b/src/lib/Hydra.pm
@@ -92,7 +92,7 @@ has 'hydra_plugins' => (
after setup_finalize => sub {
my $class = shift;
- $plugins = [Hydra::Plugin->plugins(db => $class->model('DB'), config => $class->config)];
+ $plugins = [Hydra::Plugin->instantiate(db => $class->model('DB'), config => $class->config)];
};
__PACKAGE__->setup();
diff --git a/src/lib/Hydra/Plugin.pm b/src/lib/Hydra/Plugin.pm
index 47a5faeb..8b3df782 100644
--- a/src/lib/Hydra/Plugin.pm
+++ b/src/lib/Hydra/Plugin.pm
@@ -7,11 +7,19 @@ use Module::Pluggable
sub new {
my ($class, %args) = @_;
- my $self = { db => $args{db}, config => $args{config} };
+ my $self = { db => $args{db}, config => $args{config}, plugins => $args{plugins} };
bless $self, $class;
return $self;
}
+sub instantiate {
+ my ($class, %args) = @_;
+ my $plugins = [];
+ $args{plugins} = $plugins;
+ push @$plugins, $class->plugins(%args);
+ return @$plugins;
+}
+
# Called when build $build has finished. If the build failed, then
# $dependents is an array ref to a list of builds that have also
# failed as a result (i.e. because they depend on $build or a failed
@@ -34,4 +42,12 @@ sub fetchInput {
return undef;
}
+# Get the commits to repository ‘$value’ between revisions ‘$rev1’ and
+# ‘$rev2’. Each commit should be a hash ‘{ revision = "..."; author =
+# "..."; email = "..."; }’.
+sub getCommits {
+ my ($self, $type, $value, $rev1, $rev2) = @_;
+ return [];
+}
+
1;
diff --git a/src/lib/Hydra/Plugin/GitInput.pm b/src/lib/Hydra/Plugin/GitInput.pm
index 98c4c24a..fc267e70 100644
--- a/src/lib/Hydra/Plugin/GitInput.pm
+++ b/src/lib/Hydra/Plugin/GitInput.pm
@@ -12,17 +12,9 @@ sub supportedInputTypes {
$inputTypes->{'git'} = 'Git checkout';
}
-sub fetchInput {
- my ($self, $type, $name, $value) = @_;
-
- return undef if $type ne "git";
-
- (my $uri, my $branch, my $deepClone) = split ' ', $value;
- $branch = defined $branch ? $branch : "master";
-
- my $timestamp = time;
- my $sha256;
- my $storePath;
+# Clone or update a branch of a repository into our SCM cache.
+sub _cloneRepo {
+ my ($self, $uri, $branch, $deepClone) = @_;
my $cacheDir = getSCMCacheDir . "/git";
mkpath($cacheDir);
@@ -38,8 +30,8 @@ sub fetchInput {
chdir $clonePath or die $!; # !!! urgh, shouldn't do a chdir
- # This command force the update of the local branch to be in the same as
- # the remote branch for whatever the repository state is. This command mirror
+ # This command forces the update of the local branch to be in the same as
+ # the remote branch for whatever the repository state is. This command mirrors
# only one branch of the remote repository.
($res, $stdout, $stderr) = captureStdoutStderr(600,
"git", "fetch", "-fu", "origin", "+$branch:$branch");
@@ -47,16 +39,6 @@ sub fetchInput {
"git", "fetch", "-fu", "origin") if $res;
die "error fetching latest change from git repo at `$uri':\n$stderr" if $res;
- ($res, $stdout, $stderr) = captureStdoutStderr(600,
- ("git", "rev-parse", "$branch"));
- die "error getting revision number of Git branch '$branch' at `$uri':\n$stderr" if $res;
-
- my ($revision) = split /\n/, $stdout;
- die "error getting a well-formated revision number of Git branch '$branch' at `$uri':\n$stdout"
- unless $revision =~ /^[0-9a-fA-F]+$/;
-
- my $ref = "refs/heads/$branch";
-
# If deepClone is defined, then we look at the content of the repository
# to determine if this is a top-git branch.
if (defined $deepClone) {
@@ -74,6 +56,40 @@ sub fetchInput {
}
}
+ return $clonePath;
+}
+
+sub _parseValue {
+ my ($value) = @_;
+ (my $uri, my $branch, my $deepClone) = split ' ', $value;
+ $branch = defined $branch ? $branch : "master";
+ return ($uri, $branch, $deepClone);
+
+}
+
+sub fetchInput {
+ my ($self, $type, $name, $value) = @_;
+
+ return undef if $type ne "git";
+
+ my ($uri, $branch, $deepClone) = _parseValue($value);
+
+ my $clonePath = $self->_cloneRepo($uri, $branch, $deepClone);
+
+ my $timestamp = time;
+ my $sha256;
+ my $storePath;
+
+ my ($res, $stdout, $stderr) = captureStdoutStderr(600,
+ ("git", "rev-parse", "$branch"));
+ die "error getting revision number of Git branch '$branch' at `$uri':\n$stderr" if $res;
+
+ my ($revision) = split /\n/, $stdout;
+ die "error getting a well-formated revision number of Git branch '$branch' at `$uri':\n$stdout"
+ unless $revision =~ /^[0-9a-fA-F]+$/;
+
+ my $ref = "refs/heads/$branch";
+
# Some simple caching: don't check a uri/branch/revision more than once.
# TODO: Fix case where the branch is reset to a previous commit.
my $cachedInput ;
@@ -145,4 +161,28 @@ sub fetchInput {
};
}
+sub getCommits {
+ my ($self, $type, $value, $rev1, $rev2) = @_;
+ return [] if $type ne "git";
+
+ return [] unless $rev1 =~ /^[0-9a-f]+$/;
+ return [] unless $rev2 =~ /^[0-9a-f]+$/;
+
+ my ($uri, $branch, $deepClone) = _parseValue($value);
+
+ my $clonePath = $self->_cloneRepo($uri, $branch, $deepClone);
+
+ my $out;
+ IPC::Run::run(["git", "log", "--pretty=format:%H%x09%an%x09%ae%x09%at", "$rev1..$rev2"], \undef, \$out)
+ or die "cannot get git logs: $?";
+
+ my $res = [];
+ foreach my $line (split /\n/, $out) {
+ my ($revision, $author, $email, $date) = split "\t", $line;
+ push @$res, { revision => $revision, author => $author, email => $email };
+ }
+
+ return $res;
+}
+
1;
diff --git a/src/lib/Hydra/Plugin/HipChatNotification.pm b/src/lib/Hydra/Plugin/HipChatNotification.pm
index 13ef187f..c7b11352 100644
--- a/src/lib/Hydra/Plugin/HipChatNotification.pm
+++ b/src/lib/Hydra/Plugin/HipChatNotification.pm
@@ -35,6 +35,36 @@ sub buildFinished {
}
}
+ return if scalar keys %rooms == 0;
+
+ # Determine who broke/fixed the build.
+ my $prevBuild = getPreviousBuild($build);
+
+ my $nrCommits = 0;
+ my %authors;
+
+ if ($prevBuild) {
+ foreach my $curInput ($build->buildinputs_builds) {
+ next unless $curInput->type eq "git";
+ my $prevInput = $prevBuild->buildinputs_builds->find({ name => $curInput->name });
+ next unless defined $prevInput;
+
+ next if $curInput->type ne $prevInput->type;
+ next if $curInput->uri ne $prevInput->uri;
+
+ my @commits;
+ foreach my $plugin (@{$self->{plugins}}) {
+ push @commits, @{$plugin->getCommits($curInput->type, $curInput->uri, $prevInput->revision, $curInput->revision)};
+ }
+
+ foreach my $commit (@commits) {
+ print STDERR "$commit->{revision} by $commit->{author}\n";
+ $authors{$commit->{author}} = $commit->{email};
+ $nrCommits++;
+ }
+ }
+ }
+
# Send a message to each room.
foreach my $roomId (keys %rooms) {
my $room = $rooms{$roomId};
@@ -53,7 +83,16 @@ sub buildFinished {
$msg .= " (and ${\scalar @deps} others)" if scalar @deps > 0;
$msg .= ": " . showStatus($build) . "";
+ if (scalar keys %authors > 0) {
+ # FIXME: HTML escaping
+ my @x = map { "$_" } (sort keys %authors);
+ $msg .= ", likely due to ";
+ $msg .= "$nrCommits commits by " if $nrCommits > 1;
+ $msg .= join(" or ", scalar @x > 1 ? join(", ", @x[0..scalar @x - 2]) : (), $x[-1]);
+ }
+
print STDERR "sending hipchat notification to room $roomId: $msg\n";
+ next;
my $ua = LWP::UserAgent->new();
my $resp = $ua->post('https://api.hipchat.com/v1/rooms/message?format=json&auth_token=' . $room->{room}->{token}, {
diff --git a/src/script/hydra-build b/src/script/hydra-build
index 902b5bff..ab2650db 100755
--- a/src/script/hydra-build
+++ b/src/script/hydra-build
@@ -17,7 +17,7 @@ my $db = Hydra::Model::DB->new();
my $config = getHydraConfig();
-my @plugins = Hydra::Plugin->plugins(db => $db, config => $config);
+my @plugins = Hydra::Plugin->instantiate(db => $db, config => $config);
sub addBuildStepOutputs {
diff --git a/src/script/hydra-evaluator b/src/script/hydra-evaluator
index dd6ffa7e..50e0e448 100755
--- a/src/script/hydra-evaluator
+++ b/src/script/hydra-evaluator
@@ -21,7 +21,7 @@ STDOUT->autoflush();
my $db = Hydra::Model::DB->new();
my $config = getHydraConfig();
-my $plugins = [Hydra::Plugin->plugins(db => $db, config => $config)];
+my $plugins = [Hydra::Plugin->instantiate(db => $db, config => $config)];
# Don't check a jobset more than once every five minutes.
my $minCheckInterval = 5 * 60;