[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