Rev.pm #73

  • //
  • guest/
  • perforce_software/
  • revml/
  • lib/
  • VCP/
  • Rev.pm
  • View
  • Commits
  • Open Download .zip Download (17 KB)
package VCP::Rev;

=head1 NAME

VCP::Rev - VCP's concept of a revision

=head1 SYNOPSIS

   use VCP::Rev;

   use VCP::Rev qw( iso8601format );

   my $r = VCP::Rev->new;

=head1 DESCRIPTION

A data structure that represents a revision to a file (but, technically,
not a version of a file, though the two are often synonymous).

=head1 METHODS

=over

=cut

$VERSION = 1 ;

@EXPORT_OK = qw( iso8601format );
use Exporter ();
*import = \&Exporter::import;
*import = \&Exporter::import;

use strict ;

use Carp ;
use VCP::Logger qw( lg pr BUG );
use VCP::Debug ':debug' ;
use VCP::Utils 'empty' ;

my @fields;

BEGIN {
## VCP::Revs are blessed arrays that contain a series of unpacked fields
## (the references to shared strings above) and a packed string.  The
## accessors for the packed strings unpack as needed.  The packing is
## to save overhead for "payload" fields that are not used for sorting.
##
@fields = (
   ##
   ## RevML fields and their types.
   ##    s=string, the default
   ##    i=integer
   ##    _=build private accessors (prefixed with an "_") for a packed
   ##      field; allows public wrappers around packed fields.
   ##    @=it's an array (needed for serialization support)
   ##
   'ID:_',                 ## A unique identifier for the rev
   'NAME',                 ## The file name, relative to REV_ROOT
   'SOURCE_NAME',          ## immutable field, initialized to NAME
   'SOURCE_FILEBRANCH_ID', ## immutable field, initialized to
                           ## NAME or NAME<branch_number> for cvs
   'SOURCE_REPO_ID',       ## immutable field, initialized to
                           ## <repo_type>:<repo_server>
   'TYPE',                 ## Type.  Binary/text.
   'BRANCH_ID',            ## What branch this revision is on
   'SOURCE_BRANCH_ID',     ## immutable field initialized to BRANCH_ID
   'REV_ID',               ## The source rep's ID for this revision
   'SOURCE_REV_ID',        ## immutable field initialized to REV_ID
   'CHANGE_ID',            ## The unique ID for the change set, if any
   'SOURCE_CHANGE_ID',     ## immutable field initialized to CHANGE_ID
   'P4_INFO',              ## p4-specific info.
   'CVS_INFO',             ## cvs-specific info.
   'TIME:i',               ## The commit/submit time, in seconds-since-the-epoch
   'MOD_TIME:i',           ## The last modification time, if available
   'USER_ID',              ## The submitter/commiter of the revision
   'LABELS:_@',            ## A bit vector of tags/labels assoc. with this rev.
   'COMMENT',              ## The comment/message for this rev.
   'ACTION',               ## What was done ('edit', 'move', 'delete', etc.)
   'PREVIOUS_ID',          ## The id of the preceding version on this filebranch
   'FROM_ID',              ## The id of a revision to clone or merge
                           ## with the previous_id
   'SOURCE:_',             ## A reference to the source so that the destination
                           ## can get the file it needs right from the source.
                           ## NOTE: it's up to callers to thunk this for
                           ## serialization, not VCP::Rev.  Some will want
                           ## to undef() it, others will want to save
                           ## and restore it.
);

##
## Compile the fields' accessors
##

my %call_count;
END {
   lg "$_: $call_count{$_}\n"
      for sort {
         $call_count{$a} <=> $call_count{$b}
      } keys %call_count;

}

my @code;

for ( @fields ) {
   my $key = $_;
   my ( $n, $t ) = split /:/;
   my $is_private = $t ? $t =~ s/_//  : undef;
   my $fname         = lc $n;
   my $name          = ( $is_private ? "_" : "" ) . lc $n;
   my $set_name      = ( $is_private ? "_" : "" ) . "set_" . lc $n;

   push @code, <<ACCESSOR;
#line 1 VCP::Rev::$name()
sub $name {
   goto &$set_name if \@_ > 1;
   my \$self = shift;
\$call_count{$name}++;
   return \$self->{$fname};
}


#line 1 VCP::Rev::$set_name()
sub $set_name {
   my \$self = shift;
\$call_count{$set_name}++;
   \$self->{$fname} = shift;
   Carp::cluck "$set_name called in non-void context" if defined wantarray;
}
ACCESSOR

}


debug @code if debugging;

eval join "", @code, 1 or do {
    my $line = 1;
    ( my $msg = join "", @code ) =~ s/^/sprintf "%3d|", $line++/mge;
    die "$@:\n$msg";
};

}

=item new

Creates an instance, see subclasses for options.

   my $rev = VCP::Rev->new(
      name => 'foo',
      time => $commit_time,
      ...
   ) ;

=cut

sub new {
   my $class = ref $_[0] ? ref shift : shift;
   my $self = bless {@_}, $class;

   if ( $self->{labels} ) {
      $self->set_labels( @{delete $self->{labels}} );
   }
   else {
      $self->{labels} = [];
      $self->{seen_labels} = {};
   }

   return $self;
}

sub as_hash {
   my $self = shift;
   return { %$self };
}

=item fields

Returns a list of field names, with "@" prepended to any array fields.

=cut

sub fields {
   return map {
      my $name = lc $_;
      my $is_array = /\@/;
      $name =~ s/:.*//;
      $is_array ? "\@$name" : $name;
   } @fields;
}

=item serialize

Converts the revision metadata to a set of "name=value" strings
suitable for emitting to a flat file for later recovery.  Names are
included so that new revisions of VCP can rescuscitate revisions.

=cut

sub serialize {
   my $self = shift;

   return map {
      my $name = lc $_;
      my $is_array = /\@/;
      $name =~ s/:.*//;
      my $getter = $name eq "source" ? "_source" : $name;
      my @v = $self->$getter();
      @v && defined $v[0]
         ? $name . (
            $is_array
               ? "@" . join ",",
                  map {
                     my $v = $_;
                     $v =~ s/\\/\\\\/g;
                     $v =~ s/,/\\-/g;
                     $v;
                  } @v
               : "=" . $v[0]
            )
         : ();
   } sort @fields;
}


sub deserialize {
   my $class = shift;

   my $r = VCP::Rev->new;

   for ( @_ ) {
      my ( $name, $type, $value ) = /\A(\w+)([@=])(.*)\z/s
         or BUG "can't deserialize '$_'";
      my $setter = $name eq "source" ? "_set_source" : "set_$name";
      if ( $type eq "=" ) {
         BUG "unknown VCP::Rev field '$name'" unless $r->can( $setter );
         $r->$setter( $value );
      }
      else {
         my @values = map {
            s{\\\\}{\\}g;
            s{\\-}{,}g;
            $_;
         } split /,/, $value;
         $r->$setter( \@values );
      }
   }

   return $r;
}


sub split_name {
   shift;
   local $_ = $_[0];
   return ()     unless defined ;
   return ( "" ) unless length ;

   s{\A[\\/]+}{};
   s{[\\/]+\z}{};

   return split qr{[\\/]+};
}

sub cmp_name {
   my $self = shift;
   Carp::confess unless UNIVERSAL::isa( $self, __PACKAGE__ );

   my @a = ref $_[0] ? @{$_[0]} : $self->split_name( $_[0] );
   my @b = ref $_[1] ? @{$_[1]} : $self->split_name( $_[1] );

   my $r = 0;
   $r = shift( @a ) cmp shift( @b )
      while ! $r && @a && @b;

   $r || @a <=> @b;
}

=item split_id

   VCP::Rev->split_id( $id );

Splits an id in to chunks on punctuation and number/letter boundaries.

   Id           Result
   ==           ======
   1            ( 1 )
   1a           ( 1, "a" )
   1.2          ( 1, "", 2 )
   1a.2         ( 1, "a", 2 )

This oddness is to facilitate manually named revisions that use a
lettering scheme.  Note that the sort algorithms make an assumption that
"1.0a" is after "1.0".  This prevents kind of naming like "1.2pre1".

=cut

sub split_id {
   shift;
   for ( $_[0] ) {
      return ()     unless defined ;
      return ( "" ) unless length ;

      my @r = map /(\d*)(\D*)/, split /[^[:alnum:]]+/;
      pop @r while @r && ! length $r[-1];
      return @r;
   }
}

=item cmp_id

   VCP::Rev->cmp_id( $id1, $id2 );
   VCP::Rev->cmp_id( \@id1, \@id2 );  # for presplit ids

splits $id1 and $id2 if necessary and compares them using C<< <=> >> on
even numbered elements and C<cmp> on odd numbered elements.

=cut

sub cmp_id {
   my $self = shift;
   Carp::confess unless UNIVERSAL::isa( $self, __PACKAGE__ );

   my @a = ref $_[0] ? @{$_[0]} : $self->split_id( $_[0] );
   my @b = ref $_[1] ? @{$_[1]} : $self->split_id( $_[1] );

   my ( $A, $B, $r );
   while ( 1 ) {
      last unless @a && @b;
      ( $A, $B ) = ( shift @a, shift @b );
      confess "\$A='$A' not numeric" unless $A =~ /\A\d+\z/;
      confess "\$B='$B' not numeric" unless $B =~ /\A\d+\z/;
      $r = $A <=> $B;
      return $r if $r;

      last unless @a && @b;
      ( $A, $B ) = ( shift @a, shift @b );
      $r = $A cmp $B;
      return $r if $r;
   }

   return @a <=> @b;
}


=item is_base_rev

Returns TRUE if this is a base revision.  This is the case if no action
is defined.  A base revision is a revision that is being transferred
merely to check it's contents against the destination repository's
contents. Base revisions contain no action and contain a <digest> but no
<delta> or <content>.

When a VCP::Dest::* receives a base revision, the actual body of the
revision is 'backfilled' from the destination repository and checked
against the digest.  This cuts down on transfer size, since the full
body of the file never need be sent with incremental updates.

See L<VCP::Dest/backfill> as well.

=cut

sub is_base_rev {
   my $self = shift ;
   return ! defined $self->{action}
      || $self->{action} eq "digest";
}


=item is_real_rev

Returns true for base revisions and for "add" or "edit" revisions, all
of which must be able to be checked out from the source repository.

=cut

sub is_real_rev {
   my $self = shift;
   return $self->is_base_rev
      || $self->{action} eq "add"
      || $self->{action} eq "edit";
}


=item is_placeholder_rev

Returns TRUE if this is a branch or clone revision.  Placeholder revisions
are used to record branch points without causing edits and to perform
cloning operations.

Note that placeholders may have rev_id and change_id fields, but they
are likely to be fabricated as they often don't actually exist in the
source repository.  For instance, CVS does not record a revision number
for a branch operation unless the file has been alterde on the branch.

=cut

sub is_placeholder_rev {
   my $a = shift->{action};
   return $a eq "branch" || $a eq "clone";
}


=item is_branch_rev

Returns TRUE if this is a branch founding placeholder revision.
These revisions are used to record branch points for files without
modifying the files.

A branch revision has an action of "branch".

Note that branch placeholders may have rev_id and change_id fields, but
they may be malformed; they are present for sorting purposes only and
should be ignored by the destination repository.

Branch revisions may not be present for branches which have files on
them but should be in order to cause the destination to create the
branch before altering any files on it.

=cut

sub is_branch_rev {
   return shift->{action} eq "branch";
}


=item is_clone_rev

Returns TRUE if this is a cloning placeholder revision.  These revisions
are used to mirror files from one branch to another when a physical
filebranch maps to more than one logical branch.  This is not possible
in p4, but is possible in both CVS and VSS.  CVS generates these as of
this writing, VSS may by the time you read this.

=cut

sub is_clone_rev {
   return shift->{action} eq "clone";
}


=item base_revify

Converts a "normal" rev in to a base rev.

=cut

sub base_revify {
   my $self = shift ;

   $self->set_labels;
   $self->{action} = "digest";
   $self->{$_} = undef for qw(
      p4_info
      cvs_info
      time
      mod_time
      user_id
      comment
      previous_id
      from_id
   );
}

=item id

Sets/gets the id.  Returns "$name#$rev_id" by default, which should work
for most systems.

=cut

sub id {
   goto &_set_id if @_ > 1;
   my $self = shift;

   my $id = $self->_id;

   return $id if defined $id;
   my $n = $self->{source_name};
   my $r = $self->{source_rev_id};
   BUG "undefined name: ", $self->as_string unless defined $n;
   BUG "empty name: ", $self->as_string unless length $n;
   BUG "undefined source_rev_id: ", $self->as_string unless defined $r;
   BUG "empty source_rev_id: ", $self->as_string unless length $r;
   return "$n#$r";
}


sub set_id {
   goto &_set_id;
}

## We maintain a reference to the sources and pack the index.  This allows
## for recoverable serialization (as changesets.pm uses), but may hamper
## storage between instantiations (as VCP::Dest::metadb does).
my %sources;
sub source {
   my $self = shift;

   goto \&set_source if @_;

   return $sources{$self->{source} || ""}; ## $sources{""} == undef
}


sub set_source {
   my $self = shift;
   my ( $new_source ) = @_;

BUG "source must be an object" if defined $new_source && !ref $new_source;

   if ( defined $new_source ) {
      my $key = int $new_source;
      $sources{$key} ||= do {
          ## Make sure circrefs to filters are removed at END.  This
          ## makes using VCP::Rev->source() unstable in END{} blocks, but
          ## really, juggling a live grenade in the final seconds of
          ## your life is ok.
          require VCP::Plugin;
          VCP::Plugin->queue_END_sub( sub { delete $sources{$key} } );

          $new_source;
      };

      $self->{source} = $key;
   }
   else {
      $self->{source} = $new_source;
   }
}


=item get_source_file

Fetches the file from the source repository and returns a path to that file.

=cut

sub get_source_file {
    my $self = shift;
    die "source() not set for ", $self->as_string, "\n"
       unless $self->source;
    $self->source->get_source_file( $self );
}


=item labels

   $r->set_labels( \@labels ) ;  ## pass an array ref for speed
   @labels = $r->labels ;

Sets/gets labels associated with a revision.  If a label is applied multiple
times, it will only be returned once.  This feature means that the automatic
label generation code for r_... revision and ch_... change labels won't add
additional copies of labels that were already applied to this revision in the
source repository.

Returns labels in an unpredictible order, which happens to be sorted for
now.  This sorting is purely for logging purposes and may disappear at
any moment.

=item add_label

  $r->add_label( $label ) ;
  $r->add_label( @labels ) ;

Marks one or more labels as being associated with this revision of a file.

=cut

sub add_label {
   my $self = shift ;
   for ( @_ ) {
      push @{$self->{labels}}, $_
         unless $self->{seen_labels}->{$_}++;
   }
   return ;
}


sub labels {
   goto &set_labels if @_ > 1;
   my $self = shift;
   @{$self->{labels}};
}


sub set_labels {
   my $self = shift;
   @{$self->{labels}} = ();
   %{$self->{seen_labels}} = ();
   $self->add_label( map ref() ? @$_ : $_, @_ );
}


=item iso8601format

   VCP::Rev::iso8601format( $time );

Takes a seconds-since-the-epoch time value and converts it to
an ISO8601 formatted date.  Exportable:

   use VCP::Rev qw( iso8601format );

=cut

sub iso8601format {
   die "time parameter missing" unless @_;
   my @f = reverse( (gmtime shift)[0..5] ) ;
   $f[0] += 1900 ;
   $f[1] ++ ; ## Month of year needs to be 1..12
   return sprintf( "%04d-%02d-%02d %02d:%02d:%02dZ", @f ) ;
}


=item as_string

Prints out a string representation of the name, rev_id, change_id, type,
time, and a bit of the comment.  base revisions are flagged as such (and
don't have fields like time and comment).

=cut

sub as_string {
   my $self = shift ;

   my @v = map(
      defined $_ ? $_ : "<undef>",
      map(
         $_ eq 'time' && defined $self->$_()
             ? iso8601format $self->$_()
         : $_ eq 'comment' && defined $self->$_()
             ? do {
                my $c = $self->$_();
                $c =~ s/\\/\\\\/g;
                $c =~ s/\n/\\n/g;
                $c =~ s/\r/\\r/g;
                $c =~ s/\t/\\t/g;
                $c =~ s/\f/\\f/g;
                $c =~ s/([^\020-\177])/sprintf "\\%03o", ord $1/eg;
                $c = substr( $c, 0, 32 )
                   if length( $c ) > 32;
                $c;
             }
         : $_ eq 'action' && defined $self->$_()
             ? sprintf "%-6s", $self->$_() # 6 == length "delete"
             : $self->$_(),
         (
            qw( id action change_id branch_id type ),
            $self->is_base_rev
               ? ()
               : qw( time user_id comment ),
         )
      )
   ) ;

   return $self->is_base_rev
      ? sprintf( qq{%s %s @%s <%s> (%s)}, @v )
      : sprintf( qq{%s %s @%s <%s> (%s) %s %s "%s"}, @v );
}

=back

=head1 SUBCLASSING

This class uses the fields pragma, so you'll need to use base and 
possibly fields in any subclasses.

=head1 COPYRIGHT

Copyright 2000, Perforce Software, Inc.  All Rights Reserved.

This module and the VCP package are licensed according to the terms given in
the file LICENSE accompanying this distribution, a copy of which is included in
L<vcp>.

=head1 AUTHOR

Barrie Slaymaker <barries@slaysys.com>

=cut

1
# Change User Description Committed
#78 5404 Barrie Slaymaker - SVN support added
- Makefile gives clearer notices about missing optional
  prereqs.
- VCP::Filter::labelmap and VCP::Filter::map: <<skip>> replaces
  deprecated <<delete>> to be clearer that no revisions
  are deleted from either repository but some just are
  skipped and not inserted.
- VCP::Filter::map: support added for SVN-like branch labels
- VCP::Source: support added for ISO8601 timestamps
  emitted by SVN.
#77 5086 Barrie Slaymaker - Prevent infinite recursion in as_string so VCP_DEBUG=1 and
  partially constructed revs in test suite don't cause infinite
  recursion.
#76 5082 Barrie Slaymaker - VCP::Source tells VCP::Rev to uncache the source to allow
  the source instance to be DESTROYed and thus clean up its
  working files.
#75 4514 Barrie Slaymaker - VCP::Rev::earlier_ids and <release_id> added
#74 4511 Barrie Slaymaker - iso8691 formatted time returns undef when passed in an undef
#73 4507 Barrie Slaymaker - RevML:
    - added <action>, removed <delete>, <placeholder> and <move>
    - added <from_id> for clones (and eventually merge actions)
    - Simplified DTD (can't branch DTD based on which action
      any more)
- VCP::Source::cvs, VCP::Filter::changesets and VCP::Dest::p4
  support from_id in <action>clone</action> records
- VCP::Dest::perl_data added
- VCP::Rev::action() "branch" added, no more undefined action
  strings
- "placeholder" action removed
#72 4487 Barrie Slaymaker - dead code removal (thanks to clkao's coverage report)
#71 4077 Barrie Slaymaker - VCP on Win32 no longer whines about permission denied errors
  for some disk file cleanup tasks.
#70 4021 Barrie Slaymaker - Remove all phashes and all base & fields pragmas
- Work around SWASHGET error
#69 4012 Barrie Slaymaker - Remove dependance on pseudohashes (deprecated Perl feature)
#68 3970 Barrie Slaymaker - VCP::Source handles rev queing, uses disk to reduce RAM
- Lots of other fixes
#67 3942 Barrie Slaymaker - ChangeSets now passes tests
#66 3930 Barrie Slaymaker - VCP::Source::cvs and VCP::Dest::p4 handle cloning deletes
- "placeholder" actions and is_placeholder_rev() deprecated in
  favor of is_branch_rev() and is_clone_rev().
- Misc cleanups and minor bugfixes
#65 3850 Barrie Slaymaker - No longer stores all revs in memory
#64 3827 Barrie Slaymaker - VCP::Rev supports better debugging and csv-ification
#63 3817 Barrie Slaymaker - Minor reformatting & error identification
#62 3813 Barrie Slaymaker - VCP::Rev::previous() is no more
#61 3811 Barrie Slaymaker - fetch_*() and get_rev() renamed get_source_file()
#60 3804 Barrie Slaymaker - Refactored to prepare way for reducing memory footprint
#59 3775 Barrie Slaymaker - VCP::Rev now support serialization/deserialization
#58 3769 Barrie Slaymaker - avg_comment_time sort key removed
#57 3755 Barrie Slaymaker - VCP::Rev dies if an empty label is set
#56 3737 Barrie Slaymaker - Missing / empty data fields other than branch_id no longer affect
  changeset aggregation
    - Needed for VSS, which lacks time information on deletes
- If all revs have change_ids, they are now sorted by change_id
  and name (as opposed to change_id, rev_id)
- VCP::Rev::sort_time() removed
- VCP::Filter::changesets has better debugging
- TestUtils now dumps large diffs
#55 3704 Barrie Slaymaker - Whitespace tweaked
#54 3680 Barrie Slaymaker - Comments are more properly escaped in debugging messages
#53 3496 Barrie Slaymaker - VSS branching
#52 3475 Barrie Slaymaker - Add VCP::Dest::data_dump
#51 3441 Barrie Slaymaker - Add some debugging code
#50 3155 Barrie Slaymaker Convert to logging using VCP::Logger to reduce stdout/err spew.
       Simplify & speed up debugging quite a bit.
       Provide more verbose information in logs.
       Print to STDERR progress reports to keep users from wondering
       what's going on.
       Breaks test; halfway through upgrading run3() to an inline
       function for speed and for VCP specific features.
#49 3133 Barrie Slaymaker Make destinations call back to sources to check out files to
       simplify the architecture (is_metadata_only() no longer needed)
       and make it more optimizable (checkouts can be batched).
#48 3127 Barrie Slaymaker Minor cleanups.
#47 3117 Barrie Slaymaker Cut over to faster VCP::Rev::new, remove symbolic method
       calls.
#46 3116 Barrie Slaymaker Cleanup, test tweaks
#45 3115 Barrie Slaymaker Move sorting function to the new VCP::Filter::sort;
       it's for testing and reporting only and the code
       was bloating VCP::Dest and limiting VCP::Rev
       and VCP::Dest optimizations.  Breaks test suite in minor
       way.
#44 3112 Barrie Slaymaker Reduce memory footprint when handling large numbers
       of revisions.
#43 3110 Barrie Slaymaker Optimize label handling.
#42 3106 Barrie Slaymaker Remove an unused field (state) from VCP::Rev
       optimize and bugfix labelmap
#41 3096 Barrie Slaymaker Tuning
#40 3086 Barrie Slaymaker Optimize change aggregation from something like O(N^2)
       down to something more reasonable.  Noticable only
       on large transfers.
#39 3073 Barrie Slaymaker Improve debugging output
#38 3060 Barrie Slaymaker Note arglist too long error in p4->p4 conversion
#37 3049 Barrie Slaymaker Fix minor bug that caused lots of failing tests (undef branch_id
       handling).
#36 3047 Barrie Slaymaker Fold branch_ids, improve debug & error (as_string()) output.
#35 3038 Barrie Slaymaker Get proper identification of founding revisions implemented.
#34 3027 Barrie Slaymaker VCP::Filter::labelmap
#33 2972 Barrie Slaymaker Interim checkin
#32 2935 John Fetkovich added empty() calls
#31 2802 John Fetkovich Added a source_repo_id to each revision, and repo_id to each
Source and Dest.  The repo_ids include repository type
(cvs,p4,revml,vss,...) and the repo_server fields.  Changed the
$self->...->set() and $self->...->get() lines in VCP::Dest::* to
pass in a conglomerated key value, by passing in the key as an
ARRAY ref.  Also various restructuring in VCP::DB.pm,
VCP::DB_file.pm and VCP::DB_file::sdbm.pm related to this
change.
#30 2743 John Fetkovich Add fields to vcp:
         source_name,
         source_filebranch_id,
         source_branch_id,
         source_rev_id,
         source_change_id

        1. Alter revml.dtd to include the fields
        2. Alter bin/gentrevml to emit legal RevML
        3. Extend VCP::Rev to have the fields
        4. Extend VCP::{Source,Dest}::revml to read/write the fields
           (VCP::Dest::revml should die() if VCP tries to emit illegal
           RevML)
        5. Extend VCP::{Source,Dest}::{cvs,p4} to read the fields
        7. Get all tests through t/91*.t to pass
           except those that rely on ch_4 labels
#29 2725 Barrie Slaymaker Start using HeadRevs.pm.
#28 2372 John Fetkovich Remove time, sort_time methods, allow them to be autogenerated.
#27 2357 John Fetkovich removed debugging message left in BEGIN block.
#26 2356 John Fetkovich Changed BEGIN block so all accessor functions autogenerated in a single
       eval, rather than individually.
#25 2340 Barrie Slaymaker Update manifest, comment out some debugging stuff in VCP::Rev
#24 2245 Barrie Slaymaker cvs -r (re)implemented for direct reads, passes all cvs-only tests
#23 2241 Barrie Slaymaker RCS file scanning improvements, implement some of -r
#22 2240 Barrie Slaymaker Start on cvs -r option support.
#21 2233 Barrie Slaymaker debug
#20 2232 Barrie Slaymaker Major memory and sort speed enhancements.
#19 2154 Barrie Slaymaker Speed up sorting
#18 2042 Barrie Slaymaker Basic source::p4 branching support
#17 2026 Barrie Slaymaker VCP::8::cvs now supoprt branching
#16 2017 Barrie Slaymaker Interim checkin of id=/base_version_id for revml: and
       branch_diagram:
#15 2015 Barrie Slaymaker submit changes
#14 1998 Barrie Slaymaker Initial, revml and core VCP support for branches
#13 1855 Barrie Slaymaker Major VSS checkin.
 Works on Win32
#12 1822 Barrie Slaymaker Get all other tests passing but VSS.
 Add agvcommenttime
       sort field.
#11 1809 Barrie Slaymaker VCP::Patch should ignore lineends
#10 1756 Barrie Slaymaker Extend VCPNODELETE to revs' cleanup
#9 1358 Barrie Slaymaker Win32 changes
#8 1055 Barrie Slaymaker add sorting, revamp test suite, misc cleanup.
 Dest/revml is
not portable off my system yet (need to release ...::Diff)
#7 628 Barrie Slaymaker Cleaned up POD in bin/vcp, added BSD-style license.
#6 620 Barrie Slaymaker Underscorify CVS tags, only warn about undeleted files if
debugging.
#5 608 Barrie Slaymaker Lots of changes to get vcp to install better, now up to 0.066.
Many thanks to Matthew Attaway for testing & suggestions.
#4 480 Barrie Slaymaker 0.06 Wed Dec 20 23:19:15 EST 2000
   - bin/vcp: Added --versions, which loads all modules and checks them
     for a $VERSION and print the results out.  This should help with
     diagnosing out-of-sync modules.
   - Added $VERSION vars to a few modules :-).  Forgot to increment any
     $VERSION strings.
   - VCP::Dest::cvs: The directory "deeply" was not being `cvs add`ed on
     paths like "a/deeply/nested/file", assuming "deeply" had no files
     in it.
   - VCP::Dest::revml: fixed a bug that was causing files with a lot of
     linefeeds to be emitted in base64 instead of deltaed.  This means
     most text files.
   - Various minor cleanups of diagnostics and error messages, including
     exposing "Can't locate Foo.pm" when a VCP::Source or VCP::Dest
     module depends on a module that's not installed, as reported by
     Jeff Anton.
#3 478 Barrie Slaymaker 0.05 Mon Dec 18 07:27:53 EST 2000
   - Use `p4 labels //...@label` command as per Rober Cowham's suggestion, with
     the '-s' flag recommended by Christopher Siewald and
     Amaury.FORGEOTDARC@atsm.fr.  Though it's actually something like

       vcp: running /usr/bin/p4 -u safari -c safari -p localhost:5666 -s files
       //.../NtLkly //...@compiler_a3 //.../NtLkly //...@compiler_may3

     and so //on //for 50 parameters to get the speed up.  I use the
     //.../NtLkly "file" as //a separator between the lists of files in various
     //revisions.  Hope nobody has any files named that :-).  What I should do
     is choose a random label that doesn't occur in the labels list, I guess.
   - VCP::Source::revml and VCP::Dest::revml are now binary, control code, and
     "hibit ASCII" (I know, that's an oxymoron) clean.  The <comment>, <delta>,
     and <content> elements now escape anything other than tab, line feed,
     space, or printable chars (32 <= c <= ASCII 126) using a tag like '<char
     code="0x09">'.  The test suite tests all this.  Filenames should also
     be escaped this way, but I didn't get to that.
   - The decision whether to do deltas or encode the content in base64 is now
     based on how many characters would need to be escaped.
   - We now depend on the users' diff program to have a "-a" option to force it
     to diff even if the files look binary to it.  I need to use Diff.pm and
     adapt it for use on binary data.
   - VCP::Dest::cvs now makes sure that no two consecutive revisions of the
     same file have the same mod_time.  VCP::Source::p4 got so fast at pulling
     revisions from the repositories the test suite sets up that CVS was not
     noticing that files had changed.
   - VCP::Plugin now allows you to set a list of acceptable result codes, since
     we now use p4 in ways that make it return non-zero result codes.
   - VCP::Revs now croaks if you try to add two entries of the same VCP::Rev
     (ie matching filename and rev_id).
   - The <type> tag is now limited to "text" or "binary", and is meant to
     pass that level of info between foreign repositories.
   - The <p4_info> on each file now carries the one line p4 description of
     the file so that p4->p4 transferes can pick out the more detailed
     info.  VCP::Source::p4, VCP::Dest::p4 do this.
   - VCP::{Source,Dest}::{p4,cvs} now set binaryness on added files properly,
     I think.  For p4->p4, the native p4 type is preserved.  For CVS sources,
     seeing the keyword substitution flag 'o' or 'b' implies binaryness, for
     p4, seeing a filetype like qr/u?x?binary/ or qr/x?tempobj/ or "resource"
     implies binaryness (to non-p4 destinations).  NOTE: Seeing a 'o' or 'b'
     in a CVS source only ends up setting the 'b' option on the destination.
     That should be ok for most uses, but we can make it smarter for cvs->cvs
     transfers if need be.
#2 468 Barrie Slaymaker - VCP::Dest::p4 now does change number aggregation based on the
  comment field changing or whenever a new revision of a file with
  unsubmitted changes shows up on the input stream.  Since revisions of
  files are normally sorted in time order, this should work in a number
  of cases.  I'm sure we'll need to generalize it, perhaps with a time
  thresholding function.
- t/90cvs.t now tests cvs->p4 replication.
- VCP::Dest::p4 now doesn't try to `p4 submit` when no changes are
  pending.
- VCP::Rev now prevents the same label from being applied twice to
  a revision.  This was occuring because the "r_1"-style label that
  gets added to a target revision by VCP::Dest::p4 could duplicate
  a label "r_1" that happened to already be on a revision.
- Added t/00rev.t, the beginnings of a test suite for VCP::Rev.
- Tweaked bin/gentrevml to comment revisions with their change number
  instead of using a unique comment for every revision for non-p4
  t/test-*-in-0.revml files.  This was necessary to test cvs->p4
  functionality.
#1 467 Barrie Slaymaker Version 0.01, initial checkin in perforce public depot.