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. This destination driver will check out the indicated destination in a temporary directory and use it to add, delete, and alter files. =head1 DESCRIPTION This driver allows L<vcp|vcp> to insert revisions in to a CVS repository. There are no options at this time. =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 ) ; ## 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_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 ; $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 = $r->name ; my $work_path = $self->work_path( $fn ) ; debug "vcp: work_path '$work_path'" if debugging $self ; my $saw = $self->seen( $r ) ; die "Can't backfill already seen file '", $r->name, "'" if $saw ; 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 ; ## 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). $self->cvs( [ qw( update -d -p ), -r => $tag, $fn ], '>', $work_path ) ; die "'$work_path' not created by cvs checkout" unless -e $work_path ; return $work_path ; } sub backfill { my VCP::Dest::cvs $self = shift ; my VCP::Rev $r ; ( $r ) = @_ ; $r->work_path( $self->checkout_file( $r ) ) ; return 1 ; } my $old_r ; 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 ; ## Don't save the reference. This forces the DESTROY to happen here, ## if possible. TODO: Keep VCP::Rev from deleting files prematurely. my $saw = ! ! $self->seen( $r ) ; return if $r->is_base_rev ; my $fn = $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] ) ; $self->delete_seen( $r ) ; } 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 ; } } if ( -e $work_path ) { unlink $work_path or die "$! unlinking $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 ( ! $saw ) { ## 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 { $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 @tags = map { s/^([^a-zA-Z])/tag_$1/ ; s/\W/_/g ; $_ ; }( defined $r->rev_id ? "r_" . $r->rev_id : (), defined $r->change_id ? "ch_" . $r->change_id : (), $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 | |
---|---|---|---|---|---|
#1 | 1375 | Sean McCune | Creating my own branch for work on vcp. | ||
//guest/perforce_software/revml/lib/VCP/Dest/cvs.pm | |||||
#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. |