[Bps-public-commit] smokingit branch, anna, created. cdda0a472f6d46af0c5c2e7bf05835af202699be

Alex Vandiver alexmv at bestpractical.com
Tue May 28 12:25:27 EDT 2013


The branch, anna has been created
        at  cdda0a472f6d46af0c5c2e7bf05835af202699be (commit)

- Log -----------------------------------------------------------------
commit 81db510c53001345a8457bb0a2ba75f84dffed98
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Sun May 26 21:26:33 2013 -0400

    Rename branchpoint to first_commit, as that is what it is

diff --git a/lib/Smokingit/Model/Branch.pm b/lib/Smokingit/Model/Branch.pm
index 29d9fba..4b67a76 100644
--- a/lib/Smokingit/Model/Branch.pm
+++ b/lib/Smokingit/Model/Branch.pm
@@ -81,7 +81,6 @@ sub create {
     my $tip = $project->sha( delete $args{sha} );
     $args{current_commit_id} = $tip->id;
     $args{tested_commit_id}  = $tip->id;
-    $args{first_commit_id}   = $tip->id;
     $args{owner} = $tip->committer;
 
     my ($ok, $msg) = $self->SUPER::create(%args);
@@ -215,12 +214,15 @@ sub commit_list {
     my $self = shift;
     local $ENV{GIT_DIR} = $self->project->repository_path;
 
-    my $first = $self->first_commit->sha;
+    my $first = $self->first_commit ? "^".$self->first_commit->sha."~" : "";
     my $last = $self->current_commit->sha;
-    my @revs = map {chomp; $_} `git rev-list ^$first $last --topo-order --max-count=50`;
+    my @revs = map {chomp; $_} `git rev-list $first $last --topo-order --max-count=50`;
     my $left = 50 - @revs; $left = 11 if $left > 11;
-    push @revs, map {chomp; $_} `git rev-list $first --topo-order --max-count=$left`
-        if $left > 0;
+    if ($self->first_commit) {
+        $first = $self->first_commit->sha . "~";
+        push @revs, map {chomp; $_} `git rev-list $first --topo-order --max-count=$left`
+            if $left > 0;
+    }
 
     my $commits = Smokingit::Model::CommitCollection->new;
     $commits->limit( column => "project_id", value => $self->project->id );
@@ -254,17 +256,19 @@ sub commit_list {
     return map $commits{$_} || $self->project->sha($_), @revs;
 }
 
-sub branchpoint {
+sub first_commit {
     my $self = shift;
-    my $max = shift || 100;
-    return undef if $self->status eq "master";
-    return undef unless $self->to_merge_into->id;
 
-    my $trunk = $self->to_merge_into->current_commit->sha;
-    my $tip   = $self->current_commit->sha;
+    if ($self->status eq "master") {
+        # No first commit for master branches
+        return undef;
+    }
+
+    my @trunks = map {"^".$_->current_commit->sha} @{$self->project->trunks};
+    my $tip    = $self->current_commit->sha;
 
     local $ENV{GIT_DIR} = $self->project->repository_path;
-    my @branch = map {chomp; $_} `git rev-list $tip ^$trunk --topo-order --max-count=$max`;
+    my @branch = map {chomp; $_} `git rev-list $tip @trunks --topo-order`;
     return unless @branch;
 
     my $commit = $self->project->sha( $branch[-1] );
diff --git a/lib/Smokingit/View/Branch.pm b/lib/Smokingit/View/Branch.pm
index 4191168..47f132b 100644
--- a/lib/Smokingit/View/Branch.pm
+++ b/lib/Smokingit/View/Branch.pm
@@ -48,7 +48,7 @@ template '/branch' => page {
 
     my $project_id = $b->project->id;
     my @commits = $b->commit_list;
-    my $branchpoint = $b->branchpoint(@commits+1);
+    my $first = $b->first_commit;
     div {
         id is "branch-commits";
         class is "commitlist biglist";
@@ -99,7 +99,7 @@ template '/branch' => page {
                 }
                 span {{class is "subject"} $commit->subject };
             };
-            if ($branchpoint and $branchpoint->id == $commit->id) {
+            if ($first and $first->id == $commit->id) {
                 div { { class is "branchpoint" } };
             }
         }

commit 3ebb0fa18b5ca053e8107b7ba11cfba12299e96d
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Sun May 26 21:28:42 2013 -0400

    Cache the branchpoint in the helpfully already-existing first_commit_id column

diff --git a/lib/Smokingit/Model/Branch.pm b/lib/Smokingit/Model/Branch.pm
index 4b67a76..e8dabd0 100644
--- a/lib/Smokingit/Model/Branch.pm
+++ b/lib/Smokingit/Model/Branch.pm
@@ -81,6 +81,7 @@ sub create {
     my $tip = $project->sha( delete $args{sha} );
     $args{current_commit_id} = $tip->id;
     $args{tested_commit_id}  = $tip->id;
+    $args{first_commit_id}   = undef; # Compute lazily
     $args{owner} = $tip->committer;
 
     my ($ok, $msg) = $self->SUPER::create(%args);
@@ -259,9 +260,17 @@ sub commit_list {
 sub first_commit {
     my $self = shift;
 
-    if ($self->status eq "master") {
+    my $id = $self->first_commit_id;
+    if ($id) {
+        my $commit = Smokingit::Model::Commit->new;
+        $commit->load($id);
+        return $commit;
+    } elsif ($self->status eq "master") {
         # No first commit for master branches
         return undef;
+    } elsif (defined $id and $id == 0) {
+        # 0 means "Not found"
+        return undef;
     }
 
     my @trunks = map {"^".$_->current_commit->sha} @{$self->project->trunks};
@@ -272,7 +281,13 @@ sub first_commit {
     return unless @branch;
 
     my $commit = $self->project->sha( $branch[-1] );
-    return $commit->id ? $commit : undef;
+    if ($commit->id) {
+        $self->set_first_commit_id( $commit->id );
+        return $commit;
+    } else {
+        $self->set_first_commit_id( 0 );
+        return undef;
+    }
 }
 
 sub test_status {

commit 7fec656bfbc31576b80c7195d3ee0e9db5daa2f4
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Sun May 26 21:29:19 2013 -0400

    Update guess_merge_into to use first_commit, rather than reimplementing it

diff --git a/lib/Smokingit/Model/Branch.pm b/lib/Smokingit/Model/Branch.pm
index e8dabd0..bce24ee 100644
--- a/lib/Smokingit/Model/Branch.pm
+++ b/lib/Smokingit/Model/Branch.pm
@@ -104,30 +104,20 @@ sub create {
 sub guess_merge_into {
     my $self = shift;
 
-    my @trunks;
-    my $branches = $self->project->trunk_or_relengs;
-    while (my $b = $branches->next) {
-        push @trunks, [$b->id, $b->current_commit->sha, $b->name];
-    }
+    my $first = $self->first_commit;
+    return unless $first;
 
-    # Find the commit before the first non-trunk commit, which is the
-    # commit this branch was branched off of
-    local $ENV{GIT_DIR} = $self->project->repository_path;
-    my $topic = $self->current_commit->sha;
-    my @revlist = map {chomp; $_} `git rev-list $topic @{[map {"^".$_->[1]} @trunks]}`;
-    my $branchpoint;
-    if (@revlist) {
-        $branchpoint = `git rev-parse $revlist[-1]~`;
-        chomp $branchpoint;
-    } else {
-        $branchpoint = $topic;
-    }
+    my ($branchpoint) = $first->parents;
+    return unless $branchpoint;
+    my $branchsha = $branchpoint->sha;
 
-    for my $t (@trunks) {
+    my $trunks = $self->project->trunk_or_relengs;
+    while (my $t = $trunks->next) {
         # Find the first trunk which contains all the branch point
-        # (i.e. branchpoint - trunk is the empty set)
-        next if `git rev-list --max-count=1 $branchpoint ^$t->[1]` =~ /\S/;
-        return $t->[0];
+        # (i.e. branchsha - trunk is the empty set)
+        my $sha = $t->current_commit->sha;
+        next if `git rev-list --max-count=1 $branchsha ^$sha` =~ /\S/;
+        return $t->id;
     }
     return undef;
 }

commit 3d652eb5c34f61a06a65d4cef42e471953da8df2
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Mon May 27 13:53:59 2013 -0400

    Factor out and improve merge commit check

diff --git a/lib/Smokingit/Model/Commit.pm b/lib/Smokingit/Model/Commit.pm
index 1af2fbe..789a39f 100644
--- a/lib/Smokingit/Model/Commit.pm
+++ b/lib/Smokingit/Model/Commit.pm
@@ -75,6 +75,14 @@ sub short_sha {
     return substr($self->sha,0,7);
 }
 
+sub is_merge {
+    my $self = shift;
+    my @parents = $self->parents;
+    return unless @parents > 1;
+    return $1 if $self->subject =~ /^Merge branch '(.*?)'/;
+    return 'Unknown branch';
+}
+
 sub run_smoke {
     my $self = shift;
     my $config = shift;
diff --git a/lib/Smokingit/View/Branch.pm b/lib/Smokingit/View/Branch.pm
index 47f132b..92a3769 100644
--- a/lib/Smokingit/View/Branch.pm
+++ b/lib/Smokingit/View/Branch.pm
@@ -54,8 +54,7 @@ template '/branch' => page {
         class is "commitlist biglist";
         for my $commit (@commits) {
             $commit->hash_results;
-            my $merge = $commit->subject =~ /^Merge branch /
-                ? "merge" : "nonmerge";
+            my $merge = $commit->is_merge ? "merge" : "nonmerge";
             div {
                 {class is $commit->sha." $merge commit ".$commit->status};
                 for my $config (@configs) {

commit 3fcb0ecfdbc12bf41c484e6188581cf2a50b639f
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Mon May 27 22:05:09 2013 -0400

    Promote "branch contains commit" to a method
    
    Make use of the git 1.8.0 feature of `git merge-base --is-ancestor commit branch`

diff --git a/lib/Smokingit/Model/Branch.pm b/lib/Smokingit/Model/Branch.pm
index bce24ee..cefdedf 100644
--- a/lib/Smokingit/Model/Branch.pm
+++ b/lib/Smokingit/Model/Branch.pm
@@ -3,6 +3,7 @@ use warnings;
 
 package Smokingit::Model::Branch;
 use Jifty::DBI::Schema;
+use Scalar::Util qw/blessed/;
 
 use Smokingit::Record schema {
     column project_id =>
@@ -109,15 +110,10 @@ sub guess_merge_into {
 
     my ($branchpoint) = $first->parents;
     return unless $branchpoint;
-    my $branchsha = $branchpoint->sha;
 
     my $trunks = $self->project->trunk_or_relengs;
     while (my $t = $trunks->next) {
-        # Find the first trunk which contains all the branch point
-        # (i.e. branchsha - trunk is the empty set)
-        my $sha = $t->current_commit->sha;
-        next if `git rev-list --max-count=1 $branchsha ^$sha` =~ /\S/;
-        return $t->id;
+        return $t->id if $t->contains($branchpoint);
     }
     return undef;
 }
@@ -201,6 +197,18 @@ sub is_tested {
     return $self->status ne "ignore";
 }
 
+sub contains {
+    my $self = shift;
+    my $commit = shift;
+    $commit = $commit->sha if blessed($commit);
+
+    my $tip = $self->current_commit->sha;
+
+    local $ENV{GIT_DIR} = $self->project->repository_path;
+    `git merge-base --is-ancestor $commit $tip`;
+    return not $?;
+}
+
 sub commit_list {
     my $self = shift;
     local $ENV{GIT_DIR} = $self->project->repository_path;

commit d680989832f11dd34e7c904ee4b5f07364eb66d7
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Mon May 27 22:06:03 2013 -0400

    Add a method to retrieve completed smoke results for a commit

diff --git a/lib/Smokingit/Model/Commit.pm b/lib/Smokingit/Model/Commit.pm
index 789a39f..a657e62 100644
--- a/lib/Smokingit/Model/Commit.pm
+++ b/lib/Smokingit/Model/Commit.pm
@@ -106,6 +106,15 @@ sub run_smoke {
     return $smoke->run_smoke;
 }
 
+sub smoke_results {
+    my $self = shift;
+    my $results = Smokingit::Model::SmokeResultCollection->new;
+    $results->limit( column => "commit_id", value => $self->id );
+    $results->limit( column => "project_id", value => $self->project->id );
+    $results->limit( column => "queue_status", operator => "IS", value => "NULL" );
+    return $results;
+}
+
 sub hash_results {
     my $self = shift;
     my $results = shift || $self->prefetched( "smoke_results" );

commit cdda0a472f6d46af0c5c2e7bf05835af202699be
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Tue May 28 01:38:54 2013 -0400

    anna is a bot

diff --git a/bin/anna b/bin/anna
new file mode 100755
index 0000000..878bba1
--- /dev/null
+++ b/bin/anna
@@ -0,0 +1,17 @@
+#!/usr/bin/env perl
+use YAML;
+
+use strict;
+use warnings;
+
+use Jifty;
+BEGIN { Jifty->new( no_request => 1 ); };
+use Smokingit::IRC;
+
+my %config = %{ Jifty->config->app('irc') || {} };
+unless (%config) {
+    print "IRC is not configured; edit your site_config.yml\n";
+    exit;
+}
+
+Smokingit::IRC->new->run;
diff --git a/lib/Smokingit/IRC.pm b/lib/Smokingit/IRC.pm
new file mode 100644
index 0000000..8d74f2a
--- /dev/null
+++ b/lib/Smokingit/IRC.pm
@@ -0,0 +1,307 @@
+use strict;
+use warnings;
+
+package Smokingit::IRC;
+use String::IRC;
+
+use Moose;
+extends 'IM::Engine';
+
+has '+interface_args' => (
+    required => 0,
+    default  => sub {
+        my %config = %{ Jifty->config->app('irc') || {} };
+        return {
+            protocol => 'IRC',
+            credentials => {
+                server   => $config{host},
+                port     => $config{port},
+                nick     => $config{nick} || 'anna',
+                channels => [$config{channel}],
+            },
+        };
+    },
+);
+
+sub BUILD {
+    my $self = shift;
+
+    $self->interface->incoming_callback(
+        sub { $self->incoming(@_) },
+    );
+
+    $self->interface->irc->reg_cb(
+        registered => sub {
+            my $sub = Jifty->bus->new_listener;
+            $sub->subscribe(Jifty->bus->topic("test_result"));
+            $sub->poll( sub { $self->test_progress(@_) } );
+        },
+    );
+}
+
+sub error_reply {
+    my($incoming, $msg) = @_;
+    return $incoming->reply(
+        String::IRC->new( $msg )->maroon->stringify,
+    );
+}
+
+sub incoming {
+    my $self = shift;
+    my $incoming = shift;
+    Jifty::Record->flush_cache if Jifty::Record->can('flush_cache');
+
+    # Skip messages from the system
+    if ($incoming->sender->name =~ /\./) {
+        warn $incoming->sender->name . ": " .
+            $incoming->message;
+        return;
+    } elsif ($incoming->command eq "NOTICE") {
+        # NOTICE's are required to never trigger auto-replies
+        return;
+    }
+
+    my $msg = $incoming->message;
+    my $nick = $self->interface->irc->nick;
+    return if $incoming->isa("IM::Engine::Incoming::IRC::Channel")
+        and not $msg =~ s/^\s*$nick(?:\s*[:,])?\s*(?:please\s+)?//i;
+
+    if ($msg =~ /^retest\s+(.*)/) {
+        return $self->do_retest($incoming, $1);
+    } elsif ($msg =~ /^status\s+(?:of\s+)?(.*)/) {
+        return $self->do_status($incoming, $1);
+    } elsif ($msg =~ /^(?:re)?sync(?:\s+(.*))?/) {
+        return $self->do_sync($incoming, $1);
+    } else {
+        return $incoming->reply( "What?" );
+    }
+}
+
+sub do_retest {
+    my $self = shift;
+    my ($incoming, $what) = @_;
+    my $action = Smokingit::Action::Test->new(
+        current_user => Smokingit::CurrentUser->superuser,
+        arguments    => { commit => $what },
+    );
+    $action->validate;
+    return error_reply(
+        $incoming => $action->result->field_error("commit"),
+    ) unless $action->result->success;
+
+    $action->run;
+    return error_reply(
+        $incoming => $action->result->error,
+    ) if $action->result->error;
+
+    return $incoming->reply( $action->result->message );
+}
+
+sub do_status {
+    my $self = shift;
+    my ($incoming, $what) = @_;
+    if ($what =~ /^\s*([a-fA-F0-9]{5,})\s*$/) {
+        my $commits = Smokingit::Model::CommitCollection->new;
+        $commits->limit( column => "sha", operator => "like", value => "$what%" );
+        my @matches = @{ $commits->items_array_ref };
+        if (not @matches) {
+            return error_reply(
+                $incoming => "No such SHA!"
+            );
+        } elsif (@matches > 0) {
+            return error_reply(
+                $incoming => "Found ".(@matches+0)." matching SHAs!",
+            );
+        }
+
+        $what = $matches[0];
+    } else {
+        my ($project, $branch) = $what =~ /^\s*(?:(\S+):)?(\S+)\s*$/;
+        my $branches = Smokingit::Model::BranchCollection->new;
+        $branches->limit( column => "name", value => "$branch" );
+
+        if ($project) {
+            my $project_obj = Smokingit::Model::Project->new;
+            $project_obj->load_by_cols( name => $project );
+            if (not $project_obj->id) {
+                return error_reply(
+                    $incoming => "No such project '$project'!",
+                );
+            }
+            $branches->limit( column => "project_id", value => $project_obj->id );
+        }
+
+        my @matches = @{ $branches->items_array_ref };
+        if (not @matches) {
+            return error_reply(
+                $incoming => "No branch '$branch' found",
+            );
+        } elsif (@matches > 1) {
+            return error_reply(
+                $incoming => "Found '$branch' in ".
+                    join(", ", map {$_->project->name} @matches).
+                    ".  Try, $matches[0]:$branch"
+            );
+        }
+
+        # Need to re-parse if this got any updates
+        return $self->do_status($incoming, $what)
+            if $matches[0]->project->as_superuser->sync;
+
+        $what = $matches[0]->current_commit;
+    }
+    return $incoming->reply( $what->short_sha . " is " . $what->status );
+}
+
+sub do_sync {
+    my $self = shift;
+    my ($incoming, $what) = @_;
+
+    if ($what) {
+        my $project = Smokingit::Model::Project->new;
+        $project->load_by_cols( name => $what );
+        if (not $project->id) {
+            return error_reply(
+                $incoming => "No such project '$what'!",
+            );
+        }
+        my @results = $project->sync;
+        return $incoming->reply("No changes") unless @results;
+        return $incoming->reply(join("; ", @results));
+    } else {
+        my $projects = Smokingit::Model::ProjectCollection->new;
+        $projects->unlimit;
+        while (my $p = $projects->next) {
+            $p->sync;
+        }
+        return $incoming->reply("Synchronized ".$projects->count." projects");
+    }
+}
+
+sub test_progress {
+    my $self = shift;
+    my $msg = shift;
+    Jifty::Record->flush_cache if Jifty::Record->can('flush_cache');
+
+    eval {
+        my %message = %{ $msg };
+
+        my $smoke = Smokingit::Model::SmokeResult->new;
+        $smoke->load( $message{smoke_id} );
+        return unless $smoke->id;
+
+        my $message = $self->do_analyze($smoke);
+        return unless $message;
+
+        my $out = IM::Engine::Outgoing::IRC::Channel->new(
+            channel => Jifty->config->app('irc')->{channel},
+            message => $message,
+            command => "NOTICE",
+        );
+        $self->interface->send_message($out);
+    };
+    warn "$@" if $@;
+}
+
+sub do_analyze {
+    my $self = shift;
+    my ($smoke) = @_;
+
+    my $commit = $smoke->commit;
+    my $project = $smoke->project;
+
+    warn "Got test result for ".$commit->short_sha;
+
+    # First off, have we tested all configurations?
+    return unless $commit->smoke_results->count == $project->configurations->count;
+
+    warn "Have tested all configs";
+
+    # See if we can find the branch for this commit
+    my $branch = Smokingit::Model::Branch->new;
+    $branch->load_by_cols(
+        project_id => $project->id,
+        name       => $smoke->branch_name,
+    );
+    my $branchname = $branch->name;
+    return unless $branch->id;
+
+    # Make sure the branch actually still contains the commit
+    return unless $branch->contains($commit);
+
+    my $author = $commit->author;
+    $author = $1 if $author =~ /<(.*?)@/;
+
+    # If this is the first commit on the branch, _or_ we haven't tested
+    # some configuration of each parent of this commit, then this is
+    # first news we have of the branch.
+    my @tested_parents = grep {$_->smoke_results->count} $commit->parents;
+    if (($branch->first_commit and $commit->sha eq $branch->first_commit->sha)
+            or not @tested_parents) {
+        if ($commit->status eq "passing") {
+            return "New branch '$branchname' " .
+              String::IRC->new("passes tests")->green;
+        } else {
+            return "$author pushed a new branch '$branchname' which is " .
+              String::IRC->new("failing tests")->red;
+        }
+    } elsif ($commit->is_merge){
+        my $mergename = $commit->is_merge;
+        if ($commit->status eq "passing") {
+            return "Merged '$mergename' into '$branchname', " .
+              String::IRC->new("passes tests")->green;
+        }
+
+        # So the merge commit is fail: there are four possibilities,
+        # based on which of trunk/branch were passing previous to the
+        # commit.  We assume here that there are no octopus commits.
+        my ($trunk_commit, $branch_commit) = $commit->parents;
+        my $trunk_good  = $trunk_commit->status eq "passing";
+        my $branch_good = $branch_commit->status eq "passing";
+
+        if ($trunk_good and $branch_good) {
+            return "$author merged '$mergename' into '$branchname', which is " .
+                String::IRC->new("failing tests")->red .
+                ", although both parents were passing";
+        } elsif ($trunk_good and not $branch_good) {
+            return "$author merged '$mergename' (".
+              String::IRC->new("failing tests")->red.
+              ") into '$branchname', which is now ".
+              String::IRC->new("failing tests")->red;
+        } elsif (not $trunk_good and not $branch_good) {
+            return "$author merged '$mergename' (".
+              String::IRC->new("failing tests")->red.
+              ") into '$branchname', which is still ".
+              String::IRC->new("failing tests")->red;
+        } else {
+            return "$author merged '$mergename'".
+              " into '$branchname', which is still ".
+              String::IRC->new("failing tests")->red;
+        }
+    } elsif ($commit->status ne "passing") {
+        # A new commit on an existing branch, which fails tests.  Let's
+        # check if this is better or worse than the previous commit.
+        if (@tested_parents == grep {$_->status eq "passing"} @tested_parents) {
+            return "'$branchname' by $author began ".
+                String::IRC->new("failing tests")->red .
+                " as of ".$commit->short_sha;
+        } else {
+            # Was failing, still failing?  Let's not spam about it
+            return;
+        }
+    } elsif (grep {$_->status ne "passing"} @tested_parents) {
+        # A new commit on an existing branch, which passes tests but
+        # whose parents didn't!
+        return "'$branchname' by $author ".
+            String::IRC->new("passes tests")->green .
+            " as of ".$commit->short_sha;
+    } else {
+        # A commit which passes, and whose parents all passed.  Go them?
+        return;
+    }
+}
+
+__PACKAGE__->meta->make_immutable;
+no Moose;
+
+1;

-----------------------------------------------------------------------



More information about the Bps-public-commit mailing list