[Bps-public-commit] SD branch, master, updated. e611e17b537573972897eebc90f6d491bd4cb06e
jesse
jesse at bestpractical.com
Thu May 14 11:45:42 EDT 2009
The branch, master has been updated
via e611e17b537573972897eebc90f6d491bd4cb06e (commit)
via 81fbc2faa95c996f7798c470fc920305f99fb13e (commit)
from 78a782f7f925bfdd7da22d80fd69cf532709d0f4 (commit)
Summary of changes:
lib/App/SD/ForeignReplica.pm | 2 +-
lib/App/SD/Replica/gcode.pm | 86 +++++++++++++++++++++
lib/App/SD/Replica/{trac => gcode}/PullEncoder.pm | 34 +++++----
lib/App/SD/Replica/{trac => gcode}/PushEncoder.pm | 0
lib/App/SD/Replica/trac.pm | 65 +--------------
5 files changed, 111 insertions(+), 76 deletions(-)
create mode 100644 lib/App/SD/Replica/gcode.pm
copy lib/App/SD/Replica/{trac => gcode}/PullEncoder.pm (92%)
copy lib/App/SD/Replica/{trac => gcode}/PushEncoder.pm (100%)
- Log -----------------------------------------------------------------
commit 81fbc2faa95c996f7798c470fc920305f99fb13e
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Thu May 14 11:17:59 2009 -0400
Finish refactoring the trac replica to use the newly factored infrastructure
diff --git a/lib/App/SD/ForeignReplica.pm b/lib/App/SD/ForeignReplica.pm
index 47e7563..ef44086 100644
--- a/lib/App/SD/ForeignReplica.pm
+++ b/lib/App/SD/ForeignReplica.pm
@@ -68,7 +68,7 @@ sub record_pushed_transactions {
# if the transaction id is older than the id of the last changeset
# we got from the original source of this changeset, we're done
- last if $txn->{id} <= $self->upstream_last_txn();
+ last if $txn->{id} <= $self->upstream_last_txn($args{changeset});
# if the transaction from RT is more recent than the most recent
# transaction we got from the original source of the changeset
diff --git a/lib/App/SD/Replica/trac.pm b/lib/App/SD/Replica/trac.pm
index aa31880..000012d 100644
--- a/lib/App/SD/Replica/trac.pm
+++ b/lib/App/SD/Replica/trac.pm
@@ -48,7 +48,6 @@ sub BUILD {
}
-
sub get_txn_list_by_date {
my $self = shift;
my $ticket = shift;
@@ -56,70 +55,16 @@ sub get_txn_list_by_date {
my $ticket_obj = Net::Trac::Ticket->new( connection => $self->trac);
$ticket_obj->load($ticket);
- my @txns = map { { id => $_->id, creator => $_->author, created => $_->date->epoch } }
+ my @txns = map { { id => $_->date->epoch, creator => $_->author, created => $_->date->epoch } }
sort {$b->date <=> $a->date } @{$ticket_obj->history->entries};
return @txns;
}
+
-
-
-sub record_pushed_transactions {
+sub upstream_last_txn {
my $self = shift;
- my %args = validate( @_,
- { ticket => 1, changeset => { isa => 'Prophet::ChangeSet' }, start_time => 1} );
-
-
- my $earliest_valid_txn_date;
-
- # walk through every transaction on the ticket, starting with the latest
- my $ticket = Net::Trac::Ticket->new( connection => $self->trac);
- $ticket->load($args{ticket});
-
- for my $txn ( sort {$b->date <=> $a->date } @{$ticket->history->entries}) {
-
- warn "Recording that we pushed ".$ticket->id. " " .$txn->date;
-
- my $oldest_changeset_for_ticket = $self->app_handle->handle->last_changeset_from_source( $args{changeset}->original_source_uuid);
-
- # walk backwards through all transactions on the ticket we just updated
- # Skip any transaction where the remote user isn't me, this might include any transaction
- # RT created with a scrip on your behalf
-
- next unless $txn->author eq $self->trac->user;
- # XXX - are we always decoding txn author correctly?
-
- # get the completion time _after_ we do our next round trip to rt to try to make sure
- # a bit of lag doesn't skew us to the wrong side of a 1s boundary
- my $txn_created_dt = $txn->date;
- unless($txn_created_dt) {
- die $args{ticket}. " - Couldn't parse '".$txn->created."' as a timestamp";
- }
- my $txn_created = $txn_created_dt->epoch;
-
-
- # skip any transaction created more than 5 seconds before the push started.
- if (!$earliest_valid_txn_date){
- my $change_window = time() - $args{start_time};
- # I can't think of any reason that number shouldn't be 1, but clocks are fickle
- $earliest_valid_txn_date = $txn_created - ($change_window + 5);
- }
-
- last if $txn_created < $earliest_valid_txn_date;
-
- # if the transaction id is older than the id of the last changeset
- # we got from the original source of this changeset, we're done
- last if $txn_created <= $oldest_changeset_for_ticket;
-
- # if the transaction from trac is more recent than the most recent
- # transaction we got from the original source of the changeset
- # then we should record that we sent that transaction upstream
-
- $self->record_pushed_transaction(
- transaction => $txn_created,
- changeset => $args{'changeset'},
- record => $args{'ticket'}
- );
- }
+ my $changeset = shift;
+ return $self->app_handle->handle->last_changeset_from_source( $changeset->original_source_uuid);
}
=head2 uuid
commit e611e17b537573972897eebc90f6d491bd4cb06e
Author: Jesse Vincent <jesse at bestpractical.com>
Date: Thu May 14 11:45:19 2009 -0400
First pass at stealing our trac implementation for google code
diff --git a/lib/App/SD/Replica/gcode.pm b/lib/App/SD/Replica/gcode.pm
new file mode 100644
index 0000000..eb1781e
--- /dev/null
+++ b/lib/App/SD/Replica/gcode.pm
@@ -0,0 +1,86 @@
+package App::SD::Replica::gcode;
+use Any::Moose;
+extends qw/App::SD::ForeignReplica/;
+
+use Params::Validate qw(:all);
+use Path::Class;
+use File::Temp 'tempdir';
+use Memoize;
+
+use constant scheme => 'gcode';
+use constant pull_encoder => 'App::SD::Replica::gcode::PullEncoder';
+use constant push_encoder => 'App::SD::Replica::gcode::PushEncoder';
+
+
+use Prophet::ChangeSet;
+
+has gcode => ( isa => 'Net::Google::Code', is => 'rw');
+has project => ( isa => 'Str', is => 'rw');
+
+sub remote_url { return "http://code.google.com/p/".shift->project}
+sub foreign_username { return shift->gcode->user(@_) }
+
+sub BUILD {
+ my $self = shift;
+
+ # Require rather than use to defer load
+ require Net::Google::Code;
+
+ my ( $project ) = $self->{url} =~ m/^gcode:(.*?)$/
+ or die "Can't parse Google::Code server spec. Expected gcode:k9mail";
+ $self->project($project);
+
+ #( $username, $password ) = $self->prompt_for_login( $uri, $username ) unless $password;
+ $self->gcode( Net::Google::Code->new( project => $self->project));
+ $self->gcode->load();
+}
+
+
+sub get_txn_list_by_date {
+ my $self = shift;
+ my $ticket = shift;
+
+ my $ticket_obj = Net::Google::Code::Issue->new( project => $self->project);
+ $ticket_obj->load($ticket);
+
+ my @txns = map { { id => $_->date->epoch, creator => $_->author, created => $_->date->epoch } }
+ sort {$b->date <=> $a->date } @{$ticket_obj->comments};
+ return @txns;
+}
+
+
+sub upstream_last_txn {
+ my $self = shift;
+ my $changeset = shift;
+ return $self->app_handle->handle->last_changeset_from_source( $changeset->original_source_uuid);
+}
+
+=head2 uuid
+
+Return the replica's UUID
+
+=cut
+
+sub uuid {
+ my $self = shift;
+ return $self->uuid_for_url( $self->remote_url);
+}
+
+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;
diff --git a/lib/App/SD/Replica/gcode/PullEncoder.pm b/lib/App/SD/Replica/gcode/PullEncoder.pm
new file mode 100644
index 0000000..a0e0069
--- /dev/null
+++ b/lib/App/SD/Replica/gcode/PullEncoder.pm
@@ -0,0 +1,397 @@
+package App::SD::Replica::gcode::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::gcode',
+ is => 'rw',
+ weak_ref => 1
+);
+
+sub run {
+ ### XXX TODO - can we factor this out?
+ my $self = shift;
+ my %args = validate(
+ @_,
+ { after => 1,
+ callback => 1,
+ }
+ );
+ $self->sync_source->log('Finding matching tickets');
+ my @tickets = @{ $self->find_matching_tickets() };
+
+ if ( @tickets == 0 ) {
+ $self->sync_source->log("No tickets found.");
+ return;
+ }
+
+ my @changesets;
+ my $counter = 0;
+ $self->sync_source->log("Discovering ticket history");
+ my $progress = Time::Progress->new();
+ $progress->attr( max => $#tickets );
+ local $| = 1;
+
+ my $last_modified_date;
+
+ for my $ticket (@tickets) {
+ print $progress->report( "%30b %p Est: %E\r", $counter );
+ $self->sync_source->log(
+ "Fetching ticket @{[$ticket->id]} - " . ++$counter . " of " . scalar @tickets );
+
+ $last_modified_date = $ticket->last_modified
+ if ( !$last_modified_date || $ticket->last_modified > $last_modified_date );
+
+ my $ticket_data = $self->_translate_final_ticket_state($ticket);
+ my $ticket_initial_data = {%$ticket_data};
+ my $txns = $self->skip_previously_seen_transactions(
+ ticket => $ticket,
+ transactions => $ticket->history->entries,
+ starting_transaction => $self->sync_source->app_handle->handle->last_changeset_from_source(
+ $self->sync_source->uuid_for_remote_id( $ticket->id )
+ )
+
+ );
+
+ # Walk transactions newest to oldest.
+ for my $txn ( sort { $b->date <=> $a->date } @$txns ) {
+ $self->sync_source->log( $ticket->id . " - Transcoding transaction @{[$txn->date]} " );
+
+ # the changesets are older than the ones that came before, so they goes first
+ unshift @changesets,
+ grep {defined} $self->transcode_one_txn( $txn, $ticket_initial_data, $ticket_data );
+ }
+
+ }
+
+ $args{callback}->($_) for @changesets;
+ $self->sync_source->record_upstream_last_modified_date($last_modified_date);
+}
+
+
+sub _translate_final_ticket_state {
+ my $self = shift;
+ my $ticket_object = shift;
+
+ my $content = $ticket_object->description;
+ my $ticket_data = {
+
+ $self->sync_source->uuid . '-id' => $ticket_object->id,
+
+ owner => ( $ticket_object->owner || undef ),
+ type => ($ticket_object->type || undef),
+ created => ( $ticket_object->created->ymd . " " . $ticket_object->created->hms ),
+ reporter => ( $ticket_object->reporter || undef ),
+ status => $self->translate_status( $ticket_object->status ),
+ summary => ( $ticket_object->summary || undef ),
+ description => ( $content||undef),
+ tags => ( $ticket_object->keywords || undef ),
+ component => ( $ticket_object->component || undef ),
+ milestone => ( $ticket_object->milestone || undef ),
+ priority => ( $ticket_object->priority || undef ),
+ severity => ( $ticket_object->severity || undef ),
+ cc => ( $ticket_object->cc || undef ),
+ };
+
+
+
+
+ # delete undefined and empty fields
+ delete $ticket_data->{$_}
+ for grep !defined $ticket_data->{$_} || $ticket_data->{$_} eq '', keys %$ticket_data;
+
+ return $ticket_data;
+}
+
+=head2 find_matching_tickets QUERY
+
+Returns a Google Code::TicketSearch collection for 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();
+ $self->sync_source->log("Searching for tickets");
+ require Net::Google::Code::Issue::Search;
+ my $search = Net::Google::Code::Issue::Search->new( project => $self->sync_source->project, limit => 9999 );
+ $search->search();
+ my @ids = @{$search->ids};
+ my @results;
+ foreach my $item (@results) {
+ my $t = $self->sync_source->ticket->load($item);
+ if (!$last_changeset_seen_dt || ($t->last_modified >= $last_changeset_seen_dt)) {
+ push @results, $t;
+ }
+ }
+ return \@results;
+}
+
+=head2 skip_previously_seen_transactions { ticket => $id, starting_transaction => $num, transactions => \@txns }
+
+Returns a reference to an array of all transactions (as hashes) on ticket $id after transaction $num.
+
+=cut
+
+sub skip_previously_seen_transactions {
+ my $self = shift;
+ my %args = validate( @_, { ticket => 1, transactions => 1, starting_transaction => 0 } );
+ my @txns;
+
+ for my $txn ( sort @{ $args{transactions} } ) {
+ 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'}->id) );
+
+ # ok. it didn't originate locally. we might want to integrate it
+ push @txns, $txn;
+ }
+ $self->sync_source->log('Done looking at pulled txns');
+ return \@txns;
+}
+
+sub build_initial_ticket_state {
+ my $self = shift;
+ my $final_state = shift;
+ my $ticket_object = shift;
+
+ my %initial_state = %{$final_state};
+
+ for my $txn ( reverse @{ $ticket_object->history->entries } ) {
+ for my $pc ( values %{ $txn->prop_changes } ) {
+ unless ( $initial_state{ $pc->property } eq $pc->new_value ) {
+ warn "I was expecting "
+ . $pc->property
+ . " to be "
+ . $pc->new_value
+ . " but it was actually "
+ . $initial_state{ $pc->property };
+ }
+ $initial_state{ $pc->property } = $pc->old_value;
+
+ }
+ }
+ return \%initial_state;
+}
+
+sub transcode_create_txn {
+ my $self = shift;
+ my $txn = shift;
+ my $create_data = shift;
+ my $final_data = shift;
+ my $ticket = $txn->ticket;
+ # this sequence_no only works because gcode tickets only allow one update
+ # per ticket per second.
+ # we decrement by 1 on the off chance that someone created and
+ # updated the ticket in the first second
+ my $changeset = Prophet::ChangeSet->new(
+ { original_source_uuid => $self->sync_source->uuid_for_remote_id( $ticket->id ),
+ original_sequence_no => ( $ticket->created->epoch-1),
+ creator => $self->resolve_user_id_to( email_address => $create_data->{reporter} ),
+ created => $ticket->created->ymd ." ".$ticket->created->hms
+ }
+ );
+
+ my $change = Prophet::Change->new(
+ { record_type => 'ticket',
+ record_uuid => $self->sync_source->uuid_for_remote_id( $ticket->id ),
+ change_type => 'add_file'
+ }
+ );
+
+ for my $prop ( keys %$create_data ) {
+ next unless defined $create_data->{$prop};
+ next if $prop =~ /^(?:patch)$/;
+ $change->add_prop_change( name => $prop, old => '', new => $create_data->{$prop} );
+ }
+
+ $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, $txn, $ticket, $ticket_final ) = (@_);
+
+
+ if ($txn->is_create) {
+ return $self->transcode_create_txn($txn,$ticket,$ticket_final);
+ }
+
+ my $ticket_uuid = $self->sync_source->uuid_for_remote_id( $ticket->{ $self->sync_source->uuid . '-id' } );
+
+ my $changeset = Prophet::ChangeSet->new(
+ { original_source_uuid => $ticket_uuid,
+ original_sequence_no => $txn->date->epoch, # see comment on ticket
+ # create changeset
+ creator => $self->resolve_user_id_to( email_address => $txn->author ),
+ created => $txn->date->ymd . " " . $txn->date->hms
+ }
+ );
+
+ my $change = Prophet::Change->new(
+ { record_type => 'ticket',
+ record_uuid => $ticket_uuid,
+ change_type => 'update_file'
+ }
+ );
+
+# warn "right here, we need to deal with changed data that gcode failed to record";
+
+ foreach my $prop_change ( values %{ $txn->prop_changes || {} } ) {
+ my $new = $prop_change->new_value;
+ my $old = $prop_change->old_value;
+ my $property = $prop_change->property;
+ next if $property =~ /^(?:patch)$/;
+
+ $old = undef if ( $old eq '' );
+ $new = undef if ( $new eq '' );
+
+ if (!exists $ticket_final->{$property}) {
+ $ticket_final->{$property} = $new;
+ $ticket->{$property} = $new;
+ }
+
+
+ # walk back $ticket's state
+ if ( ( !defined $new && !defined $ticket->{$property} )
+ || ( defined $new && defined $ticket->{$property} && $ticket->{$property} eq $new ) )
+ {
+ $ticket->{$property} = $old;
+ }
+
+ $change->add_prop_change( name => $property, old => $old, new => $new );
+
+ }
+
+ $changeset->add_change( { change => $change } ) if $change->has_prop_changes;
+
+ my $comment = Prophet::Change->new(
+ { record_type => 'comment',
+ record_uuid => Data::UUID->new->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/html' );
+ $comment->add_prop_change( name => 'ticket', new => $ticket_uuid);
+
+ $changeset->add_change( { change => $comment } );
+ }
+ }
+
+ return undef unless $changeset->has_changes;
+ return $changeset;
+}
+
+sub _recode_attachment_create {
+ my $self = shift;
+ my %args = validate( @_, { ticket => 1, txn => 1, changeset => 1, attachment => 1 } );
+ my $change = Prophet::Change->new(
+ { record_type => 'attachment',
+ record_uuid => $self->sync_source->uuid_for_url(
+ $self->sync_source->remote_url . "/attachment/" . $args{'attachment'}->{'id'}
+ ),
+ change_type => 'add_file'
+ }
+ );
+ $change->add_prop_change(
+ name => 'content_type',
+ old => undef,
+ new => $args{'attachment'}->{'ContentType'}
+ );
+ $change->add_prop_change( name => 'created', old => undef, new => $args{'txn'}->{'Created'} );
+ $change->add_prop_change(
+ name => 'creator',
+ old => undef,
+ new => $self->resolve_user_id_to( email_address => $args{'attachment'}->{'Creator'} )
+ );
+ $change->add_prop_change(
+ name => 'content',
+ old => undef,
+ new => $args{'attachment'}->{'Content'}
+ );
+ $change->add_prop_change(
+ name => 'name',
+ old => undef,
+ new => $args{'attachment'}->{'Filename'}
+ );
+ $change->add_prop_change(
+ name => 'ticket',
+ old => undef,
+ new => $self->sync_source->uuid_for_remote_id(
+ $args{'ticket'}->{ $self->sync_source->uuid . '-id' }
+ )
+ );
+ $args{'changeset'}->add_change( { change => $change } );
+}
+
+sub translate_status {
+ my $self = shift;
+ my $status = shift;
+
+ $status =~ s/^resolved$/closed/;
+ return $status;
+}
+
+my %PROP_MAP;
+sub translate_prop_names {
+ my $self = shift;
+ my $changeset = shift;
+
+ for my $change ( $changeset->changes ) {
+ next unless $change->record_type eq 'ticket';
+
+ my @new_props;
+ for my $prop ( $change->prop_changes ) {
+ next if ( ( $PROP_MAP{ lc( $prop->name ) } || '' ) eq '_delete' );
+ $prop->name( $PROP_MAP{ lc( $prop->name ) } ) if $PROP_MAP{ lc( $prop->name ) };
+
+ # Normalize away undef -> "" and vice-versa
+ for (qw/new_value old_value/) {
+ $prop->$_("") if !defined( $prop->$_() );
+ }
+ next if ( $prop->old_value eq $prop->new_value );
+
+ if ( $prop->name =~ /^cf-(.*)$/ ) {
+ $prop->name( 'custom-' . $1 );
+ }
+
+ push @new_props, $prop;
+
+ }
+ $change->prop_changes( \@new_props );
+
+ }
+ return $changeset;
+}
+
+sub resolve_user_id_to {
+ my $self = shift;
+ my $to = shift;
+ my $id = shift;
+ return $id . '@gcode-instance.local';
+
+}
+
+__PACKAGE__->meta->make_immutable;
+no Any::Moose;
+1;
diff --git a/lib/App/SD/Replica/gcode/PushEncoder.pm b/lib/App/SD/Replica/gcode/PushEncoder.pm
new file mode 100644
index 0000000..7621715
--- /dev/null
+++ b/lib/App/SD/Replica/gcode/PushEncoder.pm
@@ -0,0 +1,167 @@
+package App::SD::Replica::trac::PushEncoder;
+use Any::Moose;
+use Params::Validate;
+use Path::Class;
+use Time::HiRes qw/usleep/;
+
+has sync_source =>
+ ( isa => 'App::SD::Replica::trac',
+ 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
+ return
+ if $self->sync_source->app_handle->handle->last_changeset_from_source(
+ $changeset->original_source_uuid
+ ) >= $changeset->original_sequence_no;
+
+ my $before_integration = time();
+
+ 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 'attachment'
+ and $change->change_type eq 'add_file') {
+ $id = $self->integrate_attachment( $change, $changeset );
+ }
+ 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 undef;
+ }
+
+ $self->sync_source->record_pushed_transactions(
+ start_time => $before_integration,
+ ticket => $id,
+ changeset => $changeset);
+ };
+
+ if (my $err = $@) {
+ $self->sync_source->log("Push error: ".$err);
+ }
+
+ usleep(1100); # trac only accepts one ticket update per second. Yes.
+
+ 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 = Net::Trac::Ticket->new( connection => $self->sync_source->trac);
+ $ticket->load($remote_ticket_id);
+ $ticket->update( %{ $self->_recode_props_for_integrate($change) }, no_auto_status => 1);
+ 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 = Net::Trac::Ticket->new(
+ connection => $self->sync_source->trac);
+ my $id = $ticket->create( %{ $self->_recode_props_for_integrate($change) });
+
+ return $id
+}
+
+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 = Net::Trac::Ticket->new( trac => $self->sync_source->trac, id => $ticket_id);
+
+ my %content = ( message => $props{'content'},
+ );
+
+ if ( ($props{'type'} ||'') eq 'comment' ) {
+ $ticket->comment( %content);
+ } else {
+ $ticket->correspond(%content);
+ }
+ return $ticket_id;
+}
+
+sub integrate_attachment {
+ my ($self, $change, $changeset ) = validate_pos( @_, { isa => 'App::SD::Replica::rt::PushEncoder'}, { isa => 'Prophet::Change' }, { isa => 'Prophet::ChangeSet' });
+
+
+ my %props = map { $_->name => $_->new_value } $change->prop_changes;
+ my $ticket_id = $self->sync_source->remote_id_for_uuid( $props{'ticket'});
+ my $ticket = Net::Trac::Ticket->new( trac => $self->sync_source->trac, id => $ticket_id );
+
+ my $tempdir = File::Temp::tempdir( CLEANUP => 1 );
+ my $file = file( $tempdir => ( $props{'name'} || 'unnamed' ) );
+ my $fh = $file->openw;
+ print $fh $props{content};
+ close $fh;
+ my %content = ( message => '(See attachments)', attachments => ["$file"]);
+ $ticket->correspond(%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 ) {
+ next unless ( $key =~ /^(summary|queue|status|owner|custom)/ );
+ if ( $key =~ /^custom-(.*)/ ) {
+ $attr{cf}->{$1} = $props{$key};
+ } else {
+ $attr{$key} = $props{$key};
+ }
+ if ( $key eq 'status' ) {
+ $attr{$key} =~ s/^closed$/resolved/;
+ }
+ }
+ return \%attr;
+}
+
+__PACKAGE__->meta->make_immutable;
+no Any::Moose;
+
+1;
-----------------------------------------------------------------------
More information about the Bps-public-commit
mailing list