[Bps-public-commit] r11032 - in SVN-PropDB: bin doc lib/Prophet lib/Prophet/Sync/Source lib/Prophet/Sync/Source/SVN
jesse at bestpractical.com
jesse at bestpractical.com
Sun Mar 9 18:47:46 EDT 2008
Author: jesse
Date: Sun Mar 9 18:47:45 2008
New Revision: 11032
Modified:
SVN-PropDB/ (props changed)
SVN-PropDB/bin/merger
SVN-PropDB/doc/todo
SVN-PropDB/lib/Prophet/ChangeSet.pm
SVN-PropDB/lib/Prophet/Handle.pm
SVN-PropDB/lib/Prophet/Sync/Source/SVN.pm
SVN-PropDB/lib/Prophet/Sync/Source/SVN/ReplayEditor.pm
Log:
r28200 at 31b: jesse | 2008-03-09 18:46:27 -0400
Getting close to the point of actually having to write a conflict resolution algorithm
Modified: SVN-PropDB/bin/merger
==============================================================================
--- SVN-PropDB/bin/merger (original)
+++ SVN-PropDB/bin/merger Sun Mar 9 18:47:45 2008
@@ -53,12 +53,10 @@
for my $changeset (@$changesets_to_integrate) {
next if ( $target->has_seen_changeset($changeset));
if ( $target->changeset_will_conflict($changeset) ) {
+
+ my $conflicts = $target->conflicts_from_changeset($changeset);
+
- # make a decision about who wins the conflict
- # For parts of the conflict _source_ should win, write out a nullification changeset beforehand,
- # apply the changeset. it will now apply cleanly
-
- # For parts of the conflict _target_ should win,
# write out a nullification changeset beforehand,
# - that way, the source update will apply cleanly
# Then write out the source changeset
@@ -67,7 +65,6 @@
$target->integrate_changeset($changeset);
}
- $target->record_changeset_integration($changeset);
}
}
Modified: SVN-PropDB/doc/todo
==============================================================================
--- SVN-PropDB/doc/todo (original)
+++ SVN-PropDB/doc/todo Sun Mar 9 18:47:45 2008
@@ -1,12 +1,16 @@
+- when committing any change:
+ - record the original depot uuid and change sequence_no as revprops
+ - record two merge tickets:
+ - sequence_no from the source that gave it to us
+ - sequence_no from the original recording source
-- ability to record a merge ticket for a uuid
-- ability to read all merge tickets
+
+- implement merge of conflicts with: "local always wins"
+
-- ability to 'pull' non-conflicting updates from a remote db
- ability to 'pull' conflicting updates from a remote db
-- ability to 'push' updates to a remote db
- base bug tracking schemb
- naive support for large attachments
- elegant support for large attachments
Modified: SVN-PropDB/lib/Prophet/ChangeSet.pm
==============================================================================
--- SVN-PropDB/lib/Prophet/ChangeSet.pm (original)
+++ SVN-PropDB/lib/Prophet/ChangeSet.pm Sun Mar 9 18:47:45 2008
@@ -7,19 +7,18 @@
use base qw/Class::Accessor/;
-__PACKAGE__->mk_accessors(qw/sequence_no source_uuid/);
+__PACKAGE__->mk_accessors(qw/sequence_no source_uuid original_source_uuid original_sequence_no/);
sub add_change {
my $self = shift;
- my %args = validate(@_, { change => 1} );
- push @{$self->{changes}}, $args{change};
-
+ my %args = validate( @_, { change => 1 } );
+ push @{ $self->{changes} }, $args{change};
}
-sub changes {
- my $self = shift;
- return @{$self->{'changes'}||[]}
- }
+sub changes {
+ my $self = shift;
+ return @{ $self->{'changes'} || [] };
+}
1;
Modified: SVN-PropDB/lib/Prophet/Handle.pm
==============================================================================
--- SVN-PropDB/lib/Prophet/Handle.pm (original)
+++ SVN-PropDB/lib/Prophet/Handle.pm Sun Mar 9 18:47:45 2008
@@ -79,6 +79,75 @@
}
+
+sub integrate_changeset {
+ my $self = shift;
+ my $changeset = shift;
+
+ # open up a change handle locally
+
+ $self->begin_edit();
+
+ for my $change ( $changeset->changes ) {
+ $self->_integrate_change($change);
+ }
+
+
+ $self->_set_original_source_metadata($changeset);
+
+
+ # finalize the local change
+ $self->commit_edit();
+
+
+
+ # update the change's metadata with:
+ # original repo
+ # orignal sequence no
+}
+
+sub _set_original_source_metadata {
+ my $self = shift;
+ my $change = shift;
+
+ $self->current_edit->change_prop( 'prophet:original-source' => $change->original_source_uuid ||$change->source_uuid );
+ $self->current_edit->change_prop( 'prophet:original-sequence-no' => $change->original_sequence_no ||$change->sequence_no);
+
+
+}
+
+
+
+
+sub _integrate_change {
+ my $self = shift;
+ my $change = shift;
+
+ my %new_props = map { $_->name => $_->new_value } $change->prop_changes;
+
+ if ( $change->change_type eq 'add_file' ) {
+ $self->create_node(
+ type => $change->node_type,
+ uuid => $change->node_uuid,
+ props => \%new_props
+ );
+ } elsif ( $change->change_type eq 'add_dir' ) {
+ } elsif ( $change->change_type eq 'update_file' ) {
+ $self->set_node_props(
+ type => $change->node_type,
+ uuid => $change->node_uuid,
+ props => \%new_props
+ );
+ } elsif ( $change->change_type eq 'delete' ) {
+ $self->delete_node(
+ type => $change->node_type,
+ uuid => $change->node_uuid
+ );
+ }
+}
+
+
+
sub create_node {
my $self = shift;
my %args = validate( @_, { uuid => 1, props => 1, type => 1 } );
@@ -161,25 +230,42 @@
our $MERGETICKET_METATYPE = '_merge_tickets';
+
sub last_changeset_from_source {
my $self = shift;
my ($source) = validate_pos( @_, { isa => 'Prophet::Sync::Source' } );
-
my $props = eval {$self->get_node_props(uuid => $source->uuid, type => $MERGETICKET_METATYPE)};
-
return $props->{'last-changeset'};
}
sub record_changeset_integration {
my $self = shift;
- my ($changeset) = validate_pos(@_, { isa => 'Prophet::ChangeSet'});
+ my ($changeset) = validate_pos( @_, { isa => 'Prophet::ChangeSet' } );
+
+ # Record a merge ticket for the changeset's "direct" source
+ $self->_record_merge_ticket( $changeset->source_uuid, $changeset->sequence_no );
+
+ # Record a merge ticket for the changeset's "original" source
+ $self->_record_merge_ticket( $changeset->original_source_uuid, $changeset->original_sequence_no )
+ if ( $changeset->original_source_uuid && $changeset->original_source_uuid ne $changeset->source_uuid );
+
+}
+
+sub _record_merge_ticket {
+ my $self = shift;
+ my ($source_uuid, $sequence_no) = validate_pos(@_, 1,1);
- my $props = eval { $self->get_node_props(uuid => $changeset->source_uuid, type => $MERGETICKET_METATYPE)};
- unless ($props->{'last-changeset'}) {
- eval { $self->create_node( uuid => $changeset->source_uuid, type => $MERGETICKET_METATYPE, props => {} )};
+ my $props = eval { $self->get_node_props( uuid => $source_uuid, type => $MERGETICKET_METATYPE ) };
+ unless ( $props->{'last-changeset'} ) {
+ eval { $self->create_node( uuid => $source_uuid, type => $MERGETICKET_METATYPE, props => {} ) };
}
- $self->set_node_props(uuid => $changeset->source_uuid, type => $MERGETICKET_METATYPE, props => { 'last-changeset' => $changeset->sequence_no});
+
+ $self->set_node_props(
+ uuid => $source_uuid,
+ type => $MERGETICKET_METATYPE,
+ props => { 'last-changeset' => $sequence_no }
+ );
}
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 9 18:47:45 2008
@@ -25,20 +25,12 @@
sub setup {
my $self = shift;
- my ( $baton, $ref )
- = SVN::Core::auth_open_helper( SVK::Config->get_auth_providers );
+ my ( $baton, $ref ) = SVN::Core::auth_open_helper( SVK::Config->get_auth_providers );
my $config = SVK::Config->svnconfig;
- $self->ra(
- SVN::Ra->new( url => $self->url, config => $config, auth => $baton )
- );
+ $self->ra( SVN::Ra->new( url => $self->url, config => $config, auth => $baton ));
if ( $self->url =~ /^file:\/\/(.*)$/ ) {
- warn "Connecting to $1";
- $self->prophet_handle(
- Prophet::Handle->new(
- { repository => $1, db_root => '_prophet' }
- )
- );
+ $self->prophet_handle( Prophet::Handle->new( { repository => $1, db_root => '_prophet' }));
}
}
@@ -50,16 +42,11 @@
sub fetch_changesets {
my $self = shift;
-
-
-
-
my %args = validate( @_, { after => 1});
my @results;
my $last_editor;
-
my $handle_replayed_txn = sub {
$last_editor = Prophet::Sync::Source::SVN::ReplayEditor->new( _debug => 0 );
$last_editor->ra( $self->ra );
@@ -68,14 +55,11 @@
my $first_rev = $args{'after'} || 1;
- warn "The first rev is $first_rev";
-
for my $rev ( $first_rev .. $self->ra->get_latest_revnum ) {
# This horrible hack is here because I have no idea how to pass custom variables into the editor
$Prophet::Sync::Source::SVN::ReplayEditor::CURRENT_REMOTE_REVNO = $rev;
-
$self->ra->replay( $rev, 0, 1, $handle_replayed_txn->() );
- push @results, $self->_recode_changeset( $last_editor->dump_deltas);
+ push @results, $self->_recode_changeset( $last_editor->dump_deltas, $self->ra->rev_proplist($rev) );
}
return \@results;
@@ -85,12 +69,18 @@
sub _recode_changeset {
my $self = shift;
my $entry = shift;
+ my $revprops = shift;
my $changeset = Prophet::ChangeSet->new(
- { sequence_no => $entry->{'revision'},
- source_uuid => $self->uuid
+ { sequence_no => $entry->{'revision'},
+ source_uuid => $self->uuid,
+ original_source_uuid => $revprops->{original_source_uuid},
+ original_sequence_no => $revprops->{original_sequence_no},
+
}
);
+
+ # add each node's changes to the changeset
for my $path ( keys %{ $entry->{'paths'} } ) {
if ( $path =~ qr|^(.+)/(.*?)/(.*?)$| ) {
my ( $prefix, $type, $record ) = ( $1, $2, $3 );
@@ -101,7 +91,6 @@
}
);
for my $name ( keys %{ $entry->{'paths'}->{$path}->{prop_deltas} } ) {
- warn "Changing $name for $change";
$change->add_prop_change(
name => $name,
old => $entry->{paths}->{$path}->{prop_deltas}->{$name}->{'old'},
@@ -110,6 +99,7 @@
}
$changeset->add_change( change => $change );
+
} else {
warn "Discarding change to a non-record: $path";
}
@@ -117,54 +107,74 @@
}
return $changeset;
}
+
+
+
+=head2 accepts_changesets
+
+Returns true if this source is one we know how to write to (and have permission to write to)
+
+Returns false otherwise
+
+=cut
+
sub accepts_changesets {
my $self = shift;
+
return 1 if $self->prophet_handle;
return undef;
}
-sub has_seen_changeset {
+sub has_seen_changeset {
my $self = shift;
- my ($changeset) = validate_pos(@_, {isa => "Prophet::ChangeSet"});
+ my ($changeset) = validate_pos( @_, { isa => "Prophet::ChangeSet" } );
+
+ my $last_changeset_from_source
+ = $self->last_changeset_from_source( $changeset->original_source_uuid || $changeset->source_uuid );
- # find the last changeset for the source
- my $last_changeset_from_source = $self->last_changeset_from_source($changeset->source_uuid);
-
# if the source's sequence # is >= the changeset's sequence #, we can safely skip it
-
- warn "the liast changeset I saw from ".$changeset->source_uuid . " was $last_changeset_from_source";
- warn "my changeset is ".$changeset->sequence_no;
- if ($last_changeset_from_source >= $changeset->sequence_no) {
- return 1;
- }
+ return 1 if ( $last_changeset_from_source >= $changeset->sequence_no );
+}
+
-
-
+sub changeset_will_conflict {
+ my $self = shift;
+ my ($changeset) = validate_pos( @_, { isa => "Prophet::ChangeSet" } );
+
+ return 1 if ( keys %{$self->conflicts_from_changeset($changeset)});
+ return undef;
}
-sub changeset_will_conflict {
+sub conflicts_from_changeset{
my $self = shift;
- my ($changeset) = validate_pos(@_, {isa => "Prophet::ChangeSet"} );
+ my ($changeset) = validate_pos( @_, { isa => "Prophet::ChangeSet" } );
- warn "Checking ".$changeset->sequence_no."@".$changeset->source_uuid ." for conflicts";
+ my $conflict = Prophet::Conflict->new();
+
+ # XXX TODO
+ die "Right here, we need to actually walk through all the changes and generate
+
+ - a ConflictingChange if there are any conflicts in the change
+ - for each conflictingchange, we need to create a conflicting change property for each and every property that conflicts
- for my $change ($changeset->changes) {
+
+ for my $change ( $changeset->changes ) {
return 1 if $self->change_will_conflict($change);
}
return 0;
}
+
+
sub change_will_conflict {
my $self = shift;
my ($change) = validate_pos( @_, { isa => "Prophet::Change" } );
- $change->change_type;
-
my $current_state = $self->prophet_handle->get_node_props( uuid => $change->node_uuid, type => $change->node_type );
# It's ok to delete a node that exists
@@ -175,7 +185,12 @@
return 0 if ( $change->change_type eq 'add_dir' && !keys %$current_state );
for my $propchange ( $change->prop_changes ) {
+ # skip properties added by the change
next if ( !defined $current_state->{ $propchange->name } && !defined $propchange->old_value );
+
+ # If either the old version didn't have a value or the delta didn't have a value, then we
+ # know there's a conflict
+ # Ditto if they don't agree
return 1
if ( !exists $current_state->{ $propchange->name }
|| !defined $propchange->old_value
@@ -186,57 +201,21 @@
}
-
-
-
sub integrate_changeset {
- my $self = shift;
- my $changeset = shift;
-
- # open up a change handle locally
- warn "Applying Changeset " . $changeset->sequence_no;
-
- $self->prophet_handle->begin_edit();
- for my $change ( $changeset->changes ) {
- $self->_integrate_change($change);
- }
-
- $self->prophet_handle->commit_edit();
+ my $self = shift;
- # finalize the local change
-}
+ if (there's a conflict ) {
+ figure out our conflict resolution
+ generate a nullification change
+ # IMPORTANT: these should be an atomic unit. dying here would be poor.
+ # BUT WE WANT THEM AS THREEDIFFERENT SVN REVS
+ integrate the nullification change
+ integrate the original change
+ integrate the conflict resolution change
+ } else {
+ $self->prophet_handle->integrate_changeset(@_);
-sub _integrate_change {
- my $self = shift;
- my $change = shift;
- warn "\tApplying a change";
- warn "\t" . $change->change_type;
-
- my %new_props = map { $_->name => $_->new_value } $change->prop_changes;
-
- if ( $change->change_type eq 'add_file' ) {
- warn "\tAdded a file - " . $change->node_type, $change->node_uuid;
- $self->prophet_handle->create_node(
- type => $change->node_type,
- uuid => $change->node_uuid,
- props => \%new_props
- );
- } elsif ( $change->change_type eq 'add_dir' ) {
- warn "\tAdded a dir - " . $change->node_type, $change->node_uuid;
- } elsif ( $change->change_type eq 'update_file' ) {
- warn "\tUpdated a file - " . $change->node_type, $change->node_uuid;
- $self->prophet_handle->set_node_props(
- type => $change->node_type,
- uuid => $change->node_uuid,
- props => \%new_props
- );
- } elsif ( $change->change_type eq 'delete' ) {
- warn "\tDeleted file - " . $change->node_type, $change->node_uuid;
- $self->prophet_handle->delete_node(
- type => $change->node_type,
- uuid => $change->node_uuid
- );
}
}
@@ -251,24 +230,11 @@
# XXX HACK
my $filename = join( "/", "_prophet", $Prophet::Handle::MERGETICKET_METATYPE, $source );
- warn "Looking up $filename to find its last seen changeset";
- my ( $rev_fetched, $props ) = eval {
- $self->ra->get_file( $filename, $self->ra->get_latest_revnum, $stream, $pool );
- };
+ my ( $rev_fetched, $props ) = eval { $self->ra->get_file( $filename, $self->ra->get_latest_revnum, $stream, $pool ); };
- warn "Its last changeset as ".$props->{'last-changeset'};
return ( $props->{'last-changeset'} ||0 );
}
-sub record_changeset_integration {
- my $self = shift;
-
- return undef unless ( $self->accepts_changesets );
- my ($changeset) = validate_pos(@_, { isa => 'Prophet::ChangeSet'});
-
- $self->prophet_handle->record_changeset_integration($changeset);
-
-}
1;
Modified: SVN-PropDB/lib/Prophet/Sync/Source/SVN/ReplayEditor.pm
==============================================================================
--- SVN-PropDB/lib/Prophet/Sync/Source/SVN/ReplayEditor.pm (original)
+++ SVN-PropDB/lib/Prophet/Sync/Source/SVN/ReplayEditor.pm Sun Mar 9 18:47:45 2008
@@ -19,9 +19,7 @@
return $self->{'_ra'};
}
-sub apply {
- my $self = shift;
-};
+
sub open_root {
my $self = shift;
@@ -68,10 +66,10 @@
$self->{'paths'}->{$path}->{fs} = 'update_file';
$self->{'paths'}->{$path}->{prev_properties} = $prev_props;
+}
-}
sub close_file {
my $self = shift;
my ($file_baton, $text_checksum, $pool) = (@_);
@@ -83,16 +81,12 @@
sub absent_file {
my $self = shift;
my ($file_baton, $text_checksum, $pool) = (@_);
-
-
}
sub close_directory {
my $self = shift;
my ($dir_baton, $pool) = (@_);
pop @{$self->{dir_stack}};
-
-
}
sub absent_directory {
@@ -102,13 +96,14 @@
sub change_file_prop {
my $self = shift;
- my ($file_baton, $name, $value, $pool) = (@_);
+ my ( $file_baton, $name, $value, $pool ) = (@_);
- $self->{'paths'}->{$self->{'current_file'}}->{prop_deltas}->{$name} = { old =>
- $self->{'paths'}->{$self->{'current_file'}}->{'prev_properties'}->{$name}
- , new => $value };
-
+ $self->{'paths'}->{ $self->{'current_file'} }->{prop_deltas}->{$name} = {
+ old => $self->{'paths'}->{ $self->{'current_file'} }->{'prev_properties'}->{$name},
+ new => $value
+ };
}
+
sub change_dir_prop {
my $self = shift;
my ($dir_baton, $name, $value, $pool) = (@_);
@@ -119,13 +114,10 @@
}
-#sub set_target_revision { my $self = shift; my ($edit_baton, $target_revnum, $pool) = (@_); }
-
sub close_edit {
my $self = shift;
my ($edit_baton, $pool) = (@_);
- warn YAML::Dump($self->{'paths'}); use YAML;
}
More information about the Bps-public-commit
mailing list