[Bps-public-commit] r11309 - in SVN-PropDB: lib/Prophet lib/Prophet/Sync/Source t

clkao at bestpractical.com clkao at bestpractical.com
Mon Mar 31 19:44:46 EDT 2008


Author: clkao
Date: Mon Mar 31 19:44:45 2008
New Revision: 11309

Modified:
   SVN-PropDB/lib/Prophet/Change.pm
   SVN-PropDB/lib/Prophet/ChangeSet.pm
   SVN-PropDB/lib/Prophet/Conflict.pm
   SVN-PropDB/lib/Prophet/ConflictingChange.pm
   SVN-PropDB/lib/Prophet/Handle.pm
   SVN-PropDB/lib/Prophet/Sync/Source.pm
   SVN-PropDB/lib/Prophet/Sync/Source/SVN.pm
   SVN-PropDB/lib/Prophet/Test.pm
   SVN-PropDB/t/real-conflicting-merge.t
   SVN-PropDB/t/simple-conflicting-merge.t

Log:
resolution, resolution, resolutions.


Modified: SVN-PropDB/lib/Prophet/Change.pm
==============================================================================
--- SVN-PropDB/lib/Prophet/Change.pm	(original)
+++ SVN-PropDB/lib/Prophet/Change.pm	Mon Mar 31 19:44:45 2008
@@ -5,9 +5,8 @@
 use base qw/Class::Accessor/;
 
 use Prophet::PropChange;
-
 use Params::Validate;
-__PACKAGE__->mk_accessors(qw/node_type node_uuid change_type/);
+__PACKAGE__->mk_accessors(qw/node_type node_uuid change_type resolution_cas/);
 
 =head1 NAME
 
@@ -41,7 +40,23 @@
 
 sub prop_changes {
     my $self = shift;
-    return @{$self->{prop_changes}};
+    Carp::cluck unless $self->{prop_changes};
+    return @{$self->{prop_changes} || []};
+}
+
+=head2 new_from_conflict( $conflict )
+
+=cut
+
+sub new_from_conflict {
+    my ($class, $conflict) = @_;
+    my $self = $class->new
+        ( { is_resolution => 1,
+            resolution_cas => $conflict->cas_key,
+            change_type => $conflict->change_type,
+            node_type   => $conflict->node_type,
+            node_uuid   => $conflict->node_uuid } );
+    return $self;
 }
 
 
@@ -67,4 +82,21 @@
 }
 
 
+sub as_hash {
+ my $self = shift;
+        my $props = {};
+        for my $pc ($self->prop_changes) {
+                $props->{$pc->name} = { old_value => $pc->old_value, new_value => $pc->new_value};
+        }
+ 
+  return      { node_type => $self->node_type, 
+                        change_type => $self->change_type,
+                        prop_changes => $props
+
+        };
+    }
+
+
+
+
 1;

Modified: SVN-PropDB/lib/Prophet/ChangeSet.pm
==============================================================================
--- SVN-PropDB/lib/Prophet/ChangeSet.pm	(original)
+++ SVN-PropDB/lib/Prophet/ChangeSet.pm	Mon Mar 31 19:44:45 2008
@@ -90,4 +90,18 @@
     return $self->changes ? 0 : 1;
 }
 
+
+sub as_hash {
+    my $self = shift;
+    my $as_hash = { map { $_ => $self->$_() } qw(sequence_no source_uuid original_source_uuid original_sequence_no is_nullification is_resolution is_empty)};
+    
+    
+    for my $change ($self->changes){
+        
+ 
+        $as_hash->{changes}->{$change->node_uuid} = $change->as_hash;
+    }
+    return $as_hash;
+}
+
 1;

Modified: SVN-PropDB/lib/Prophet/Conflict.pm
==============================================================================
--- SVN-PropDB/lib/Prophet/Conflict.pm	(original)
+++ SVN-PropDB/lib/Prophet/Conflict.pm	Mon Mar 31 19:44:45 2008
@@ -8,7 +8,7 @@
 use Prophet::ConflictingPropChange;
 use Prophet::ConflictingChange;
 
-__PACKAGE__->mk_accessors(qw/prophet_handle resolvers  nullification_changeset resolution_changeset autoresolved/);
+__PACKAGE__->mk_accessors(qw/prophet_handle resolvers changeset nullification_changeset resolution_changeset autoresolved/);
 
 =head2 analyze_changeset Prophet::ChangeSet
 
@@ -20,9 +20,9 @@
 
 sub analyze_changeset {
     my $self = shift;
-    my ($changeset) = validate_pos( @_, { isa => 'Prophet::ChangeSet' } );
+    #my ($changeset) = validate_pos( @_, { isa => 'Prophet::ChangeSet' } );
 
-    $self->generate_changeset_conflicts($changeset);
+    $self->generate_changeset_conflicts();
     return unless (@{$self->conflicting_changes});
 
     $self->generate_nullification_changeset;
@@ -50,15 +50,13 @@
     for my $conflict ( @{ $self->conflicting_changes } ) {
         for (@resolvers) {
             if (my $resolution = $_->($conflict)) {
-            warn $_;
-                $resolutions->add_change(change => $resolution);
+                $resolutions->add_change(change => $resolution) if $resolution->prop_changes;
                 last;
             }
         }
     }
 
     $self->resolution_changeset($resolutions);
-    
     return 1;
 }
 
@@ -97,20 +95,14 @@
     my $conflict = shift;
   # for everything from the changeset that is the same as the old value of the target replica
     # we can skip applying 
-warn "attempting to resolve conflict automatically";
-warn Dumper(@_);use Data::Dumper;
     return 0 if $conflict->file_op_conflict;
 
-    my $resolution = Prophet::Change->new( { is_resolution => 1, 
-                                             node_type => $conflict->node_type,
-                                             node_uuid => $conflict->node_uuid });
-
+    my $resolution = Prophet::Change->new_from_conflict( $conflict );
 
     for my $prop_change ( @{$conflict->prop_conflicts} ) {
         return 0 unless $prop_change->target_value eq $prop_change->source_new_value
     }
 
-warn Dumper($resolution);
     $self->autoresolved(1);
 
     return $resolution;
@@ -123,7 +115,7 @@
 }
 
 
-=head2 generate_changeset_conflicts Prophet::ChangeSet
+=head2 generate_changeset_conflicts 
 
 Given a changeset, populates $self->conflicting_changes with all the conflicts that applying that changeset to the target replica would result in.
 
@@ -132,8 +124,7 @@
 
 sub generate_changeset_conflicts {
     my $self = shift;
-    my ($changeset) = validate_pos( @_, { isa => 'Prophet::ChangeSet' } );
-    for my $change ( $changeset->changes ) {
+    for my $change ( $self->changeset->changes ) {
         if ( my $change_conflicts = $self->_generate_change_conflicts($change) ) {
             push @{ $self->conflicting_changes }, $change_conflicts;
         }

Modified: SVN-PropDB/lib/Prophet/ConflictingChange.pm
==============================================================================
--- SVN-PropDB/lib/Prophet/ConflictingChange.pm	(original)
+++ SVN-PropDB/lib/Prophet/ConflictingChange.pm	Mon Mar 31 19:44:45 2008
@@ -6,6 +6,7 @@
 use Prophet::ConflictingPropChange;
 
 use base qw/Class::Accessor/;
+use Storable 'dclone';
 
 # change_type is one of: add_file add_dir update delete
 __PACKAGE__->mk_accessors(qw/node_type node_uuid source_node_exists target_node_exists change_type file_op_conflict/);
@@ -24,4 +25,33 @@
 
 }
 
+=head2 neutralize
+
+Returns the clone of the changeset, except hte propchanges will have target_value and source_new_value as a sorted "choices" field of arrayref.
+
+=cut
+
+sub neutralize {
+    my $self = shift;
+    my $struct = dclone($self);
+    for (@{$struct->{prop_conflicts}}) {
+        $_->{choices} = [ sort (delete $_->{source_new_value}, delete $_->{target_value}) ];
+    }
+    return $struct;
+}
+
+=head2 cas_key
+
+returned the key signatured by the content of the conflicting change.
+
+=cut
+
+use YAML::Syck;
+use Digest::MD5 'md5_hex';
+
+sub cas_key {
+    my $self = shift;
+    return md5_hex(YAML::Syck::Dump($self->neutralize));
+}
+
 1;

Modified: SVN-PropDB/lib/Prophet/Handle.pm
==============================================================================
--- SVN-PropDB/lib/Prophet/Handle.pm	(original)
+++ SVN-PropDB/lib/Prophet/Handle.pm	Mon Mar 31 19:44:45 2008
@@ -92,7 +92,7 @@
 sub commit_edit {
     my $self = shift;
     my $txn  = shift;
-    $self->current_edit->change_prop( 'svn:author', $ENV{'USER'} );
+    $self->current_edit->change_prop( 'svn:author', ($ENV{'PROPHET_USER'} ||$ENV{'USER'} ));
     $self->current_edit->commit;
     $self->current_edit(undef);
 
@@ -123,13 +123,41 @@
     $self->commit_edit();
 }
 
+sub record_resolutions {
+    my $self      = shift;
+    my $changeset = shift;
+
+    return unless $changeset->changes;
+
+    $self->begin_edit();
+    $self->record_changeset($changeset);
+    
+    warn "to commit... " if ($DEBUG);
+    my $changed = $self->current_edit->root->paths_changed;
+    warn Dumper($changed) if ($DEBUG);
+
+use Data::Dumper;
+    warn Dumper($changeset);
+    $self->record_resolution($_) for $changeset->changes;
+    $self->commit_edit();
+}
+
+sub record_resolution {
+    my ($self, $change) = @_;
+    
+    # XXX for now, ignore if there's existing stored resolution on this conflict
+    return if $self->node_exists( uuid => $change->resolution_cas, type => '_prophet_resolution' );
+    $self->create_node( uuid => $change->resolution_cas, type => '_prophet_resolution',
+                        props => { _meta => $change->change_type,
+                                    map { $_->name => $_->new_value } $change->prop_changes } );
+}
+
 sub record_changeset {
     my $self = shift;
     my $changeset = shift;
 
     
     my $inside_edit = $self->current_edit ? 1: 0;
-    warn "==> to record $changeset / $inside_edit";
     $self->begin_edit() unless ($inside_edit);
     $self->_integrate_change($_) for ($changeset->changes);
     $self->current_edit->change_prop( 'prophet:special-type'  => 'nullification') if ($changeset->is_nullification);

Modified: SVN-PropDB/lib/Prophet/Sync/Source.pm
==============================================================================
--- SVN-PropDB/lib/Prophet/Sync/Source.pm	(original)
+++ SVN-PropDB/lib/Prophet/Sync/Source.pm	Mon Mar 31 19:44:45 2008
@@ -61,12 +61,25 @@
     for my $changeset (@$changesets_to_integrate) {
     
        next if ( $self->has_seen_changeset($changeset) );
+       next if $changeset->is_nullification || $changeset->is_resolution;
+       
         $self->integrate_changeset( changeset => $changeset, conflict_callback => $args{conflict_callback}, resolver => $args{resolver});
 
     }
 }
 
+sub fetch_resolutions {
+    my $self = shift;
+    my %args   = validate( @_, { from => { isa => 'Prophet::Sync::Source'},
+                                 resolver => { optional => 1},
+                                 conflict_callback => { optional => 1 } } );
+    my $source = $args{'from'};
+    
+    my $changesets
+        = $source->fetch_changesets( after => $self->last_changeset_from_source( $source->uuid ) );
 
+    return grep { $_->is_resolution && !$self->has_seen_changeset($_) } @$changesets;
+}
 
 
 

Modified: SVN-PropDB/lib/Prophet/Sync/Source/SVN.pm
==============================================================================
--- SVN-PropDB/lib/Prophet/Sync/Source/SVN.pm	(original)
+++ SVN-PropDB/lib/Prophet/Sync/Source/SVN.pm	Mon Mar 31 19:44:45 2008
@@ -90,17 +90,18 @@
     my $self  = shift;
     my $entry = shift;
     my $revprops = shift;
-
     my $changeset = Prophet::ChangeSet->new(
         {   sequence_no          => $entry->{'revision'},
             source_uuid          => $self->uuid,
-            original_source_uuid => $revprops->{'prophet:original-source'} || $self->uuid,
+            original_source_uuid => $revprops->{'prophet:original-source'}|| $self->uuid,
             original_sequence_no => $revprops->{'prophet:original-sequence-no'} || $entry->{'revision'},
-            is_nullification     => (($revprops->{'prophet:special-type'} || '') eq 'nullification'),
-            is_resolution        => (($revprops->{'prophet:special-type'} || '') eq 'resolution'),
+            is_nullification     => (($revprops->{'prophet:special-type'} || '') eq 'nullification') ? 1 : undef ,
+            is_resolution        => (($revprops->{'prophet:special-type'} || '') eq 'resolution') ? 1: undef ,
 
         });
 
+
+    
     # add each node's changes to the changeset
     for my $path ( keys %{ $entry->{'paths'} } ) {
         if ( $path =~ qr|^(.+)/(.*?)/(.*?)$| ) {
@@ -198,9 +199,9 @@
     my $self = shift;
     my ($changeset) = validate_pos( @_, { isa => "Prophet::ChangeSet" } );
 
-    my $conflict = Prophet::Conflict->new({ prophet_handle => $self->prophet_handle});
+    my $conflict = Prophet::Conflict->new({ changeset => $changeset, prophet_handle => $self->prophet_handle});
 
-    $conflict->analyze_changeset($changeset);
+    $conflict->analyze_changeset();
 
     return undef unless @{$conflict->conflicting_changes};
 
@@ -254,7 +255,6 @@
         $args{conflict_callback}->($conflict) if $args{'conflict_callback'};
         $conflict->resolvers([sub { $args{resolver}->(@_) }]) if $args{resolver};
         my $resolutions = $conflict->generate_resolution;
-        Carp::cluck;
         #figure out our conflict resolution
 
         # IMPORTANT: these should be an atomic unit. dying here would be poor.  BUT WE WANT THEM AS THREEDIFFERENT SVN REVS
@@ -264,9 +264,7 @@
         # integrate the original change
         $self->prophet_handle->integrate_changeset($changeset);
         # integrate the conflict resolution change
-        $self->prophet_handle->record_changeset($conflict->resolution_changeset);
-
-
+        $self->prophet_handle->record_resolutions($conflict->resolution_changeset);
     } else {
         $self->prophet_handle->integrate_changeset($changeset);
 
@@ -276,7 +274,7 @@
 sub remove_redundant_data {
     my ($self, $changeset) = @_;
     # XXX: encapsulation
-    $changeset->{changes} = [ grep {
+    $changeset->{changes} = [ grep { $_->node_type ne '_prophet_resolution' } grep {
         !($_->node_type eq $Prophet::Handle::MERGETICKET_METATYPE &&
           $_->node_uuid eq $self->prophet_handle->uuid)
     } $changeset->changes ];
@@ -304,5 +302,26 @@
 
 }
 
+=head2 always_mine_resolver
+
+=cut
+
+sub always_mine_resolver {
+    return
+       sub { my $conflict = shift;
+            return 0 if $conflict->file_op_conflict;
+
+            my $resolution = Prophet::Change->new_from_conflict( $conflict );
+
+            for my $prop_conflict ( @{ $conflict->prop_conflicts } ) {
+                $resolution->add_prop_change(
+                    name => $prop_conflict->name,
+                    old  => $prop_conflict->source_old_value,
+                    new  => $prop_conflict->target_value
+                );
+            }
+            return $resolution;
+    };
+}
 
 1;

Modified: SVN-PropDB/lib/Prophet/Test.pm
==============================================================================
--- SVN-PropDB/lib/Prophet/Test.pm	(original)
+++ SVN-PropDB/lib/Prophet/Test.pm	Mon Mar 31 19:44:45 2008
@@ -2,13 +2,16 @@
 use warnings;
 package Prophet::Test;
 use base qw/Test::More Exporter/;
-our @EXPORT = qw/as_alice as_bob as_charlie as_david run_ok repo_uri_for run_script run_output_matches replica_last_rev replica_merge_tickets replica_uuid_for/;
+our @EXPORT = qw/as_alice as_bob as_charlie as_david run_ok repo_uri_for run_script run_output_matches replica_last_rev replica_merge_tickets replica_uuid_for fetch_newest_changesets ok_added_revisions
+serialize_conflict serialize_changeset
+/;
 
 use File::Path 'rmtree';
 use File::Temp qw/tempdir/;
 use Path::Class 'dir';
 use Test::Exception;
 use IPC::Run3 'run3';
+use Params::Validate ':all';
 
 use Prophet::CLI;
 
@@ -217,8 +220,9 @@
 sub as_user {
   my $username = shift;
   my $coderef = shift;
-
+  local $ENV{'PROPHET_USER'} = $username;
   local $ENV{'PROPHET_REPO'} = repo_path_for($username);
+  diag("I am $username. My replica id is ".replica_uuid());
  my $ret=  $coderef->();
  $REPLICA_UUIDS{$username} = replica_uuid();
  return $ret;
@@ -231,6 +235,70 @@
 
 }
 
+=head2 fetch_newest_changesets COUNT
+
+Returns C<COUNT> newest L<Prophet::ChangeSet> objects in the current user's replica.
+
+=cut
+
+sub fetch_newest_changesets {
+    my $count = shift;
+    my $source = Prophet::Sync::Source->new( { url => repo_uri_for($ENV{'PROPHET_USER'})} );
+    return @{$source->fetch_changesets( after => (replica_last_rev() - $count))};
+
+
+}
+
+
+=head2 ensure_new_revisions { CODE }, $numbers_of_new_revisions, $msg
+
+=cut
+
+sub ok_added_revisions (&$$) {
+    my ($code, $num, $msg) = @_;
+    my $last_rev = replica_last_rev();
+    $code->();    
+    is( replica_last_rev(), $last_rev + $num, $msg );
+}
+
+
+
+=head2 serialize_conflict Prophet::Conflict
+
+returns a simple, serialized version of a Prophet::Conflict object suitable for comparison in tests
+
+=cut
+
+sub serialize_conflict {
+    my ($conflict_obj) = validate_pos(@_, { isa => 'Prophet::Conflict'});    
+    my $conflicts;
+    for my $change ( @{$conflict_obj->conflicting_changes} ) {
+        $conflicts->{meta} = { original_source_uuid => $conflict_obj->changeset->original_source_uuid};
+        $conflicts->{records}->{$change->node_uuid} =  {
+                change_type => $change->change_type,
+                };
+
+        for my $propchange (@{$change->prop_conflicts}) {
+             $conflicts->{records}->{$change->node_uuid}->{props}->{$propchange->name} = { source_old => $propchange->source_old_value,
+                source_new => $propchange->source_new_value,
+                target_old => $propchange->target_value    
+        }
+
+            
+    }
+}
+return $conflicts;
+}
+
+
+sub serialize_changeset {
+    my $cs = shift;
+    
+    return $cs->as_hash;
+    }
+    
+    
+
 =head2 as_alice CODE, as_bob CODE, as_charlie CODE, as_david CODE
 
 Runs CODE as alice, bob, charlie or david

Modified: SVN-PropDB/t/real-conflicting-merge.t
==============================================================================
--- SVN-PropDB/t/real-conflicting-merge.t	(original)
+++ SVN-PropDB/t/real-conflicting-merge.t	Mon Mar 31 19:44:45 2008
@@ -1,16 +1,15 @@
-#!/usr/bin/perl 
-#
+#!/usr/bin/perl
+
 use warnings;
 use strict;
 use Test::Exception;
 
-use Prophet::Test tests => 12;
+use Prophet::Test tests => 16;
 
 as_alice {
-    run_ok('prophet-node-create', [qw(--type Bug --status new --from alice )], "Created a record as alice"); 
-    run_output_matches('prophet-node-search', [qw(--type Bug --regex .)], [qr/new/], " Found our record");
-    };
-
+    run_ok( 'prophet-node-create', [qw(--type Bug --status new --from alice )], "Created a record as alice" );
+    run_output_matches( 'prophet-node-search', [qw(--type Bug --regex .)], [qr/new/], " Found our record" );
+};
 
 diag('Bob syncs from alice');
 
@@ -18,84 +17,125 @@
 
 as_bob {
 
-    run_ok('prophet-node-create', [qw(--type Dummy --ignore yes)], "Created a dummy record"); 
-  
-    run_ok('prophet-merge', ['--to', repo_uri_for('bob'), '--from', repo_uri_for('alice')], "Sync ran ok!");
+    run_ok( 'prophet-node-create', [qw(--type Dummy --ignore yes)], "Created a dummy record" );
+
+    run_ok( 'prophet-merge', [ '--to', repo_uri_for('bob'), '--from', repo_uri_for('alice') ], "Sync ran ok!" );
+
     # check our local replicas
-   my  ($ret, $out, $err) = run_script('prophet-node-search', [qw(--type Bug --regex .)]);
-    like($out, qr/new/, "We have the one node from alice") ;
-    if ($out =~ /^(.*?)\s./) {
+    my ( $ret, $out, $err ) = run_script( 'prophet-node-search', [qw(--type Bug --regex .)] );
+    like( $out, qr/new/, "We have the one node from alice" );
+    if ( $out =~ /^(.*?)\s./ ) {
         $record_id = $1;
     }
     diag($record_id);
 
-    run_ok('prophet-node-update', ['--type','Bug','--uuid',$record_id, '--status' => 'stalled']);
-    run_output_matches('prophet-node-show', ['--type', 'Bug', '--uuid', $record_id],
-                       ['id: '.$record_id, 'status: stalled', 'from: alice'],
-                       'content is correct');
+    run_ok( 'prophet-node-update', [ '--type', 'Bug', '--uuid', $record_id, '--status' => 'stalled' ] );
+    run_output_matches( 'prophet-node-show', [ '--type', 'Bug', '--uuid', $record_id ],
+        [ 'id: ' . $record_id, 'status: stalled', 'from: alice' ],
+        'content is correct' );
 };
 
 as_alice {
-    run_ok('prophet-node-update', ['--type','Bug','--uuid',$record_id, '--status' => 'open']);
-    run_output_matches('prophet-node-show', ['--type', 'Bug', '--uuid', $record_id],
-                       ['id: '.$record_id, 'status: open', 'from: alice'],
-                       'content is correct');
+    run_ok( 'prophet-node-update', [ '--type', 'Bug', '--uuid', $record_id, '--status' => 'open' ] );
+    run_output_matches( 'prophet-node-show', [ '--type', 'Bug', '--uuid', $record_id ],
+        [ 'id: ' . $record_id, 'status: open', 'from: alice' ],
+        'content is correct' );
 
 };
 
 # This conflict, we can autoresolve
 
 as_bob {
-    # XXX TODO: this should actually fail right now.
-    # in perl code, we're going to run the merge (just as prophet-merge does)
-    
-    use_ok('Prophet::Sync::Source::SVN'   );
-    
+    use_ok('Prophet::Sync::Source::SVN');
+
     my $source = Prophet::Sync::Source->new( { url => repo_uri_for('alice') } );
-    my $target = Prophet::Sync::Source->new( { url => repo_uri_for('bob')} );
+    my $target = Prophet::Sync::Source->new( { url => repo_uri_for('bob') } );
 
     my $conflict_obj;
 
     throws_ok {
         $target->import_changesets(
-            from              => $source,
+            from => $source,
         );
     } qr/not resolved/;
 
     throws_ok {
         $target->import_changesets(
-            from     => $source,
+            from => $source,
             resolver => sub { die "my way of death\n" },
         );
     } qr/my way of death/, 'our resolver is actually called';
 
-# always ours
-use Data::Dumper;
+    ok_added_revisions( sub {
+
+            $target->import_changesets(
+                from     => $source,
+                resolver => $target->always_mine_resolver )
+    }, 3, '3 revisions since the merge' );
+
+    my @changesets = fetch_newest_changesets(3);
+
+    my $resolution = $changesets[2];
+    ok( $resolution->is_resolution, 'marked as resolution' );
+    my $repo = repo_uri_for('bob');
+
+    #    diag `svn log -v $repo`;
+
+};
+
+as_alice {
+    my $source = Prophet::Sync::Source->new( { url => repo_uri_for('bob') } );
+    my $target = Prophet::Sync::Source->new( { url => repo_uri_for('alice') } );
+
+    my @res = $target->fetch_resolutions( from => $source );
+
+    # asssuming reoslutions nodes are added
+    my @resolutions = grep { $_->node_type eq '_prophet_resolution' } map { $_->changes } @res;
+
+    # fake records
+    my @res_records = map { { conflict => $_->node_uuid,
+            resolutions => { map { $_->name => $_->new_value } $_->prop_changes } } }
+        @resolutions;
+
     $target->import_changesets(
-            from     => $source,
-            resolver => sub { my $conflict = shift;
-                            warn Dumper($conflict);
-                                return 0 if $conflict->file_op_conflict;
-
-    my $resolution = Prophet::Change->new( { is_resolution => 1, 
-                                             change_type => $conflict->change_type,
-                                             node_type => $conflict->node_type,
-                                             node_uuid => $conflict->node_uuid });
-
-
-    for my $prop_conflict ( @{$conflict->prop_conflicts} ) {
-        $resolution->add_prop_change(
-                name => $prop_conflict->name,
-                old  => $prop_conflict->source_old_value,
-                new  => $prop_conflict->target_value
-                );
-    }
-    return $resolution;
+        from => $source,
+        resolver => sub { my $conflict = shift;
+
+            # find the resolution for the matchign conflict
+            my ($res) = grep { $_->{conflict} eq $conflict->cas_key } @res_records or return;
 
-});
-my $repo = repo_uri_for('bob');
-diag `svn log -v $repo`;
+            my $resolution = Prophet::Change->new_from_conflict($conflict);
+            for my $prop_conflict ( @{ $conflict->prop_conflicts } ) {
+                $resolution->add_prop_change(
+                    name => $prop_conflict->name,
+                    old  => $prop_conflict->source_old_value,
+                    new  => $res->{resolutions}{ $prop_conflict->name },
+                );
+            }
+            return $resolution;
+        },
+    );
+
+    lives_and {
+        ok_added_revisions( sub {
+                $target->import_changesets(
+                    from => $source );
+        }, 0, 'no more changes to sync' );
 
+    };
 };
 
+as_bob {
+    my $source = Prophet::Sync::Source->new( { url => repo_uri_for('alice') } );
+    my $target = Prophet::Sync::Source->new( { url => repo_uri_for('bob') } );
+
+    lives_and {
+        ok_added_revisions( sub {
+                $target->import_changesets(
+                    from => $source );
+        }, 0, 'no more changes to sync' );
+
+    };
+
+};
 

Modified: SVN-PropDB/t/simple-conflicting-merge.t
==============================================================================
--- SVN-PropDB/t/simple-conflicting-merge.t	(original)
+++ SVN-PropDB/t/simple-conflicting-merge.t	Mon Mar 31 19:44:45 2008
@@ -3,13 +3,13 @@
 use warnings;
 use strict;
 
-use Prophet::Test tests => 45;
+use Prophet::Test tests => 17;
+use Test::Exception;
 
 as_alice {
-    run_ok('prophet-node-create', [qw(--type Bug --status new --from alice )], "Created a record as alice"); 
-    run_output_matches('prophet-node-search', [qw(--type Bug --regex .)], [qr/new/], " Found our record");
-    };
-
+    run_ok( 'prophet-node-create', [qw(--type Bug --status new --from alice )], "Created a record as alice" );
+    run_output_matches( 'prophet-node-search', [qw(--type Bug --regex .)], [qr/new/], " Found our record" );
+};
 
 diag('Bob syncs from alice');
 
@@ -17,48 +17,55 @@
 
 as_bob {
 
-    run_ok('prophet-node-create', [qw(--type Dummy --ignore yes)], "Created a dummy record"); 
-  
-    run_ok('prophet-merge', ['--to', repo_uri_for('bob'), '--from', repo_uri_for('alice')], "Sync ran ok!");
+    run_ok( 'prophet-node-create', [qw(--type Dummy --ignore yes)], "Created a dummy record" );
+
+    run_ok( 'prophet-merge', [ '--to', repo_uri_for('bob'), '--from', repo_uri_for('alice') ], "Sync ran ok!" );
+
     # check our local replicas
-   my  ($ret, $out, $err) = run_script('prophet-node-search', [qw(--type Bug --regex .)]);
-    like($out, qr/new/, "We have the one node from alice") ;
-    if ($out =~ /^(.*?)\s./) {
+    my ( $ret, $out, $err ) = run_script( 'prophet-node-search', [qw(--type Bug --regex .)] );
+    like( $out, qr/new/, "We have the one node from alice" );
+    if ( $out =~ /^(.*?)\s./ ) {
         $record_id = $1;
     }
     diag($record_id);
 
-    run_ok('prophet-node-update', ['--type','Bug','--uuid',$record_id, '--status' => 'stalled']);
-    run_output_matches('prophet-node-show', ['--type', 'Bug', '--uuid', $record_id],
-                       ['id: '.$record_id, 'status: stalled', 'from: alice'],
-                       'content is correct');
+    run_ok( 'prophet-node-update', [ '--type', 'Bug', '--uuid', $record_id, '--status' => 'stalled' ] );
+    run_output_matches(
+        'prophet-node-show',
+        [ '--type',            'Bug',             '--uuid', $record_id ],
+        [ 'id: ' . $record_id, 'status: stalled', 'from: alice' ],
+        'content is correct'
+    );
 };
 
 as_alice {
-    run_ok('prophet-node-update', ['--type','Bug','--uuid',$record_id, '--status' => 'stalled']);
-    run_output_matches('prophet-node-show', ['--type', 'Bug', '--uuid', $record_id],
-                       ['id: '.$record_id, 'status: stalled', 'from: alice'],
-                       'content is correct');
+    run_ok( 'prophet-node-update', [ '--type', 'Bug', '--uuid', $record_id, '--status' => 'stalled'] );
+    run_output_matches(
+        'prophet-node-show',
+        [ '--type',            'Bug',             '--uuid', $record_id ],
+        [ 'id: ' . $record_id, 'status: stalled', 'from: alice' ],
+        'content is correct'
+    );
 
 };
 
 # This conflict, we can autoresolve
 
 as_bob {
+
     # XXX TODO: this should actually fail right now.
     # in perl code, we're going to run the merge (just as prophet-merge does)
-    
-    use_ok('Prophet::Sync::Source::SVN'   );
-    
+
+    use_ok('Prophet::Sync::Source::SVN');
+
     my $source = Prophet::Sync::Source->new( { url => repo_uri_for('alice') } );
-    my $target = Prophet::Sync::Source->new( { url => repo_uri_for('bob')} );
+    my $target = Prophet::Sync::Source->new( { url => repo_uri_for('bob') } );
 
     my $conflict_obj;
 
-my $repo = repo_uri_for('bob');
-diag `svn log -v $repo`;
+    my $repo = repo_uri_for('bob');
 
-    eval {
+    lives_ok {
         $target->import_changesets(
             from              => $source,
             conflict_callback => sub {
@@ -66,104 +73,115 @@
             }
         );
     };
-#    warn $@;
-    isa_ok($conflict_obj, 'Prophet::Conflict');
 
-    my @conflicting_changes = @{$conflict_obj->conflicting_changes};
-    is($#conflicting_changes, 0, "Only one conflicting change");
-    my $change = shift @conflicting_changes;
-    isa_ok($change, 'Prophet::ConflictingChange');
-    is($change->change_type, 'update_file');
-    my @prop_conflicts = @{$change->prop_conflicts};
-    is($#prop_conflicts, 0, "Found one prop conflict");
-    my $c = shift @prop_conflicts;
-    isa_ok($c, 'Prophet::ConflictingPropChange');
-    is($c->name, 'status');
-    is($c->source_old_value, 'new');
-    is($c->source_new_value,'stalled');
-    is($c->target_value,'stalled');
+    my $repo = repo_uri_for('bob');
+
+    my $repo = repo_uri_for('alice');
+
+    diag `svn log -v $repo`;
+
+    isa_ok( $conflict_obj, 'Prophet::Conflict' );
+
+    my $conflicts = serialize_conflict($conflict_obj);
+
+    is_deeply(
+        $conflicts,
+        {   meta    => { original_source_uuid => replica_uuid_for('alice') },
+            records => {
+                $record_id => {
+                    change_type => 'update_file',
+                    props       => {
+                        status => {
+                            source_new => 'stalled',
+                            source_old => 'new',
+                            target_old => 'stalled'
+                        }
+                    }
+                }
+            }
+        }
+    );
 
     # Check to see if the nullification changeset worked out ok
     my $nullification = $conflict_obj->nullification_changeset;
+    isa_ok( $nullification, "Prophet::ChangeSet" );
+
+    my $null_as_hash = serialize_changeset($nullification);
+
+    is_deeply(
+        $null_as_hash,
+        {
+
+            is_empty             => 0,
+            is_nullification     => 1,
+            is_resolution        => undef,
+            original_sequence_no => undef,
+            original_source_uuid => undef,
+            sequence_no          => undef,
+            source_uuid          => undef,
+            changes              => {
+                $record_id => {
+                    change_type  => 'update_file',
+                    node_type    => 'Bug',
+                    prop_changes => { status => { old_value => 'stalled', new_value => 'new' } }
+                    }
+
+            }
+        }
+    );
 
-    isa_ok($nullification, "Prophet::ChangeSet");
-    ok($nullification->is_nullification);
-    my @reverts = $nullification->changes;
-    is($#reverts, 0, "Found one change");
-    my $revert = shift @reverts;
-    is($revert->change_type, 'update_file');
-    my @prop_changes = $revert->prop_changes;
-    is ( $#prop_changes, 0, "Found one prop change");
-    is ($prop_changes[0]->name, 'status');
-    is($prop_changes[0]->old_value, 'stalled');
-    is($prop_changes[0]->new_value, 'new');
-    
-    
     # replay the last two changesets for bob's replica
-    my @changesets = @{$target->fetch_changesets( after => ($target->prophet_handle->repo_handle->fs->youngest_rev - 2))};
-    {
-       # is the second most recent change:
-       my $null_candidate  = shift @changesets;
-        # - a nullification changeset
-
-       ok($null_candidate->is_nullification, "It was marked as a nullification");
-       my @changes = $null_candidate->changes;
-        # - with one update-file
-        is ($#changes,0, "The nullification only changed one prop");
-        my $null_change = shift @changes;
-        my @prop_changes =  $null_change->prop_changes;
-        is($#prop_changes, 0, "one prop change");
-        my $prop_change = shift @prop_changes;
-        #  status: stalled->new
-        is($prop_change->name, 'status');
-        is($prop_change->old_value, 'stalled');
-        is($prop_change->new_value, 'new');
-    }
+    my @changesets = fetch_newest_changesets(2);
+
+    # is the second most recent change:
+    my $applied_null    = shift @changesets;
+    my $applied_as_hash = $applied_null->as_hash;
+
+    # these aren't available yet in the memory-version
+    $applied_as_hash->{$_} = undef for qw(sequence_no source_uuid original_source_uuid original_sequence_no);
+    is_deeply( $applied_as_hash, $null_as_hash );
+    warn YAML::Dump($applied_as_hash);
     
+    warn YAML::Dump($null_as_hash);
     
+
     # is the most recent change:
-    {
-        my $from_alice = shift @changesets;
-        my @changes = $from_alice->changes;
-        is ($#changes, 1, "Found 2 changes");
-        
-        
-            my ($data_change) = grep { $_->node_type eq 'Bug'} @changes;
-        is($data_change->change_type , 'update_file');
-        my @prop_changes = $data_change->prop_changes;
-        is($#prop_changes, 0, "only one prop changed");
-        
-        my $prop_change = shift @prop_changes;
-        is($prop_change->name, 'status');
-        is($prop_change->old_value, 'new');
-        is($prop_change->new_value, 'stalled');
+    my $from_alice = shift @changesets;
 
-     #   update-file
-    #      status new->stalled
-    
-    #  update-file
-    my($mergeticket_change) = grep { $_->node_type ne 'Bug'} @changes;
-           is($mergeticket_change->change_type , 'update_file');
-        is($mergeticket_change->node_uuid, replica_uuid_for('alice'));
-        my @mergeticket_prop_changes = $mergeticket_change->prop_changes;
-        is($#mergeticket_prop_changes, 0, "Only updated one merge ticket");
-        my $propchange = shift @mergeticket_prop_changes;
-        is ($propchange->name, 'last-changeset');
-        is($propchange->new_value, as_alice { replica_last_rev() } ); 
-       
-   }  
-    
-    
-    
-#diag `svn log -v $repo`;
+    my $from_alice_as_hash = $from_alice->as_hash;
 
+    $from_alice_as_hash->{$_} = undef for qw(sequence_no source_uuid);
+    warn YAML::Dump($from_alice_as_hash);use YAML;
+    is_deeply(
+        $from_alice_as_hash,
+        {   is_empty             => 0,
+            is_nullification     => undef,
+            is_resolution        => undef,
+            source_uuid          => undef,
+            sequence_no         => undef,
+            original_sequence_no => as_alice { replica_last_rev()   }, 
+                        original_source_uuid => replica_uuid_for('alice'),
+            changes              => {
+               $record_id   =>    {   node_type    => 'Bug',
+                    change_type  => 'update_file',
+                    prop_changes => { status => { old_value => 'new', new_value => 'stalled' } }
+                },
+
+                replica_uuid_for('alice') => {   change_type  => 'update_file',
+                    node_type    => '_merge_tickets',
+                    prop_changes => {
+                        'last-changeset' => {
+                            old_value => as_alice { replica_last_rev() - 1 },
+                            new_value => as_alice { replica_last_rev() }
+                        }
+                        }
 
-    # at the first sign of conflict, we're going to call back to a routine we inject to see if the conflict object is as we expect it
-    # Then we'll inject a hard-coded resolution into the conflict object
-    # then we'll let the code finish applying it.
-    # Then we'll check that bob's current state is right and that bob has a merge-ticket from alice
+                }
 
+                }
 
-};
+        }, "yay. the last rev from alice synced right"
+    );
 
+};
 



More information about the Bps-public-commit mailing list