[Bps-public-commit] SD branch, master, updated. 1c4c703922acdff74cd0f8352b790576311bbcc3
sunnavy at bestpractical.com
sunnavy at bestpractical.com
Thu Jun 11 23:57:39 EDT 2009
The branch, master has been updated
via 1c4c703922acdff74cd0f8352b790576311bbcc3 (commit)
via 4ea89072bfd9576201e13e5aef40c054209e328e (commit)
via ddfac2dee4a0152e8d7d61d33fbbf12528ad1ab6 (commit)
from 0a38d002f7e4b21951e9f1495cae3f9cd6f44533 (commit)
Summary of changes:
lib/App/SD/Replica/github.pm | 29 ++-
lib/App/SD/Replica/github/PullEncoder.pm | 255 ++++++++++++++++++++
.../SD/Replica/{gcode => github}/PushEncoder.pm | 81 ++-----
lib/App/SD/Util.pm | 21 ++-
4 files changed, 316 insertions(+), 70 deletions(-)
create mode 100644 lib/App/SD/Replica/github/PullEncoder.pm
copy lib/App/SD/Replica/{gcode => github}/PushEncoder.pm (60%)
- Log -----------------------------------------------------------------
commit ddfac2dee4a0152e8d7d61d33fbbf12528ad1ab6
Author: sunnavy <sunnavy at bestpractical.com>
Date: Fri Jun 12 11:55:03 2009 +0800
make string_to_datetime know the format like 2009/06/12 11:53:20 +0800
diff --git a/lib/App/SD/Util.pm b/lib/App/SD/Util.pm
index 5b91200..91cd81f 100644
--- a/lib/App/SD/Util.pm
+++ b/lib/App/SD/Util.pm
@@ -14,7 +14,26 @@ sub string_to_datetime {
second => $sec,
time_zone => 'GMT');
return $dt;
- } elsif ($date) {
+ }
+ elsif (
+ $date =~ m!^(\d{4})/(\d{2})/(\d{2}) (\d{2}):(\d{2}):(\d{2}) ([-+]?\d{4})?! )
+ {
+ # e.g. 2009/03/21 10:03:05 -0700
+ my ( $year, $month, $day, $hour, $min, $sec, $tz ) =
+ ( $1, $2, $3, $4, $5, $6, $7 );
+ my $dt = DateTime->new(
+ year => $year,
+ month => $month,
+ day => $day,
+ hour => $hour,
+ minute => $min,
+ second => $sec,
+ time_zone => $tz || 'GMT'
+ );
+ $dt->set_time_zone( 'GMT' );
+ return $dt;
+ }
+ elsif ($date) {
require DateTime::Format::Natural;
# XXX DO we want floating or GMT?
my $parser = DateTime::Format::Natural->new(time_zone => 'floating');
commit 4ea89072bfd9576201e13e5aef40c054209e328e
Author: sunnavy <sunnavy at bestpractical.com>
Date: Fri Jun 12 11:56:05 2009 +0800
update github.pm
diff --git a/lib/App/SD/Replica/github.pm b/lib/App/SD/Replica/github.pm
index f2e9a16..f567c34 100644
--- a/lib/App/SD/Replica/github.pm
+++ b/lib/App/SD/Replica/github.pm
@@ -16,16 +16,21 @@ use constant scheme => 'github';
use constant pull_encoder => 'App::SD::Replica::github::PullEncoder';
use constant push_encoder => 'App::SD::Replica::github::PushEncoder';
-has github => ( isa => 'Net::Github', is => 'rw' );
-has remote_url => ( isa => 'Str', is => 'rw' );
-has owner => ( isa => 'Str', is => 'rw' );
-has repo => ( isa => 'Str', is => 'rw' );
+has github => ( isa => 'Net::GitHub::V2', is => 'rw' );
+has remote_url => ( isa => 'Str', is => 'rw' );
+has owner => ( isa => 'Str', is => 'rw' );
+has repo => ( isa => 'Str', is => 'rw' );
+has query => ( isa => 'Str', is => 'rw' );
+
+our %PROP_MAP = ( state => 'status', );
sub BUILD {
my $self = shift;
- my ( $server , $owner , $repo ) = $self->{url} =~ m/^github:(.+?)\|(\w+)\|(\w+)\|$/
- or die "Can't parse Github server spec. Expected github:http://user\@github.com|owner|repository|";
+ my ( $server, $owner, $repo ) =
+ $self->{url} =~ m/^github:(.*?)\|(.+)\|(.+)\|$/
+ or die
+"Can't parse Github server spec. Expected github:http://user\@github.com|owner|repository|";
my $uri = URI->new($server);
@@ -58,6 +63,18 @@ sub uuid {
return $self->uuid_for_url( join( '/', $self->remote_url, $self->owner , $self->repo ) );
}
+sub remote_uri_path_for_comment {
+ my $self = shift;
+ my $id = shift;
+ return "/comment/".$id;
+}
+
+sub remote_uri_path_for_id {
+ my $self = shift;
+ my $id = shift;
+ return "/ticket/".$id;
+}
+
__PACKAGE__->meta->make_immutable;
no Any::Moose;
1;
commit 1c4c703922acdff74cd0f8352b790576311bbcc3
Author: sunnavy <sunnavy at bestpractical.com>
Date: Fri Jun 12 11:57:14 2009 +0800
added PullEncoder and PushEncoder for github
diff --git a/lib/App/SD/Replica/github/PullEncoder.pm b/lib/App/SD/Replica/github/PullEncoder.pm
new file mode 100644
index 0000000..c7463c5
--- /dev/null
+++ b/lib/App/SD/Replica/github/PullEncoder.pm
@@ -0,0 +1,255 @@
+package App::SD::Replica::github::PullEncoder;
+use Any::Moose;
+extends 'App::SD::ForeignReplica::PullEncoder';
+
+use Params::Validate qw(:all);
+use Memoize;
+use Time::Progress;
+use DateTime;
+
+has sync_source => (
+ isa => 'App::SD::Replica::github',
+ is => 'rw',
+);
+
+my %PROP_MAP = %App::SD::Replica::github::PROP_MAP;
+
+sub ticket_id {
+ my $self = shift;
+ return shift->{number};
+}
+
+=head2 translate_ticket_state
+
+=cut
+
+sub translate_ticket_state {
+ my $self = shift;
+ my $ticket = shift;
+
+ return $ticket;
+}
+
+=head2 find_matching_tickets QUERY
+
+Returns a array of all tickets found matching your QUERY hash.
+
+=cut
+
+sub find_matching_tickets {
+ my $self = shift;
+ my %query = (@_);
+ my $last_changeset_seen_dt = $self->_only_pull_tickets_modified_after()
+ || DateTime->from_epoch( epoch => 0 );
+ $last_changeset_seen_dt->set_time_zone( '-0700' );
+ my $dt = $last_changeset_seen_dt;
+ my $last_dt_str = sprintf(
+ "%4d/%02d/%02d %02d:%02d:%02d -0700",
+ $dt->year, $dt->month, $dt->day,
+ $dt->hour, $dt->minute, $dt->second
+ );
+ my $issue = $self->sync_source->github->issue;
+ my @updated =
+ grep { $_->{updated_at} ge $last_dt_str }
+ ( @{ $issue->list('open') }, @{ $issue->list('closed') } );
+ return \@updated;
+}
+
+sub _only_pull_tickets_modified_after {
+ my $self = shift;
+
+ my $last_pull = $self->sync_source->upstream_last_modified_date();
+ return unless $last_pull;
+ my $before = App::SD::Util::string_to_datetime($last_pull);
+ $self->log_debug( "Failed to parse '" . $self->sync_source->upstream_last_modified_date() . "' as a timestamp. That means we have to sync ALL history") unless ($before);
+ return $before;
+}
+
+=head2 find_matching_transactions { ticket => $id, starting_transaction => $num }
+
+Returns a reference to an array of all transactions (as hashes) on ticket $id after transaction $num.
+
+=cut
+
+sub find_matching_transactions {
+ my $self = shift;
+ my %args = validate( @_, { ticket => 1, starting_transaction => 1 } );
+ my @raw_txns =
+ @{ $self->sync_source->github->issue->comments( $args{ticket}->{number} ) };
+
+ for my $comment (@raw_txns) {
+ $comment->{date} =
+ App::SD::Util::string_to_datetime( $comment->{date} );
+ }
+
+ my @txns;
+ for my $txn ( sort { $a->{id} <=> $b->{id} } @raw_txns ) {
+ my $txn_date = $txn->{date}->epoch;
+
+ # Skip things we know we've already pulled
+ next if $txn_date < ( $args{'starting_transaction'} || 0 );
+
+ # Skip things we've pushed
+ next if (
+ $self->sync_source->foreign_transaction_originated_locally(
+ $txn_date, $args{'ticket'}->{number}
+ )
+ );
+
+ # ok. it didn't originate locally. we might want to integrate it
+ push @txns,
+ {
+ timestamp => $txn->{date},
+ serial => $txn->{id},
+ object => $txn,
+ };
+ }
+
+ my $ticket_created =
+ App::SD::Util::string_to_datetime( $args{ticket}->{created_at} );
+ if ( $ticket_created->epoch >= $args{'starting_transaction'} || 0 ) {
+ unshift @txns,
+ {
+ timestamp => $ticket_created,
+ serial => 0,
+ object => $args{ticket},
+ };
+ }
+
+ $self->sync_source->log_debug('Done looking at pulled txns');
+
+ return \@txns;
+}
+
+sub transcode_create_txn {
+ my $self = shift;
+ my $txn = shift;
+ my $ticket = $txn->{object};
+ my $ticket_uuid =
+ $self->sync_source->uuid_for_remote_id($ticket->{number});
+ my $creator =
+ $self->resolve_user_id_to( email_address => $ticket->{user} );
+ my $created = $txn->{timestamp};
+ my $changeset = Prophet::ChangeSet->new(
+ {
+ original_source_uuid => $ticket_uuid,
+ original_sequence_no => 0,
+ creator => $creator,
+ created => $created->ymd . " " . $created->hms
+ }
+ );
+
+ my $change = Prophet::Change->new(
+ {
+ record_type => 'ticket',
+ record_uuid => $ticket_uuid,
+ change_type => 'add_file',
+ }
+ );
+
+ for my $prop (qw/title body state/) {
+ $change->add_prop_change(
+ name => $PROP_MAP{$prop} || $prop,
+ new => $ticket->{$prop},
+ );
+ }
+
+ $change->add_prop_change(
+ name => $self->sync_source->uuid . '-id',
+ new => $ticket->{number},
+ );
+
+ $changeset->add_change( { change => $change } );
+
+ return $changeset;
+}
+
+# we might get return:
+# 0 changesets if it was a null txn
+# 1 changeset if it was a normal txn
+# 2 changesets if we needed to to some magic fixups.
+
+sub transcode_one_txn {
+ my $self = shift;
+ my $txn_wrapper = shift;
+ my $ticket = shift;
+
+ my $txn = $txn_wrapper->{object};
+ if ( $txn_wrapper->{serial} == 0 ) {
+ return $self->transcode_create_txn($txn_wrapper);
+ }
+
+ my $ticket_uuid =
+ $self->sync_source->uuid_for_remote_id( $ticket->{number} );
+
+ my $changeset = Prophet::ChangeSet->new(
+ {
+ original_source_uuid => $ticket_uuid,
+ original_sequence_no => $txn->{id},
+ creator =>
+ $self->resolve_user_id_to( email_address => $txn->{author} ),
+ created => $txn->{date}->ymd . " " . $txn->{date}->hms
+ }
+ );
+
+ $self->_include_change_comment( $changeset, $ticket_uuid, $txn );
+
+ return unless $changeset->has_changes;
+ return $changeset;
+}
+
+sub _include_change_comment {
+ my $self = shift;
+ my $changeset = shift;
+ my $ticket_uuid = shift;
+ my $txn = shift;
+
+ my $comment = Prophet::Change->new(
+ {
+ record_type => 'comment',
+ record_uuid => $self->sync_source->app_handle->uuid_generator->create_str(),
+ , # comments are never edited, we can have a random uuid
+ change_type => 'add_file'
+ }
+ );
+
+ if ( my $content = $txn->{content} ) {
+ if ( $content !~ /^\s*$/s ) {
+ $comment->add_prop_change(
+ name => 'created',
+ new => $txn->{date}->ymd . ' ' . $txn->{date}->hms,
+ );
+ $comment->add_prop_change(
+ name => 'creator',
+ new =>
+ $self->resolve_user_id_to( email_address => $txn->{author} ),
+ );
+ $comment->add_prop_change( name => 'content', new => $content );
+ $comment->add_prop_change(
+ name => 'content_type',
+ new => 'text/plain',
+ );
+ $comment->add_prop_change( name => 'ticket', new => $ticket_uuid, );
+
+ $changeset->add_change( { change => $comment } );
+ }
+ }
+
+}
+
+sub translate_prop_status {
+ my $self = shift;
+ my $status = shift;
+ return lc($status);
+}
+
+sub resolve_user_id_to {
+ my $self = shift;
+ my $to = shift;
+ my $id = shift;
+ return $id . '@github';
+}
+
+__PACKAGE__->meta->make_immutable;
+no Any::Moose;
+1;
diff --git a/lib/App/SD/Replica/github/PushEncoder.pm b/lib/App/SD/Replica/github/PushEncoder.pm
new file mode 100644
index 0000000..11bca38
--- /dev/null
+++ b/lib/App/SD/Replica/github/PushEncoder.pm
@@ -0,0 +1,149 @@
+package App::SD::Replica::github::PushEncoder;
+use Any::Moose;
+use Params::Validate;
+use Path::Class;
+use Net::Google::Code::Issue;
+use Net::Google::Code;
+
+has sync_source => (
+ isa => 'App::SD::Replica::github',
+ is => 'rw',
+);
+
+sub integrate_change {
+ my $self = shift;
+ my ( $change, $changeset ) = validate_pos(
+ @_,
+ { isa => 'Prophet::Change' },
+ { isa => 'Prophet::ChangeSet' }
+ );
+ my ( $id, $record );
+
+# if the original_sequence_no of this changeset is <=
+# the last changeset our sync source for the original_sequence_no, we can skip it.
+# XXX TODO - this logic should be at the changeset level, not the cahnge level, as it applies to all
+# changes in the changeset
+# warn $self->sync_source->app_handle->handle->last_changeset_from_source(
+# $changeset->original_source_uuid ), "\n";
+ return
+ if $self->sync_source->app_handle->handle->last_changeset_from_source(
+ $changeset->original_source_uuid ) >= $changeset->original_sequence_no;
+
+ my $before_integration = time();
+ my ( $email, $password );
+
+ eval {
+ if ( $change->record_type eq 'ticket'
+ and $change->change_type eq 'add_file' )
+ {
+ $id = $self->integrate_ticket_create( $change, $changeset );
+ $self->sync_source->record_remote_id_for_pushed_record(
+ uuid => $change->record_uuid,
+ remote_id => $id,
+ );
+ }
+ elsif ( $change->record_type eq 'comment'
+ and $change->change_type eq 'add_file' )
+ {
+ $id = $self->integrate_comment( $change, $changeset );
+ }
+ elsif ( $change->record_type eq 'ticket' ) {
+ $id = $self->integrate_ticket_update( $change, $changeset );
+ }
+ else {
+ $self->sync_source->log(
+ 'I have no idea what I am doing for ' . $change->record_uuid );
+ return;
+ }
+
+ $self->sync_source->record_pushed_transactions(
+ start_time => $before_integration,
+ ticket => $id,
+ changeset => $changeset,
+ );
+ };
+
+ if ( my $err = $@ ) {
+ $self->sync_source->log( "Push error: " . $err );
+ }
+
+ return $id;
+}
+
+sub integrate_ticket_update {
+ my $self = shift;
+ my ( $change, $changeset ) = validate_pos(
+ @_,
+ { isa => 'Prophet::Change' },
+ { isa => 'Prophet::ChangeSet' }
+ );
+
+ # Figure out the remote site's ticket ID for this change's record
+ my $remote_ticket_id =
+ $self->sync_source->remote_id_for_uuid( $change->record_uuid );
+ my $ticket = $self->sync_source->github->issue();
+ my $attr = $self->_recode_props_for_integrate($change);
+ $ticket->edit( $remote_ticket_id, $attr->{title}, $attr->{body} );
+ if ( $attr->{status} ) {
+ $ticket->reopen( $remote_ticket_id ) if $attr->{status} eq 'open';
+ $ticket->close( $remote_ticket_id ) if $attr->{status} eq 'closed';
+ }
+ return $remote_ticket_id;
+}
+
+sub integrate_ticket_create {
+ my $self = shift;
+ my ( $change, $changeset ) = validate_pos(
+ @_,
+ { isa => 'Prophet::Change' },
+ { isa => 'Prophet::ChangeSet' }
+ );
+
+ # Build up a ticket object out of all the record's attributes
+ my $ticket = $self->sync_source->github->issue;
+ my $attr = $self->_recode_props_for_integrate($change);
+ my $new =
+ $ticket->open( $attr->{title}, $attr->{body} );
+
+ return $new->{number};
+}
+
+sub integrate_comment {
+ my $self = shift;
+ my ( $change, $changeset ) = validate_pos(
+ @_,
+ { isa => 'Prophet::Change' },
+ { isa => 'Prophet::ChangeSet' }
+ );
+
+ # Figure out the remote site's ticket ID for this change's record
+
+ my %props = map { $_->name => $_->new_value } $change->prop_changes;
+ my $ticket_id = $self->sync_source->remote_id_for_uuid( $props{'ticket'} );
+ my $ticket = $self->sync_source->github->issue();
+ $ticket->comment($ticket_id, $props{'content'});
+ return $ticket_id;
+}
+
+sub _recode_props_for_integrate {
+ my $self = shift;
+ my ($change) = validate_pos( @_, { isa => 'Prophet::Change' } );
+
+ my %props = map { $_->name => $_->new_value } $change->prop_changes;
+ my %attr;
+
+ for my $key ( keys %props ) {
+ if ( $key =~ /^(title|body)/ ) {
+ $attr{$key} = $props{$key};
+ }
+ elsif ( $key eq 'status' ) {
+ $attr{state} = $props{$key} =~ /new|open/ ? 'open' : 'closed';
+ }
+ }
+ return \%attr;
+}
+
+__PACKAGE__->meta->make_immutable;
+no Any::Moose;
+
+1;
-----------------------------------------------------------------------
More information about the Bps-public-commit
mailing list