[Bps-public-commit] r12497 - in Prophet/branches/moose: . doc lib/Prophet/Test t

sartak at bestpractical.com sartak at bestpractical.com
Sat May 17 09:20:02 EDT 2008


Author: sartak
Date: Sat May 17 09:20:01 2008
New Revision: 12497

Added:
   Prophet/branches/moose/doc/luid
   Prophet/branches/moose/t/luid.t
Modified:
   Prophet/branches/moose/   (props changed)
   Prophet/branches/moose/MANIFEST
   Prophet/branches/moose/lib/Prophet/CLI.pm
   Prophet/branches/moose/lib/Prophet/Record.pm
   Prophet/branches/moose/lib/Prophet/Replica.pm
   Prophet/branches/moose/lib/Prophet/Test.pm
   Prophet/branches/moose/lib/Prophet/Test/Participant.pm
   Prophet/branches/moose/t/edit.t
   Prophet/branches/moose/t/export.t
   Prophet/branches/moose/t/real-conflicting-merge.t
   Prophet/branches/moose/t/simple-conflicting-merge.t

Log:
- Merge //mirror/bps-public/Prophet/trunk to //mirror/bps-public/Prophet/branches/moose

Modified: Prophet/branches/moose/MANIFEST
==============================================================================
--- Prophet/branches/moose/MANIFEST	(original)
+++ Prophet/branches/moose/MANIFEST	Sat May 17 09:20:01 2008
@@ -3,6 +3,7 @@
 bin/taste_recipe
 doc/glossary
 doc/jesse_test_env_setup
+doc/luid
 doc/notes-on-merging
 doc/repository-layout
 doc/todo
@@ -64,6 +65,7 @@
 t/export.t
 t/generalized_sync_n_merge.t
 t/lib/TestApp/Bug.pm
+t/luid.t
 t/non-conflicting-merge.t
 t/real-conflicting-merge.t
 t/res-conflict-3.t

Added: Prophet/branches/moose/doc/luid
==============================================================================
--- (empty file)
+++ Prophet/branches/moose/doc/luid	Sat May 17 09:20:01 2008
@@ -0,0 +1,18 @@
+GUIDs are not great to work with. "B900A5B8-2322-11DD-A835-2B9E427B83F6" is a
+lot to demand of a user. And god forbid they have to actually type in that
+meaningless string. Substring matching doesn't help much because two GUIDs can
+easily differ by only a bit.
+
+Instead, we give users local IDs for each record. So instead of
+"B900A5B8-2322-11DD-A835-2B9E427B83F6" they might get "7". Local IDs are local
+to a replica - so two users can have different records with local ID "7".
+
+Because local IDs are integers, they're always distinguishable from global IDs.
+
+Local IDs are mildly fleeting. They're contained in a single file (directory?)
+which may be removed at any time. Every time a record is loaded, it's given
+a local ID which is cached so the user may use it.
+
+The local ID -> global ID mapping is contained in the $replica/local-id-cache
+file (directory?).
+

Modified: Prophet/branches/moose/lib/Prophet/CLI.pm
==============================================================================
--- Prophet/branches/moose/lib/Prophet/CLI.pm	(original)
+++ Prophet/branches/moose/lib/Prophet/CLI.pm	Sat May 17 09:20:01 2008
@@ -125,9 +125,24 @@
 sub set_type_and_uuid {
     my $self = shift;
 
+    if (my $id = delete $self->{args}->{id}) {
+        if ($id =~ /^(\d+)$/) { 
+        $self->{args}->{luid} = $id;
+        } else { 
+        $self->{args}->{uuid} = $id;
+
+        }
+
+    }
+
     if ( my $uuid = delete $self->{args}->{uuid} ) {
         $self->uuid($uuid);
     }
+    elsif ( my $luid = delete $self->{args}->{luid} ) {
+        my $uuid = $self->app_handle->handle->find_uuid_by_luid(luid => $luid);
+        die "I have no UUID mapped to the local id '$luid'\n" if !defined($uuid);
+        $self->uuid($uuid);
+    }
     if ( $self->{args}->{type} ) {
         $self->type( delete $self->{args}->{'type'} );
     } elsif($self->primary_commands->[-2]) {
@@ -309,7 +324,7 @@
         return;
     }
 
-    print "Created " . $record->record_type . " " . $record->uuid . "\n";
+    print "Created " . $record->record_type . " " . $record->luid . " (".$record->uuid.")"."\n";
 
 }
 
@@ -423,8 +438,7 @@
         print "Record not found\n";
         return;
     }
-
-    print "id: " . $record->uuid . "\n";
+    print "id: ".$record->luid." (" .$record->uuid.")\n";
     my $props = $record->get_props();
     for ( keys %$props ) {
         print $_. ": " . $props->{$_} . "\n";

Modified: Prophet/branches/moose/lib/Prophet/Record.pm
==============================================================================
--- Prophet/branches/moose/lib/Prophet/Record.pm	(original)
+++ Prophet/branches/moose/lib/Prophet/Record.pm	Sat May 17 09:20:01 2008
@@ -35,7 +35,12 @@
 );
 
 has uuid => (
-    is => 'rw',
+    is  => 'rw',
+    isa => 'Str',
+);
+
+has luid => (
+    is  => 'rw',
     isa => 'Str',
 );
 
@@ -101,19 +106,23 @@
     my %args = validate( @args, { by => 1 } );
     no strict 'refs';
 
-    Prophet::App->require_module($collection_class->record_class);
-    
-    
+    Prophet::App->require_module( $collection_class->record_class );
+
     *{ $class . "::$accessor" } = sub {
-        my $self = shift;
-        my $collection = $collection_class->new( handle => $self->handle, type => $collection_class->record_class->record_type );
-        $collection->matching( sub { $_[0]->prop( $args{by} ) eq $self->uuid } );
+        my $self       = shift;
+        my $collection = $collection_class->new(
+            handle => $self->handle,
+            type   => $collection_class->record_class->record_type
+        );
+        $collection->matching( sub { $_[0]->prop( $args{by} ) eq $self->uuid }
+        );
         return $collection;
     };
 
     # XXX: add validater for $args{by} in $model->record_class
 
-    $class->REFERENCES->{$accessor} = { %args, type => $collection_class->record_class };
+    $class->REFERENCES->{$accessor}
+        = { %args, type => $collection_class->record_class };
 }
 
 =head2 create { props => { %hash_of_kv_pairs } }
@@ -136,6 +145,7 @@
     $self->validate_props( $args{'props'} ) or return undef;
 
     $self->uuid($uuid);
+    $self->find_or_create_luid();
 
     $self->handle->create_record(
         props => $args{'props'},
@@ -146,18 +156,44 @@
     return $self->uuid;
 }
 
-=head2 load { uuid => $UUID }
+=head2 load { uuid => $UUID } or { luid => $UUID }
 
-Loads a Prophet record off disk by its uuid.
+Loads a Prophet record off disk by its uuid or luid.
 
 =cut
 
 sub load {
     my $self = shift;
-    my %args = validate( @_, { uuid => 1 } );
-    $self->uuid( $args{uuid} );
 
-    return $self->handle->record_exists( uuid => $self->uuid, type => $self->type );
+    my %args = validate(
+        @_,
+        {   uuid => {
+                optional  => 1,
+                callbacks => {
+                    'uuid or luid present' => sub { $_[0] || $_[1]->{luid} },
+                },
+            },
+            luid => {
+                optional  => 1,
+                callbacks => {
+                    'luid or uuid present' => sub { $_[0] || $_[1]->{uuid} },
+                },
+            },
+        }
+    );
+
+    if ( $args{luid} ) {
+        $self->luid( $args{luid} );
+        $self->uuid( $self->handle->find_uuid_by_luid( luid => $args{luid} ) );
+    } else {
+        $self->uuid( $args{uuid} );
+        $self->find_or_create_luid();
+    }
+
+    return $self->handle->record_exists(
+        uuid => $self->uuid,
+        type => $self->type
+    );
 }
 
 =head2 set_prop { name => $name, value => $value }
@@ -193,7 +229,11 @@
 
     $self->canonicalize_props( $args{'props'} );
     $self->validate_props( $args{'props'} ) || return undef;
-    $self->handle->set_record_props( type => $self->type, uuid => $self->uuid, props => $args{'props'} );
+    $self->handle->set_record_props(
+        type  => $self->type,
+        uuid  => $self->uuid,
+        props => $args{'props'}
+    );
     return 1;
 }
 
@@ -205,7 +245,10 @@
 
 sub get_props {
     my $self = shift;
-    return $self->handle->get_record_props( uuid => $self->uuid, type => $self->type );
+    return $self->handle->get_record_props(
+        uuid => $self->uuid,
+        type => $self->type
+    );
 }
 
 =head2 prop $name
@@ -232,7 +275,10 @@
 sub delete_prop {
     my $self = shift;
     my %args = validate( @_, { name => 1 } );
-    $self->handle->delete_record_prop( uuid => $self->uuid, name => $args{'name'} );
+    $self->handle->delete_record_prop(
+        uuid => $self->uuid,
+        name => $args{'name'}
+    );
 }
 
 =head2 delete
@@ -255,11 +301,12 @@
     for my $key ( uniq( keys %$props, $self->declared_props ) ) {
         return undef unless ( $self->_validate_prop_name($key) );
         if ( my $sub = $self->can( 'validate_prop_' . $key ) ) {
-            $sub->( $self, props => $props, errors => $errors ) || push @errors, "Validation error for '$key': ".($errors->{$key}||'');
+            $sub->( $self, props => $props, errors => $errors ) || push @errors,
+                "Validation error for '$key': " . ( $errors->{$key} || '' );
         }
     }
     if (@errors) {
-        die join('', at errors);   
+        die join( '', @errors );
     }
     return 1;
 }
@@ -290,11 +337,30 @@
 sub format_summary {
     my $self   = shift;
     my $format = $self->summary_format;
-    my $uuid   = $self->uuid;
-    $format =~ s/%u/$uuid/g;
+    if ( $format =~ /%u/ ) {
+        my $uuid = $self->uuid;
+        $format =~ s/%u/$uuid/g;
+    }
+    if ( $format =~ /%l/ ) {
+        my $luid = $self->luid;
+        $format =~ s/%l/$luid/g;
+    }
+    return sprintf( $format,
+        map { $self->prop($_) || "(no $_)" } $self->summary_props );
 
-    return sprintf( $format, map { $self->prop($_) || "(no $_)" } $self->summary_props );
+}
+
+=head2 find_or_create_luid
+
+Finds the luid for the records uuid, or creates a new one. Returns the luid.
 
+=cut
+
+sub find_or_create_luid {
+    my $self = shift;
+    my $luid = $self->handle->find_or_create_luid( uuid => $self->uuid );
+    $self->luid($luid);
+    return $luid;
 }
 
 __PACKAGE__->meta->make_immutable;

Modified: Prophet/branches/moose/lib/Prophet/Replica.pm
==============================================================================
--- Prophet/branches/moose/lib/Prophet/Replica.pm	(original)
+++ Prophet/branches/moose/lib/Prophet/Replica.pm	Sat May 17 09:20:01 2008
@@ -494,6 +494,52 @@
     $exporter->export();
 }
 
+=head2 metadata_directory
+
+=cut
+
+sub metadata_directory {
+    my $self = shift;
+    return $ENV{PROPHET_METADATA_DIRECTORY} if $ENV{PROPHET_METADATA_DIRECTORY};
+    return dir($ENV{HOME}, '.prophet-meta', $self->uuid);
+}
+
+=head2 read_metadata_file
+
+Returns the contents of the given file in this replica's metadata directory.
+Returns C<undef> if the file does not exist.
+
+=cut
+
+sub read_metadata_file {
+    my $self = shift;
+    my %args = validate( @_, { path => 1 } );
+    my $file = file($self->metadata_directory, $args{path});
+
+    return undef if !-f $file;
+    return scalar $file->slurp;
+}
+
+=head2 write_metadata_file
+
+Writes the given string to the given file in this replica's metadata directory.
+
+=cut
+
+sub write_metadata_file {
+    my $self = shift;
+    my %args = validate( @_, { path => 1, content => 1 } );
+    my $file = file($self->metadata_directory, $args{path});
+
+    my $parent = $file->parent;
+    if (!-d $parent) {
+        $parent->mkpath || die "Failed to create directory " . $file->parent;
+    }
+
+    my $fh = $file->openw;
+    print $fh $args{content};
+    close $fh || die $!;
+}
 
 =head1 methods to be implemented by a replica backend
 
@@ -518,6 +564,79 @@
 
 sub latest_sequence_no {return undef }
 
+=head2 find_or_create_luid { uuid => UUID }
+
+Finds or creates a LUID for the given UUID.
+
+=cut
+
+sub find_or_create_luid {
+    my $self = shift;
+    my %args = validate( @_, { uuid => 1 } );
+
+    my $mapping = $self->_read_guid2luid_mappings;
+
+    if (!exists($mapping->{ $args{'uuid'} })) {
+        $mapping->{ $args{'uuid'} } = $self->_create_luid($mapping);
+        $self->_write_guid2luid_mappings($mapping);
+    }
+
+    return $mapping->{ $args{'uuid'} };
+}
+
+=head2 find_uuid_by_luid { luid => LUID }
+
+Finds the UUID for the given LUID. Returns C<undef> if the LUID is not known.
+
+=cut
+
+sub find_uuid_by_luid {
+    my $self = shift;
+    my %args = validate( @_, { luid => 1 } );
+
+    my $mapping = $self->_read_luid2guid_mappings;
+    return $mapping->{ $args{'luid'} };
+}
+
+sub _create_luid {
+    my $self = shift;
+    my $map  = shift;
+
+    return ++$map->{'_meta'}{'maximum_luid'};
+}
+
+sub _guid2luid_file { "local-id-cache" }
+
+sub _read_guid2luid_mappings {
+    my $self = shift;
+    my $json = $self->read_metadata_file(path => $self->_guid2luid_file)
+            || '{}';
+
+    require JSON;
+    return JSON::from_json($json, { utf8 => 1 });
+}
+
+sub _write_guid2luid_mappings {
+    my $self = shift;
+    my $map  = shift;
+
+    require JSON;
+    my $content = JSON::to_json($map, { canonical => 1, pretty => 0, utf8 => 1 });
+
+    $self->write_metadata_file(
+        path    => $self->_guid2luid_file,
+        content => $content,
+    );
+}
+
+sub _read_luid2guid_mappings {
+    my $self = shift;
+    my $guid2luid = $self->_read_guid2luid_mappings(@_);
+    delete $guid2luid->{'_meta'};
+    my %luid2guid = reverse %$guid2luid;
+    return \%luid2guid;
+}
+
 =head2 traverse_changesets { after => SEQUENCE_NO, callback => sub {} }
 
 Walk through each changeset in the replica after SEQUENCE_NO, calling the C<callback> for each one in turn.

Modified: Prophet/branches/moose/lib/Prophet/Test.pm
==============================================================================
--- Prophet/branches/moose/lib/Prophet/Test.pm	(original)
+++ Prophet/branches/moose/lib/Prophet/Test.pm	Sat May 17 09:20:01 2008
@@ -3,7 +3,7 @@
 
 package Prophet::Test;
 use base qw/Test::More Exporter/;
-our @EXPORT = qw/as_alice as_bob as_charlie as_david as_user 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 replica_uuid
+our @EXPORT = qw/as_alice as_bob as_charlie as_david as_user run_ok repo_uri_for run_script run_output_matches run_output_matches_unordered replica_last_rev replica_merge_tickets replica_uuid_for fetch_newest_changesets ok_added_revisions replica_uuid
     serialize_conflict serialize_changeset in_gladiator diag is_script_output run_command set_editor load_record
     /;
 
@@ -193,6 +193,14 @@
     };
 }
 
+sub run_output_matches_unordered {
+    my $cmd = shift;
+    my $args = shift;
+    my $output = shift;
+    my ($val, $out, $err)  = run_script( $cmd, $args);
+    is_deeply([sort split(/\n/,$out)], [sort @$output]);
+}
+
 =head2 repo_path_for $USERNAME
 
 Returns a path on disk for where $USERNAME's replica is stored

Modified: Prophet/branches/moose/lib/Prophet/Test/Participant.pm
==============================================================================
--- Prophet/branches/moose/lib/Prophet/Test/Participant.pm	(original)
+++ Prophet/branches/moose/lib/Prophet/Test/Participant.pm	Sat May 17 09:20:01 2008
@@ -84,8 +84,8 @@
     my ( $ret, $out, $err ) = call_func_ok( [ qw(create --type Scratch), @{ $args->{props} } ] );
 
     #    ok($ret, $self->name . " created a record");
-    if ( $out =~ /Created\s+(.*?)\s+(.*)$/i ) {
-        $args->{result} = $2;
+    if ( $out =~ /Created\s+(.*?)\s+(\d+)\s+\((.*)\)/i ) {
+        $args->{result} = $3;
     }
     $self->record_action( 'create_record', $args );
 }

Modified: Prophet/branches/moose/t/edit.t
==============================================================================
--- Prophet/branches/moose/t/edit.t	(original)
+++ Prophet/branches/moose/t/edit.t	Sat May 17 09:20:01 2008
@@ -7,8 +7,8 @@
 $ENV{'PROPHET_REPO'} = tempdir( CLEANUP => 0 ) . '/repo-' . $$;
 my $prophet = Prophet::CLI->new;
 
-my $uuid;
-my $created_re = qr/Created Robot Master (\S+)(?{ $uuid = $1 })/;
+my ($luid,  $uuid);
+my $created_re = qr/Created Robot Master (\d+)(?{ $luid = $1}) \((\S+)(?{ $uuid = $2 })\)/;
 my $updated_re = qr/Robot Master (\S+)(?{ $uuid = $1 }) updated/;
 my $invoked_editor = 0;
 

Modified: Prophet/branches/moose/t/export.t
==============================================================================
--- Prophet/branches/moose/t/export.t	(original)
+++ Prophet/branches/moose/t/export.t	Sat May 17 09:20:01 2008
@@ -38,7 +38,12 @@
     run_output_matches(
         'prophet',
         ['show', '--type',            'Bug',             '--uuid', $record_id ],
-        [ 'id: ' . $record_id, 'status: stalled', 'from: alice' ],
+        [
+       
+        qr/id: (\d+) \($record_id\)/,
+        
+        
+        'status: stalled', 'from: alice' ],
         'content is correct'
     );
 

Added: Prophet/branches/moose/t/luid.t
==============================================================================
--- (empty file)
+++ Prophet/branches/moose/t/luid.t	Sat May 17 09:20:01 2008
@@ -0,0 +1,38 @@
+use warnings;
+use strict;
+use Test::More tests => 10;
+
+use File::Temp qw'tempdir';
+
+use_ok('Prophet::CLI');
+$ENV{'PROPHET_REPO'} = tempdir( CLEANUP => 0 ) . '/repo-' . $$;
+$ENV{'PROPHET_METADATA_DIRECTORY'} = tempdir( CLEANUP => 0 ) . '/repo-' . $$;
+
+my $cli = Prophet::CLI->new();
+my $cxn = $cli->app_handle->handle;
+
+my $record = Prophet::Record->new(handle => $cxn, type => 'Empty');
+my $uuid = $record->create(props => {});
+my $luid = $record->luid;
+ok($uuid, "got a uuid");
+ok($luid, "got a luid");
+
+$record = Prophet::Record->new(handle => $cxn, type => 'Empty');
+$record->load(uuid => $uuid);
+is($record->uuid, $uuid, "load accepts a uuid");
+
+$record = Prophet::Record->new(handle => $cxn, type => 'Empty');
+$record->load(luid => $luid);
+is($record->uuid, $uuid, "load accepts an luid");
+is($record->luid, $luid, "same luid after load");
+
+my $record2 = Prophet::Record->new(handle => $cxn, type => 'Empty');
+my $uuid2 = $record2->create(props => {});
+my $luid2 = $record2->luid;
+isnt($uuid, $uuid2, "different uuids");
+isnt($luid, $luid2, "different luids");
+
+$record2 = Prophet::Record->new(handle => $cxn, type => 'Empty');
+$record2->load(luid => $luid2);
+is($record2->uuid, $uuid2, "load accepts an luid");
+is($record2->luid, $luid2, "same luid after load");

Modified: Prophet/branches/moose/t/real-conflicting-merge.t
==============================================================================
--- Prophet/branches/moose/t/real-conflicting-merge.t	(original)
+++ Prophet/branches/moose/t/real-conflicting-merge.t	Sat May 17 09:20:01 2008
@@ -32,7 +32,9 @@
     run_output_matches(
         'prophet',
         [ 'show', '--type',            'Bug',             '--uuid', $record_id ],
-        [ 'id: ' . $record_id, 'status: stalled', 'from: alice' ],
+        [
+        qr/id: (\d+) \($record_id\)/,
+        'status: stalled', 'from: alice' ],
         'content is correct'
     );
 };
@@ -42,7 +44,9 @@
     run_output_matches(
         'prophet',
         [ 'show', '--type',            'Bug',          '--uuid', $record_id ],
-        [ 'id: ' . $record_id, 'status: open', 'from: alice' ],
+        [ 
+        qr/id: (\d+) \($record_id\)/,
+        'status: open', 'from: alice' ],
         'content is correct'
     );
 

Modified: Prophet/branches/moose/t/simple-conflicting-merge.t
==============================================================================
--- Prophet/branches/moose/t/simple-conflicting-merge.t	(original)
+++ Prophet/branches/moose/t/simple-conflicting-merge.t	Sat May 17 09:20:01 2008
@@ -32,7 +32,7 @@
     run_output_matches(
         'prophet',
         [ 'show','--type',            'Bug',             '--uuid', $record_id ],
-        [ 'id: ' . $record_id, 'status: stalled', 'from: alice' ],
+        [ qr/id: (\d+) \($record_id\)/, 'status: stalled', 'from: alice' ],
         'content is correct'
     );
 };
@@ -42,7 +42,7 @@
     run_output_matches(
         'prophet',
         ['show', '--type',            'Bug',             '--uuid', $record_id ],
-        [ 'id: ' . $record_id, 'status: stalled', 'from: alice' ],
+        [ qr/id: (\d+) \($record_id\)/, 'status: stalled', 'from: alice' ],
         'content is correct'
     );
 



More information about the Bps-public-commit mailing list