[Bps-public-commit] r11278 - in SVN-PropDB: bin lib/Prophet lib/Prophet/Sync lib/Prophet/Sync/Source t
jesse at bestpractical.com
jesse at bestpractical.com
Sun Mar 30 20:30:05 EDT 2008
Author: jesse
Date: Sun Mar 30 20:30:04 2008
New Revision: 11278
Modified:
SVN-PropDB/ (props changed)
SVN-PropDB/Makefile.PL
SVN-PropDB/bin/prophet-merge
SVN-PropDB/lib/Prophet/ChangeSet.pm
SVN-PropDB/lib/Prophet/Conflict.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/cli.t
Log:
r28809 at 70-5-79-37: jesse | 2008-03-30 14:29:55 -1000
* ping-pong sync now works
Modified: SVN-PropDB/Makefile.PL
==============================================================================
--- SVN-PropDB/Makefile.PL (original)
+++ SVN-PropDB/Makefile.PL Sun Mar 30 20:30:04 2008
@@ -3,6 +3,8 @@
use inc::Module::Install;
requires('Params::Validate');
requires('Class::Accessor');
+requires('IPC::Run3');
+requires('Test::Exception');
requires('Data::UUID');
requires('Path::Class');
requires('SVN::Core'); # SVN::Repos SVN::Fs SVN::Ra SVN::Delta::Editor SVN::Client SVN::Delta
Modified: SVN-PropDB/bin/prophet-merge
==============================================================================
--- SVN-PropDB/bin/prophet-merge (original)
+++ SVN-PropDB/bin/prophet-merge Sun Mar 30 20:30:04 2008
@@ -25,7 +25,7 @@
fatal_error( $target->url . " does not accept changesets. Perhaps it's unwritable or something" );
}
-import_changesets( from => $source, to => $target );
+$target->import_changesets( from => $source);
exit(0);
@@ -42,32 +42,3 @@
}
-
-sub import_changesets {
- my %args = validate( @_, { from => 1, to => 1 } );
- my $source = $args{'from'};
- my $target = $args{'to'};
-
- my $changesets_to_integrate
- = $source->fetch_changesets( after => $target->last_changeset_from_source( $source->uuid ) );
-
- for my $changeset (@$changesets_to_integrate) {
- use Data::Dumper; warn Dumper($changeset);
-
- next if ( $target->has_seen_changeset($changeset) );
- if ( $target->changeset_will_conflict($changeset) ) {
-
- my $conflicts = $target->conflicts_from_changeset($changeset);
-
- # write out a nullification changeset beforehand,
- # - that way, the source update will apply cleanly
- # Then write out the source changeset
- # Then write out a new changeset which reverts the parts of the source changeset which target should win
- } else {
- $target->integrate_changeset($changeset);
- }
-
- }
-}
-
-
Modified: SVN-PropDB/lib/Prophet/ChangeSet.pm
==============================================================================
--- SVN-PropDB/lib/Prophet/ChangeSet.pm (original)
+++ SVN-PropDB/lib/Prophet/ChangeSet.pm Sun Mar 30 20:30:04 2008
@@ -21,7 +21,7 @@
=cut
-__PACKAGE__->mk_accessors(qw/sequence_no source_uuid original_source_uuid original_sequence_no is_nullification is_resolution/);
+__PACKAGE__->mk_accessors(qw/sequence_no source_uuid original_source_uuid original_sequence_no is_nullification is_resolution/);
=head2 new
@@ -79,4 +79,15 @@
return @{ $self->{'changes'} || [] };
}
+=head2 is_empty
+
+Returns true if this changeset has no changes
+
+=cut
+
+sub is_empty {
+ my $self = shift;
+ return $self->changes ? 0 : 1;
+}
+
1;
Modified: SVN-PropDB/lib/Prophet/Conflict.pm
==============================================================================
--- SVN-PropDB/lib/Prophet/Conflict.pm (original)
+++ SVN-PropDB/lib/Prophet/Conflict.pm Sun Mar 30 20:30:04 2008
@@ -71,7 +71,7 @@
# for everything from the changeset that is the same as the old value of the target replica
# we can skip applying
- Carp::cluck "have not implemented automatic conflict resolution yet";
+ warn "have not implemented automatic conflict resolution yet";
$self->autoresolved(1);
Modified: SVN-PropDB/lib/Prophet/Handle.pm
==============================================================================
--- SVN-PropDB/lib/Prophet/Handle.pm (original)
+++ SVN-PropDB/lib/Prophet/Handle.pm Sun Mar 30 20:30:04 2008
@@ -254,6 +254,17 @@
}
+=head2 uuid
+
+Returns the uuid of the repilica
+
+=cut
+
+sub uuid {
+ my $self = shift;
+ return $self->repo_handle->fs->get_uuid;
+}
+
=head2 get_node_props {uuid => $uuid, type => $type, root => $root }
Returns a hashref of all properties for the record of type $type with uuid C<$uuid>.
@@ -273,7 +284,6 @@
my $self = shift;
my %args = validate( @_, { uuid => 1, type => 1, root => undef } );
my $root = $args{'root'} || $self->current_root;
- Carp::cluck unless $self->node_exists(%args);
return $root->node_proplist( $self->file_for( uuid => $args{'uuid'}, type => $args{'type'} ) );
}
Modified: SVN-PropDB/lib/Prophet/Sync/Source.pm
==============================================================================
--- SVN-PropDB/lib/Prophet/Sync/Source.pm (original)
+++ SVN-PropDB/lib/Prophet/Sync/Source.pm Sun Mar 30 20:30:04 2008
@@ -3,6 +3,8 @@
package Prophet::Sync::Source;
use base qw/Class::Accessor/;
+use Params::Validate;
+
=head1 NAME
@@ -45,4 +47,36 @@
}
+
+sub import_changesets {
+ my $self = shift;
+ my %args = validate( @_, { from => 1 } );
+ my $source = $args{'from'};
+
+ my $changesets_to_integrate
+ = $source->fetch_changesets( after => $self->last_changeset_from_source( $source->uuid ) );
+
+ for my $changeset (@$changesets_to_integrate) {
+# use Data::Dumper;warn Dumper($changeset) if ($DEBUG);
+
+ next if ( $self->has_seen_changeset($changeset) );
+ if ( $self->changeset_will_conflict($changeset) ) {
+
+ my $conflicts = $self->conflicts_from_changeset($changeset);
+
+ # write out a nullification changeset beforehand,
+ # - that way, the source update will apply cleanly
+ # Then write out the source changeset
+ # Then write out a new changeset which reverts the parts of the source changeset which target should win
+ } else {
+ $self->integrate_changeset($changeset);
+ }
+
+ }
+}
+
+
+
+
+
1;
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 Sun Mar 30 20:30:04 2008
@@ -60,7 +60,7 @@
sub fetch_changesets {
my $self = shift;
my %args = validate( @_, { after => 1});
-
+warn "===> grabbing changesets after $args{after}";
my @results;
my $last_editor;
@@ -93,8 +93,8 @@
my $changeset = Prophet::ChangeSet->new(
{ sequence_no => $entry->{'revision'},
source_uuid => $self->uuid,
- original_source_uuid => $revprops->{original_source_uuid},
- original_sequence_no => $revprops->{original_sequence_no},
+ original_source_uuid => $revprops->{'prophet:original-source'},
+ original_sequence_no => $revprops->{'prophet:original-sequence-no'},
});
@@ -155,8 +155,11 @@
my $self = shift;
my ($changeset) = validate_pos( @_, { isa => "Prophet::ChangeSet" } );
+ # If the changeset originated locally, we never want it
+ return 1 if $changeset->original_source_uuid eq $self->uuid;
+ # Otherwise, if the we have a merge ticket from the source, we don't want the changeset
my $last = $self->last_changeset_from_source( $changeset->original_source_uuid || $changeset->source_uuid );
-
+
# if the source's sequence # is >= the changeset's sequence #, we can safely skip it
return 1 if ( $last >= $changeset->sequence_no );
@@ -210,13 +213,31 @@
If there are no conflicts, just apply the change.
-
=cut
sub integrate_changeset {
my $self = shift;
my ($changeset) = validate_pos(@_, { isa => 'Prophet::ChangeSet'});
+=begin comment
+
+ # when we start to integrate a changeset, we need to do a bit of housekeeping
+ # We never want to merge in:
+ # merge tickets that describe merges from the local node
+
+
+ # When we integrate changes, sometimes we will get handed changes we already know about.
+ # - changes from local
+ # - changes from some other party we've merged from
+ # - merge tickets for the same
+ # we'll want to skip or remove those changesets
+
+
+=cut
+ return if $changeset->original_source_uuid eq $self->prophet_handle->uuid;
+ $self->remove_redundant_data($changeset); #Things we have already seen
+ return if ($changeset->is_empty or $changeset->is_nullification);
+
if (my $conflict = $self->conflicts_from_changeset($changeset ) ) {
#figure out our conflict resolution
# generate a nullification change
@@ -232,6 +253,15 @@
}
}
+sub remove_redundant_data {
+ my ($self, $changeset) = @_;
+ # XXX: encapsulation
+ $changeset->{changes} = [ grep {
+ !($_->node_type eq $Prophet::Handle::MERGETICKET_METATYPE &&
+ $_->node_uuid eq $self->prophet_handle->uuid)
+ } $changeset->changes ];
+}
+
# XXX TODO this is hacky as hell and violates abstraction barriers in the name of doing things over the RA
Modified: SVN-PropDB/lib/Prophet/Test.pm
==============================================================================
--- SVN-PropDB/lib/Prophet/Test.pm (original)
+++ SVN-PropDB/lib/Prophet/Test.pm Sun Mar 30 20:30:04 2008
@@ -2,7 +2,7 @@
use warnings;
package Prophet::Test;
use base qw/Test::More Exporter/;
-our @EXPORT = qw/as_alice as_bob as_charlie as_david run_ok run_script run_output_matches/;
+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/;
use File::Path 'rmtree';
use File::Temp qw/tempdir/;
@@ -10,6 +10,8 @@
use Test::Exception;
use IPC::Run3 'run3';
+use Prophet::CLI;
+
our $REPO_BASE = File::Temp::tempdir();
Test::More->import;
@@ -43,8 +45,7 @@
my @cmd = ($^X, (map { "-I$_" } @INC), 'bin/'.$script);
my $ret = run3 [@cmd, @$args], undef, \$stdout, \$stderr;
-
- Carp::croak $! if $?;
+ Carp::croak $stderr if $?;
return($ret, $stdout, $stderr);
}
@@ -59,13 +60,15 @@
my $args = shift if (ref $_[0] eq 'ARRAY');
my $msg = shift if (@_);
- lives_and {
+ @_ = sub {
my ($ret, $stdout,$stderr) = (run_script($script, $args), $msg);
@_ = ($ret);
+ diag($stdout);
diag($stderr);
goto &Test::More::ok;
};
+ goto \&lives_and;
}
sub _mk_cmp_closure {
@@ -156,21 +159,62 @@
return 'file://'.$path;
}
+
+sub replica_uuid {
+ my $self = shift;
+ my $cli = Prophet::CLI->new();
+ return $cli->handle->uuid;
+}
+
+=head2 replica_merge_tickets
+
+Returns a hash of key-value pairs of the form
+
+ { uuid => revno,
+ uuid => revno,
+}
+
+=cut
+
+sub replica_merge_tickets {
+ my $self = shift;
+ my $cli = Prophet::CLI->new();
+ my $tickets = Prophet::Collection->new(handle => $cli->handle, type => $Prophet::Handle::MERGETICKET_METATYPE);
+ $tickets->matching(sub { 1 });
+ return { map { $_->uuid => $_->prop('last-changeset') } @{$tickets->as_array_ref} };
+
+}
+
+sub replica_last_rev {
+ my $cli = Prophet::CLI->new();
+ return $cli->handle->repo_handle->fs->youngest_rev;
+}
+
+
=head2 as_user USERNAME CODEREF
Run this code block as USERNAME. This routine sets up the %ENV hash so that when we go looking for a repository, we get the user's repo.
=cut
+our %REPLICA_UUIDS;
+
sub as_user {
my $username = shift;
my $coderef = shift;
local $ENV{'PROPHET_REPO'} = repo_path_for($username);
- $coderef->();
+ my (@ret)= $coderef->();
+ $REPLICA_UUIDS{$username} = replica_uuid();
+ return @ret;
}
+sub replica_uuid_for {
+ my $user = shift;
+ return $REPLICA_UUIDS{$user};
+
+}
=head2 as_alice CODE, as_bob CODE, as_charlie CODE, as_david CODE
Modified: SVN-PropDB/t/cli.t
==============================================================================
--- SVN-PropDB/t/cli.t (original)
+++ SVN-PropDB/t/cli.t Sun Mar 30 20:30:04 2008
@@ -5,15 +5,12 @@
use Prophet::Test tests => 19;
-use_ok('Prophet::CLI');
-
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");
# update the node
# show the node history
# show the node
-
};
@@ -23,11 +20,13 @@
# update the node
# show the node history
# show the node
+
};
as_alice {
# sync from bob
- run_ok('prophet-merge', ['--from', Prophet::Test::repo_uri_for('bob'), '--to', Prophet::Test::repo_uri_for('alice')], "Sync ran ok!");
+ diag('Alice syncs from bob');
+ run_ok('prophet-merge', ['--from', repo_uri_for('bob'), '--to', 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/open/) ;
@@ -35,15 +34,12 @@
my @out = split(/\n/,$out);
is (scalar @out, 2, "We found only two rows of output");
- my $cli = Prophet::CLI->new();
- isa_ok($cli->handle, 'Prophet::Handle');
-
- my $last_rev = $cli->handle->repo_handle->fs->youngest_rev;
-
- diag("Rerun the exact same sync operation. we should still only end up with two records and NO new transactions");
+ my $last_rev = replica_last_rev();
+ diag('Alice syncs from bob again. There will be no new changes from bob');
+
# sync from bob
- run_ok('prophet-merge', ['--from', Prophet::Test::repo_uri_for('bob'), '--to', Prophet::Test::repo_uri_for('alice')], "Sync ran ok!");
+ run_ok('prophet-merge', ['--from', repo_uri_for('bob'), '--to', repo_uri_for('alice')], "Sync ran ok!");
# check our local replicas
($ret, $out, $err) = run_script('prophet-node-search', [qw(--type Bug --regex .)]);
like($out, qr/open/) ;
@@ -51,23 +47,28 @@
@out = split(/\n/,$out);
is (scalar @out, 2, "We found only two rows of output");
- is( $cli->handle->repo_handle->fs->youngest_rev, $last_rev, "We have not recorded another transaction");
+ is(replica_last_rev() , $last_rev, "We have not recorded another transaction");
+ is_deeply( replica_merge_tickets(), { replica_uuid_for('bob') => as_bob { replica_last_rev()} } );
};
-
+diag('Bob syncs from alice');
as_bob {
+ my $last_rev = replica_last_rev();
+
my ($ret, $out, $err) = run_script('prophet-node-search', [qw(--type Bug --regex .)]);
unlike($out, qr/new/, "bob doesn't have alice's yet") ;
# sync from bob
- run_ok('prophet-merge', ['--to', Prophet::Test::repo_uri_for('bob'), '--from', Prophet::Test::repo_uri_for('alice')], "Sync ran ok!");
+ run_ok('prophet-merge', ['--to', repo_uri_for('bob'), '--from', repo_uri_for('alice')], "Sync ran ok!");
# check our local replicas
($ret, $out, $err) = run_script('prophet-node-search', [qw(--type Bug --regex .)]);
like($out, qr/open/) ;
like($out, qr/new/) ;
+ system("svn log -v ".repo_uri_for("bob"));
+ is( replica_last_rev, $last_rev + 1, "only one rev from alice is sycned" );
};
More information about the Bps-public-commit
mailing list