cvs.pm #25

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

=head1 NAME

VCP::Dest::cvs - cvs destination driver

=head1 SYNOPSIS

   vcp <source> cvs:module
   vcp <source> cvs:CVSROOT:module

where module is a module or directory that already exists within CVS.

=head1 DESCRIPTION

Checks out the indicated module or directory in to a temporary directory and
use it to add, delete, and alter files.

If the module does not exists, uses "cvs import" to create it.

This driver allows L<vcp|vcp> to insert revisions in to a CVS repository.
There are no options at this time.

TODO: Skip all directories named "CVS", in case a CVS tree is being imported.
Perhaps make it fatal, but use an option to allow it.  In this case, CVS
directories can be detected by scanning revs before doing anything.

=cut

$VERSION = 1 ;

use strict ;
use vars qw( $debug ) ;

$debug = 0 ;

use Carp ;
use File::Basename ;
use File::Path ;
use Getopt::Long ;
use VCP::Debug ':debug' ;
use VCP::Rev ;

use base qw( VCP::Dest VCP::Utils::cvs ) ;
use fields (
   'CVS_CHANGE_ID',  ## The current change_id in the rev_meta sequence, if any
   'CVS_LAST_MOD_TIME',  ## A HASH keyed on working files of the mod_times of
                     ## the previous revisions of those files.  This is used
		     ## to make sure that new revision get a different mod_time
		     ## so that CVS never thinks that a new revision hasn't
		     ## changed just because the VCP::Source happened to create
		     ## two files with the same mod_time.
   'CVS_PENDING_COMMAND', ## "add" or "edit"
   'CVS_PENDING',    ## Revs to be committed
## These next fields are used to detect changes between revs that cause a
## commit. Commits are batched for efficiency's sake.
   'CVS_PREV_CHANGE_ID', ## Change ID of previous rev
   'CVS_PREV_COMMENT',   ## Revs to be committed
   'CVS_LAST_SEEN',  ## HASH of last seen revisions, keyed by name
) ;

## Optimization note: The slowest thing is the call to "cvs commit" when
## something's been added or altered.  After all the changed files have
## been checked in by CVS, there's a huge pause (at least with a CVSROOT
## on the local filesystem).  So, we issue "cvs add" whenever we need to,
## but we queue up the files until a non-add is seem.  Same for when
## a file is edited.  This preserves the order of the files, without causing
## lots of commits.  Note that we commit before each delete to make sure
## that the order of adds/edits and deletes is maintained.

#=item new
#
#Creates a new instance of a VCP::Dest::cvs.  Contacts the cvsd using the cvs
#command and gets some initial information ('cvs info' and 'cvs labels').
#
#=cut

sub new {
   my $class = shift ;
   $class = ref $class || $class ;

   my VCP::Dest::cvs $self = $class->SUPER::new( @_ ) ;

   ## Parse the options
   my ( $spec, $options ) = @_ ;

   $self->parse_cvs_repo_spec( $spec ) ;
   $self->deduce_rev_root( $self->repo_filespec ) ;

   {
      local *ARGV = $options ;
      GetOptions(
         "NoFreakinOptionsAllowed" => \undef,
      )
	 or $self->usage_and_exit ;
   }

   $self->command_stderr_filter(
      qr{^(?:cvs (?:server|add|remove): (re-adding|use 'cvs commit' to).*)\n}
   ) ;

   return $self ;
}


sub handle_header {
   my VCP::Dest::cvs $self = shift ;

   debug "vcp: first rev" if debugging $self ;
   $self->rev_root( $self->header->{rev_root} )
      unless defined $self->rev_root ;

   $self->create_cvs_workspace( create_in_repository => 1 ) ;

   $self->{CVS_PENDING_COMMAND} = "" ;
   $self->{CVS_PENDING}         = [] ;
   $self->{CVS_PREV_COMMENT}    = undef ;
   $self->{CVS_PREV_CHANGE_ID}  = undef ;

   $self->SUPER::handle_header( @_ ) ;
}


sub checkout_file {
   my VCP::Dest::cvs $self = shift ;
   my VCP::Rev $r ;
   ( $r ) = @_ ;

   debug "vcp: $r checking out ", $r->as_string, " from cvs dest repo"
      if debugging $self ;

   my $fn = $self->denormalize_name( $r->name );
   my $work_path = $self->work_path( $fn ) ;
   debug "vcp: work_path '$work_path'" if debugging $self ;

   $self->{CVS_LAST_SEEN}->{$r->name} = $r;

   my ( undef, $work_dir ) = fileparse( $work_path ) ;
   $self->mkpdir( $work_path ) unless -d $work_dir ;

   my $tag = "r_" . $r->rev_id ;
   $tag =~ s/\W+/_/g ;

   my $b_suffix = $r->branch_id;
   $b_suffix = defined $b_suffix && length $b_suffix ? "_$b_suffix" : "";
   $tag .= $b_suffix;

   $tag =~ s/^([^a-zA-Z])/tag_$1/ ;
   $tag =~ s/\W/_/g ;
   ## Ok, the tricky part: we need to use a tag, but we don't want it
   ## to be sticky, or we get an error the next time we commit this
   ## file, since the tag is not likely to be a branch revision.
   ## Apparently the way to do this is to print it to stdout on update
   ## (or checkout, but we used update so it works with a $fn relative
   ## to the cwd, ie a $fn with no module name first).
## The -kb is a hack to get the tests to pass on Win32, where \n
## becomes \r\n on checkout otherwise.  TODO: figure out what is
## the best thing to do.  We might try it without the -kb, then
## if the digest check fails, try it again with -kb.  Problem is
## that said digest check occurs in VCP/Source/revml, not here,
## so we need to add a "can retry" return result to the API and
## modify the Sources to use it if a digest check fails.
   $self->cvs(
      [ qw( update -d -kb -p ), -r => $tag, $fn ],
      '>', $work_path
   ) ;
   die "'$work_path' not created by cvs checkout" unless -e $work_path ;

   return $work_path;
}


sub handle_rev {
   my VCP::Dest::cvs $self = shift ;

   my VCP::Rev $r ;
   ( $r ) = @_ ;

   if ( 
      ( @{$self->{CVS_PENDING}} )#|| $self->{CVS_DELETES_PENDING} )
      && (
         @{$self->{CVS_PENDING}} > 25  ## Limit command line length
	 || (
	    defined $r->change_id && defined $self->{CVS_PREV_CHANGE_ID}
	    &&      $r->change_id ne         $self->{CVS_PREV_CHANGE_ID}
	    && ( debugging( $self ) ? debug "vcp: change_id changed" : 1 )
	 )
	 || (
	    defined $r->comment && defined $self->{CVS_PREV_COMMENT}
	    &&      $r->comment ne         $self->{CVS_PREV_COMMENT}
	    && ( debugging( $self ) ? debug "vcp: comment changed" : 1 )
	 )
	 || (
	    grep( $r->name eq $_->name, @{$self->{CVS_PENDING}} )
	    && ( debugging( $self ) ? debug "vcp: name repeated" : 1 )
	 )
      )
   ) {
      debug "vcp: committing on general principles" if debugging $self ;
      $self->commit ;
   }

   $self->compare_base_revs( $r )
      if $r->is_base_rev && defined $r->work_path ;

   return if $r->is_base_rev ;

   my $fn = $self->denormalize_name( $r->name ) ;
   my $work_path = $self->work_path( $fn ) ;

   if ( $r->action eq 'delete' ) {
      $self->commit ;
      unlink $work_path || die "$! unlinking $work_path" ;
      $self->cvs( ['remove', $fn] ) ;
      ## Do this commit by hand since there are no CVS_PENDING revs, which
      ## means $self->commit will not work. It's relatively fast, too.
      $self->cvs( ['commit', '-m', $r->comment || '', $fn] ) ;
      delete $self->{CVS_LAST_SEEN}->{$r->name};
   }
   else {
      ## TODO: Move this in to commit().
      {
	 my ( $vol, $work_dir, undef ) = File::Spec->splitpath( $work_path ) ;
	 unless ( -d $work_dir ) {
	    my @dirs = File::Spec->splitdir( $work_dir ) ;
	    my $this_dir = shift @dirs  ;
	    my $base_dir = File::Spec->catpath( $vol, $this_dir, "" ) ;
	    do {
	       ## Warn: MacOS danger here: "" is like Unix's "..".  Shouldn't
	       ## ever be a problem, we hope.
	       if ( length $base_dir && ! -d $base_dir ) {
	          $self->mkdir( $base_dir ) ;
		  ## We dont' queue these to a PENDING because these
		  ## should be pretty rare after the first checkin.  Could
		  ## have a modal CVS_PENDING with modes like "add", "remove",
		  ## etc. and commit whenever the mode's about to change,
		  ## I guess.
		  $self->cvs( ["add", $base_dir] ) ;
	       }
	       $this_dir = shift @dirs  ;
	       $base_dir = File::Spec->catdir( $base_dir, $this_dir ) ;
	    } while @dirs ;
	 }
      }

      my $last_seen = $self->{CVS_LAST_SEEN}->{$r->name};
      $self->{CVS_LAST_SEEN}->{$r->name} = $r;

      my $switch_branches = $last_seen
            && ( ( $last_seen->branch_id || "" ) ne ( $r->branch_id || "" ) );

      $self->commit if $switch_branches;

      unlink $work_path or die "$! unlinking $work_path"
         if -e $work_path;

      if ( $switch_branches ) {
         if ( substr( $r->rev_id, -2 ) eq ".1" ) {
            ## This rev is the first of its branch.  Not sure if it's
            ## the same as the base revision or not, so play it safe and
            ## check it in.
            die "No base revision seen for ", $r->as_string
               unless $last_seen;
            $self->cvs( [ "rtag", "-b", "-r" . $last_seen->rev_id, $r->branch_id, $fn ] );
         }

         if ( defined $r->branch_id && length $r->branch_id ) {
            $self->cvs( [ "update", "-r", $r->branch_id, $fn ] );
         }
         else {
            $self->cvs( [ "update", "-A", $fn ] );
         }

         unlink $work_path or die "$! unlinking $work_path"
            if -e $work_path;
      }

      debug "vcp: linking ", $r->work_path, " to $work_path"
         if debugging $self ;

      ## TODO: Don't assume same filesystem or working link().
      link $r->work_path, $work_path
	 or die "$! linking '", $r->work_path, "' -> $work_path" ;

      if ( defined $r->mod_time ) {
	 utime $r->mod_time, $r->mod_time, $work_path
	    or die "$! changing times on $work_path" ;
      }

      my ( $acc_time, $mod_time ) = (stat( $work_path ))[8,9] ;
      if ( ( $self->{CVS_LAST_MOD_TIME}->{$work_path} || 0 ) == $mod_time ) {
         ++$mod_time ;
	 debug "vcp: tweaking mod_time on '$work_path'" if debugging $self ;
	 utime $acc_time, $mod_time, $work_path
	    or die "$! changing times on $work_path" ;
      }
      $self->{CVS_LAST_MOD_TIME}->{$work_path} = $mod_time ;

      $r->dest_work_path( $fn ) ;

      if ( ! $last_seen ) {
	 ## New file.
	 my @bin_opts = $r->type ne "text" ? "-kb" : () ;
	 $self->commit if $self->{CVS_PENDING_COMMAND} ne "add" ;
	 $self->cvs( [ "add", @bin_opts, "-m", $r->comment || '', $fn ] ) ;
	 $self->{CVS_PENDING_COMMAND} = "add" ;
      }
      else {
         ## Change the existing file
	 $self->commit if $self->{CVS_PENDING_COMMAND} ne "edit" ;
	 $self->{CVS_PENDING_COMMAND} = "edit" ;
      }

#      ## TODO: batch the commits when the comment changes or we see a
#      ## new rev for a file with a pending commit..
#      $self->cvs( ['commit', '-m', $r->comment || '', $fn] ) ;
#
debug "$r pushing ", $r->dest_work_path if debugging $self ;
      push @{$self->{CVS_PENDING}}, $r ;
  }

   $self->{CVS_PREV_CHANGE_ID} = $r->change_id ;
   $self->{CVS_PREV_COMMENT} = $r->comment ;
}


sub handle_footer {
   my VCP::Dest::cvs $self = shift ;

   $self->commit
       if $self->{CVS_PENDING} && @{$self->{CVS_PENDING}} ;#|| $self->{CVS_DELETES_PENDING} ;
   $self->SUPER::handle_footer ;
}


sub commit {
   my VCP::Dest::cvs $self = shift ;

   return unless @{$self->{CVS_PENDING}} ;

   ## All comments should be the same, since we alway commit when the 
   ## comment changes.
   my $comment = $self->{CVS_PENDING}->[0]->comment || '' ;

   ## @names was originally to try to convince cvs to commit things in the
   ## preferred order.  No go: cvs chooses some order I can't fathom without
   ## reading it's source code.  I'm leaving this in for now to keep cvs
   ## from having to scan the working dirs for changes, which may or may
   ## not be happening now (need to check at some point).
   my @names = map $_->dest_work_path, @{$self->{CVS_PENDING}} ;

   $self->cvs( ['commit', '-m', $comment, @names ] ) ;

   for my $r ( @{$self->{CVS_PENDING}} ) {
      ## TODO: Don't rtag it with r_ if it gets the same rev number from the
      ## commit.
      ## TODO: Batch files in to the rtag command, esp. for change number tags,
      ## for performance's sake.
      ## TODO: batch tags, too.
      my $b_suffix = $r->branch_id;
      $b_suffix = defined $b_suffix && length $b_suffix ? "_$b_suffix" : "";
      my @tags = map {
         s/^([^a-zA-Z])/tag_$1/ ;
	 s/\W/_/g ;
	 $_ ;
      }(
	 defined $r->rev_id    ? "r_"  . $r->rev_id    . $b_suffix : (),
         defined $r->change_id ? "ch_" . $r->change_id . $b_suffix : (),
	 $r->labels,
      ) ;

      $self->tag( $_, $r->dest_work_path ) for @tags ;
      ## TODO: Provide command line options for user-defined tag prefixes
    }

   @{$self->{CVS_PENDING}} = () ;
   $self->{CVS_PENDING_COMMAND} = "" ;
}


sub tag {
   my VCP::Dest::cvs $self = shift ;

   my $tag = shift  ;
   $tag =~ s/\W+/_/g ;
   $self->cvs( ['tag', $tag, @_] ) ;
}


=head1 AUTHOR

Barrie Slaymaker <barries@slaysys.com>

=head1 COPYRIGHT

Copyright (c) 2000, 2001, 2002 Perforce Software, Inc.
All rights reserved.

See L<VCP::License|VCP::License> (C<vcp help license>) for the terms of use.

=cut

1
# Change User Description Committed
#72 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
#71 4227 Barrie Slaymaker - VCP::Dest::cvs now handles a module name with no trailing "/..."
  (reported by Alexandros Karypidis karypid inf uth gr).
- VCP::Dest::cvs now handles a missing filespec (module name) if
  the source repository passed along a rev_root
#70 4127 Barrie Slaymaker - VCP::Dest::cvs now forces commits so that unchanged files
  will be committed.
#69 4126 Barrie Slaymaker - Comments with leading hyphens and embedded quotes are
  now tested for
- VCP::Dest::cvs now handles comments with embedded double
  quotes on Win32
#68 4021 Barrie Slaymaker - Remove all phashes and all base & fields pragmas
- Work around SWASHGET error
#67 4012 Barrie Slaymaker - Remove dependance on pseudohashes (deprecated Perl feature)
#66 3970 Barrie Slaymaker - VCP::Source handles rev queing, uses disk to reduce RAM
- Lots of other fixes
#65 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
#64 3908 Barrie Slaymaker - Debugging cleanups
#63 3855 Barrie Slaymaker - vcp scan, filter, transfer basically functional
    - Need more work in re: storage format, etc, but functional
#62 3850 Barrie Slaymaker - No longer stores all revs in memory
#61 3837 Barrie Slaymaker - Improved progress bar support
#60 3813 Barrie Slaymaker - VCP::Rev::previous() is no more
#59 3812 Barrie Slaymaker - VCP::Dest::* no longer need VCP::Rev->previous()
#58 3811 Barrie Slaymaker - fetch_*() and get_rev() renamed get_source_file()
#57 3809 Barrie Slaymaker - compare_base_revs() now always called with 2 parameters
#56 3805 Barrie Slaymaker - VCP::Revs::fetch_files() removed
#55 3706 Barrie Slaymaker - VCP gives some indication of output progress (need more)
#54 3460 Barrie Slaymaker - Revamp Plugin/Source/Dest hierarchy to allow for
  reguritating options in to .vcp files
#53 3410 Barrie Slaymaker - Minor win32 adaptation
- More aggressive filesystem cleanup
#52 3384 John Fetkovich moved setting of default repo_id
#51 3285 John Fetkovich In 'sub new' constructor, Only call parse_cvs_repo_spec if a $spec is
       provided.  parse_cvs_repo_spec also now sets repo_id.
#50 3278 John Fetkovich split 'sub init' out from 'sub new'
#49 3208 John Fetkovich documentation (pod) fixes.
#48 3194 John Fetkovich pod fix
#47 3165 Barrie Slaymaker Don't commit so often but do tell the user what's going on.
#46 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.
#45 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).
#44 2973 Barrie Slaymaker Fix handling of branched but unchanged files
#43 2972 Barrie Slaymaker Interim checkin
#42 2926 John Fetkovich remove --state-location switch
       add --db-dir and --repo-id switches
       build state location from concatenation of those two.
#41 2901 Barrie Slaymaker Make VCP::Dest::cvs use more persistant state to handle
       boundary conditions better.
#40 2872 Barrie Slaymaker Improve VCP::Dest::cvs branch handling
#39 2838 John Fetkovich Use parse_options rather than using Getopt::Long directly.
#38 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.
#37 2774 Barrie Slaymaker Update HeadRevDB on submit/commit/write
#36 2725 Barrie Slaymaker Start using HeadRevs.pm.
#35 2720 Barrie Slaymaker Factor RevMapDB code up in to VCP::Dest.
#34 2713 Barrie Slaymaker Factor RevMapDB management up in to VCP::Dest
#33 2712 Barrie Slaymaker RevMapDB works, branching seems to (pending further changes in
       statefulness so we can get incremental revml output without
       using labels in the source repo).
#32 2706 Barrie Slaymaker Interim checkin
#31 2703 Barrie Slaymaker use the new RevMapDB in VCP::Dest::cvs
#30 2699 Barrie Slaymaker remove unnecessary dependancies
#29 2663 Barrie Slaymaker Fix mtime bug in VCP::Dest::cvs in branching code
       Improve temp directory management
#28 2647 Barrie Slaymaker Add VCP::Dest::cvs --delete-cvsroot option
#27 2620 John Fetkovich Added some error checks.
#26 2293 Barrie Slaymaker Update CHANGES, TODO, improve .vcp files, add --init-cvs
#25 2235 Barrie Slaymaker Debugging cvs speed reader.
#24 2042 Barrie Slaymaker Basic source::p4 branching support
#23 2026 Barrie Slaymaker VCP::8::cvs now supoprt branching
#22 2015 Barrie Slaymaker submit changes
#21 2009 Barrie Slaymaker lots of fixes, improve core support for branches and VCP::Source::cvs
       now supports branches.
#20 2006 Barrie Slaymaker more preparations for branching support,
       handling of cvs :foo:... CVSROOT specs,
       misc fixes, improvements
#19 1998 Barrie Slaymaker Initial, revml and core VCP support for branches
#18 1758 Barrie Slaymaker Minor TODO added
#17 1728 Barrie Slaymaker CVS on win32, minor bugfixes
#16 1367 Barrie Slaymaker lots of docco updates
#15 1055 Barrie Slaymaker add sorting, revamp test suite, misc cleanup.
 Dest/revml is
not portable off my system yet (need to release ...::Diff)
#14 827 Barrie Slaymaker Add a test for and debug p4->cvs incremental exports.
#13 825 Barrie Slaymaker test, handle case where no revs are transferred and
VCP::Dest::*::handle_footer() blew up.
#12 811 Barrie Slaymaker more sensible name for a method.
#11 723 Barrie Slaymaker VCP::Dest::cvs tuning and cvs and p4 bugfixes
#10 720 Barrie Slaymaker Fix handling of $r->comment in VCP::Dest::cvs
#9 705 Barrie Slaymaker Release 0.22.
#8 628 Barrie Slaymaker Cleaned up POD in bin/vcp, added BSD-style license.
#7 623 Barrie Slaymaker Prefix CVS-unfriendly tags w/ "tag_" instead of "_"
#6 620 Barrie Slaymaker Underscorify CVS tags, only warn about undeleted files if
debugging.
#5 609 Barrie Slaymaker Add a file to the test procedure that it alternately added and
deleted (file is named "readd"). Fixed all destinations to handle
that.
#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.