[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