[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