diff options
Diffstat (limited to 'contrib/pvcs2rcs.in')
-rw-r--r-- | contrib/pvcs2rcs.in | 1314 |
1 files changed, 1314 insertions, 0 deletions
diff --git a/contrib/pvcs2rcs.in b/contrib/pvcs2rcs.in new file mode 100644 index 0000000..ac36ddc --- /dev/null +++ b/contrib/pvcs2rcs.in @@ -0,0 +1,1314 @@ +#! @PERL@ +# --------------------------------- +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +########################################################################### +# FUNCTION: +# To recursively walk through a PVCS archive directory tree (archives +# located in VCS/ or vcs/ subdirectories) and convert them to RCS archives. +# The RCS archive name is the PVCS workfile name with ",v" appended. +# +# SYNTAX: +# pvcs_to_rcs.pl --help +# +# where -l indicates the operation is to be performed only in the current +# directory (no recursion) +# +# EXAMPLE: +# pvcs_to_rcs +# Would walk through every VCS or vcs subdir starting at the current directory, +# and produce corresponding RCS archives one level above the VCS or vcs subdir. +# (VCS/../RCS/) +# +# NOTES: +# * This script performs little error checking and logging +# (i.e. USE AT YOUR OWN RISK) +# * This script was last tested using ActiveState's port of Perl 5.005_02 +# (internalcut #507) under Win95, though it does compile under Perl-5.00404 +# for Solaris 2.4 run on a Solaris 2.6 system. The script crashed +# occasionally under ActiveState's port of Perl 5.003_07 but this stopped +# happening with the update so if you are having problems, try updating Perl. +# Upgrading to cut #507 also seemed to coincide with a large speed +# improvement, so try and keep up, hey? :) It was executed from MKS's +# UNIX tools version 6.1 for Win32's sh. ALWAYS redirect your output to +# a log!!! +# * PVCS archives are left intact +# * RCS archives are created in VCS/../RCS/ (or ./RCS using '-pflat') +# * Branch labels in this script will be attached to the CVS magic +# revision number. For branch a.b.c of a particular file, this means +# the label will be attached to revision a.b.0.c of the converted +# file. If you use the TrunkTip (1.*) label, be aware that it will convert +# to RCS revision 0.1, which is useless to RCS and CVS. You'll probably +# have to delete these. +# * All revisions are saved with correct "metadata" (i.e. check-in date, +# author, and log message). Any blank log message is replaced with +# "no comment". This is because RCS does not allow non-interactive +# check in of a new revision without a comment string. +# * Revision numbers are incremented by 1 during the conversion (since +# RCS does not allow revision 1.0). +# * All converted branch numbers are even (the CVS paradigm) +# * Version labels are assigned to the appropriate (incremented) revision +# numbers. PVCS allows spaces and periods in version labels while RCS +# does not. A global search and replace converts " " and "." to "_" +# There may be other cases that ought to be added. +# * Any working (checked-out) copies of PVCS archives +# within the VCS/../ or vcs/../ (or possibly ./ with '-pflat') +# will be deleted (or overwritten) depending on your mode of +# operation since the current ./ is used in the checkout of each revision. +# I suppose if development continues these files could be redirected to +# temp space rather than ./ . +# * Locks on PVCS archives should be removed (or the workfiles should be +# checked-in) prior to conversion, although the script will blaze through +# the archive nonetheless (But you would lose any checked out revision(s)) +# * The -kb option is added to the RCS archive for workfiles with the following +# extensions: .bin .out .btl .rom .a07 .lib .exe .tco .obj .t8u .c8u .o .lku +# .a and a few others. The %bin_ext variable holds these values in regexp +# form. +# * the --force-binary option can be used to convert binary files which don't +# have proper extensions, but I'd *probably* edit the %bin_ext variable. +# * This script will abort occasionally with the error "invalid revision +# number". This is known to happen when a revision comment has +# /^\s*Rev/ (Perl regexp notation) in it. Fix the comment and start over. +# (The directory locks and existance checking make this a fairly quick +# process.) +# Binary files which do not have their mode set properly are likely to look +# corrupted on initial checkout and use, but using +# `cvs admin -kb <workfilename>' to retroactively change the RCS keyword +# substitution mode of the file to binary (and refreshing the files in any +# local workspaces they are checked out in: `rm <workfilename>; update' +# should do the trick) should end any problems with the original import. +# If anyone has checked in changes since the import, those revisions may +# be corrupted in the imported archive and therefore those changes (commits +# of corrupted data) may need to be backed out. +# * This script writes lockfiles in the RCS/ directories. It will also not +# convert an archive if it finds the RCS Archive existant in the RCS/ +# directory. This enables the conversion to quickly pick up where it left +# off after errors or interrupts occur. If you interrupt the script make +# sure you delete the last RCS Archive File which was being written. +# If you recieve the "Invalid revision number" error, then the RCS archive +# file for that particular PVCS file will not have been created yet. +# * This script will not create lockfiles when processing single +# filenames passed into the script, for hopefully obvious reasons. +# (lockfiles lock directories - DRP) +# * Log the output to a file. That makes it real easy to grep for errors +# later. (grep for "^[ \t]*(rcs|ci):" and be aware I might have missed +# a few cases (get? vcs?) !!!) *** Also note that this script will +# exibit some harmless RCS errors. Namely, it will attempt to lock +# branches which haven't been created yet. *** +# * I tried to keep the error and warning info up to date, but it seems +# to mean very little. This script almost always exits with a warning +# or an error that didn't seem to cause any harm. I didn't trace it +# and our imported source checks out and builds... +# It is probably happening when trying to convert empty directories +# or read files (possibly checked out workfiles ) which are not +# pvcs_archives. +# * You must use the -pflat option when processing single filenames +# passed as arguments to the script. This is probably a bug. +# * questions, comments, additions can be sent to info-cvs@nongnu.org +######################################################################### + + + +# +# USER Configurables +# + +# %bin_ext should be editable from the command line. +# +# NOTE: Each possible binary extension is listed as a Perl regexp +# +# The value associated with each regexp key is used to print a log +# message when a binary file is found. +my %bin_ext = + ( + '\.(?i)abs$' => "Absolute File", + '\.(?i)bin$' => "Binary", + '\.(?i)bit$' => "Bit File", + '\.(?i)ol$' => "Compiler Output", + '\.(?i)out$' => "Default Compiler Output", + '\.(?i)ln$' => "Linker Output", + '\.(?i)lob$' => "Lint Output", + '\.(?i)zob$' => "DBCO Object", + '\.(?i)mim$' => "MIME File", + '\.(?i)dwi$' => "DWI File", + '\.(?i)iop$' => "IOP File", + '\.(?i)btl$' => "", + '\.(?i)rom$' => "ROM File", + '\.(?i)a07$' => "", + '\.(?i)lib$' => "DOS/Wintel/Netware Compiler Library", + '\.(?i)lif$' => "Netware Binary File", + '\.(?i)(com|exe)$' => "DOS/Wintel Executable", + '\.(?i)tco$' => "", + '\.(?i)obj$' => "DOS/Wintel Compiler Object", + '\.(?i)res$' => "DOS/Wintel Resource File", + '\.(?i)ico$' => "DOS/Wintel Icon File", + '\.(?i)nlm$' => "Netware Loadable Module", + '\.(?i)t8u$' => "", + '\.(?i)c8u$' => "", + '\.(?i)lku$' => "", + '\.(?i)pdf$' => "Adobe Acrobat Portable Document Format", + '\.(?i)doc$' => "MS Word Document", + '\.(?i)dot$' => "MS Word Document Template", + '\.(?i)pps$' => "MS PowerPoint Presentation", + '\.(?i)xls$' => "MS Excel Spreadsheet", + '\.(?i)(bmp|gif|jfif|jpeg|jpg|png|tif|tiff|xbm)$' => "Image", + '\.(?i)(bz2|gz|tgz|zip)$' => "Compressed File", + '\.(?i)dll$' => "DOS/Wintel Dynamically Linked Library", + '\.(?i)class$' => "Compliled Java Class File", + '\.(?i)jar$' => "Java Archive File", + '\.(?i)war$' => "Java Web Archive File", + '\.o$' => "UNIX Compiler Object", + '\.a$' => "UNIX Compiler Library", + '\.so(\.\d+\.\d+)?$' => "UNIX Shared Library" + ); + +# The binaries this script is dependant on: +my @bin_dependancies = ("vcs", "vlog", "rcs", "ci"); + +# Where we should put temporary files +my $tmpdir = $ENV{TMPDIR} ? $ENV{TMPDIR} : "/var/tmp"; + +# We use these... +use strict; + +use Cwd; +use File::Basename; # For the usage message. +use File::Copy; +use File::Path; +use IO::File; +use Getopt::Long; + $Getopt::Long::bundling = 1; + +my $program = basename $0; +my $usage = "\ +usage: $program -h + $program [-lt] [-i vcsid] [-r flat|leaf] [-p flat|leaf] + [-x rcs_extension] [-v none|locks|exists] [options] [path...] +"; + +my $help = "\ +$usage + ---------------------------- ----------------------------------- + -h | --Help Print this text + + General Settings + ---------------------------- ----------------------------------- + --Recurse Recurse through directories + (default) + -l | --NORecurse Process only . + --Errorfiles Save a count of conversion errors + in the RCS archive directory + (default) (unimplemented) + --NOErrorfiles Don't save a count of conversion + errors (unimplemented) + ( -m | --Mode ) Convert Convert PVCS files to RCS files + (default) + ( -m | --Mode ) Verify Perform verification ONLY + (unimplemented) + ( -v | --VERIfy ) None Always replace existing RCS files + ( -v | --VERIfy ) LOCKS Same as exists unless a #conv.done + file exists in the RCS directory. + In that case, only the #conv.done + file's existance is verified for + that directory. (default) + ( -v | --VERIfy ) Exists Don't replace existing RCS files + ( -v | --VERIfy ) LOCKDates Verify that an existing RCS file's + last modification date is older + than that of the lockfile + (unimplemented) + ( -v | --VERIfy ) Revs Verify that the PVCS archive files + and RCS archive file contain the + same number of corresponding + revisions. Add only new revisions + to the RCS file. (unimplemented) + ( -v | --VERIfy ) Full Perform --verify=Revs and confirm + that the text of the revisions is + identical. Add only new revisions + unless an error is found. Then + erase the RCS archive and recreate + it. (unimplemented) + -t | --Test-binaries Use 'which' to check \$PATH for + the binaries required by this + script (default) + --NOTest-binaries Don't check for binaries + --VERBose Enable verbose output + --NOVerbose Disable verbose output (default) + -w | --Warnings Print warning messages (default) + --NOWarnings Don't print warning messages + + RCS Settings + ---------------------------- ----------------------------------- + ( -r | --RCS-Dirs ) leaf RCS files stored in ./RCS (default) + ( -r | --RCS-Dirs ) flat RCS files stored in . + (unimplemented) + ( -x | --RCS-Extension ) Set RCS file extension + (default = ',v') + --Force-binary Pass '-kb' to 'rcs -i' regardless + of the file extension + --NOForce-binary Only use '-kb' when the file has + a binary extension (default) + --CVS-Branch-labels Use CVS magic branch revision + numbers when attaching branch + labels (default) + --NOCvs-branch-labels Attach branch labels to RCS branch + revision numbers (unimplemented) + + CVS Settings + ---------------------------- ----------------------------------- + ( -d | --CVS-Module-path) Import RCS files directly into this + destination directory rather than + converting in place + + PVCS Settings + ---------------------------- ----------------------------------- + ( -p | --Pvcs-dirs ) leaf PVCS files expected in ./VCS + (default) + ( -p | --Pvcs-dirs ) flat PVCS files expected in . + ( -i | --VCsid ) vcsid Use vcsid instead of \$VCSID + + -------------------------------------------------------------------------- + The optional path argument should contain the name of a file or directory + to convert. If not given, it will default to '.'. + -------------------------------------------------------------------------- +"; + + + +# +# Initialize globals +# + +my ($errors, $warnings) = (0, 0); +my ($curlevel, $maxlevel); +my ($rcs_base_command, $ci_base_command); +my ($donefile_name, $errorfile_name); +my @rel_dirs = (); # list of relative directory names up to current dir + + +# set up the default options +my %options = ( + 'recurse' => 1, + 'mode' => "convert", + 'errorfiles' => 1, + 'rcs-dirs' => "leaf", + 'rcs-extension' => ",v", + 'force-binary' => 0, + 'cvs-branch-labels' => 1, + 'cvs-module-path' => undef, + 'pvcs-dirs' => "leaf", + 'verify' => "locks", + 'test-binaries' => 1, + 'vcsid' => $ENV{VCSID} || "", + 'verbose' => 0, + 'debug' => 0, + 'warnings' => 1 + ); + + + +# This is untested except under Solaris 2.4 or 2.6 and +# may not be portable +# +# I think the readline lib or some such has an interface +# which may enable this now. The perl installer sure looks +# like it's testing this kind of thing, anyhow. +sub hit_any_key + { + STDOUT->autoflush; + system "stty", "-icanon", "min", "1"; + + print "Hit any key to continue..."; + getc; + + system "stty", "icanon", "min", "0"; + STDOUT->autoflush (0); + + print "\nI always wondered where that key was...\n"; + } + + + +# print the usage +sub print_usage + { + my $fh = shift; + unless (ref $fh) + { + my $fdn = $fh ? $fh : "STDERR"; + $fh = new IO::File; + $fh->fdopen ($fdn, "w"); + } + + $fh->print ($usage); + } + +# print the help +sub print_help + { + my $fh = shift; + unless (ref $fh) + { + my $fdn = $fh ? $fh : "STDOUT"; + $fh = new IO::File; + $fh->fdopen ($fdn, "w"); + } + + $fh->print ($help); + } + +# print the help and exit $_[0] || 0 +sub exit_help + { + print_help; + exit shift || 0; + } + +sub error_count + { + my $type = shift + or die "$0: error - error_count usage: error_count type [, ref] [, LIST]\n"; + my $error_count_ref; + my $outstring; + + if (ref ($_[0]) && ref ($_[0]) == "SCALAR") + { + $error_count_ref = shift; + } + else + { + $error_count_ref = \$errors; + } + $$error_count_ref++; + + push @_, "something wrong.\n" unless ( @_ > 0 ); + + $outstring = sprintf "$0: $type - " . join ("", @_); + $outstring .= sprintf " - $!\n" unless ($outstring =~ /\n$/); + + print STDERR $outstring; + + if ($options{errorfiles}) + { + my $fh = new IO::File ">>$errorfile_name" or new IO::File ">$errorfile_name"; + if ($fh) + { + $fh->print ($$error_count_ref . "\n"); + $fh->print ($outstring); + $fh->close; + } + else + { + my $cd = cwd; + print STDERR "$0: error - failed to open errorfile $cd/$errorfile_name - $!\n" + if ($options{debug}); + } + } + + return $$error_count_ref; + } + + + +# the main procedure that is run once in each directory +sub execdir + { + my $dir = shift; + my ($errors, $warnings) = (0, 0); # We return these error counters + my $old_dir = cwd; + + local ($_, @_); + + my $i; # Generic counter + my ($pvcsarchive, $workfile, $rcsarchive); # .??v, checked out file, and ,v files, + # respectively + my ($rev_count, $first_vl, $last_vl, $description, + $rev_index, @rev_num, %checked_in, %author, + $relative_comment_index, @comment_string, + %comment); + my ($num_version_labels, $label_index, @label_revision, $label, + @new_label, $rcs_rev); + my ($revision, %rcs_rev_num); + my @remainder; + my ($get_output, $rcs_output, $ci_output, $mv_output); + my ($ci_command, $rcs_command, $wtr); + my @hits; + my ($num_fields); + my $skipdirlock; # if true, don't write conv.out + # used only for single file operations + # at the moment + my $cd; + my $cvs_dir; + + my @filenames; + # We may have recieved a single file name to process... + if ( -d $dir ) + { + # change into the directory to be processed + # open the current directory for listing + # initialize the list of filenames + # and set filenames equal to directory listing + unless ( ( chdir $dir ) and ( opendir CURDIR, "." ) and ( @filenames = readdir CURDIR ) ) + { + $cd = cwd; + error_count 'error', \$errors, "skipping directory $dir from $cd"; + chdir $old_dir or die "Failed to restore original directory ($old_dir): ", $!, ", stopped"; + return ($errors, $warnings); + } + + # clean up by closing the directory + closedir(CURDIR); + + if ($options{'rcs-dirs-flat'} && $options{'cvs-module-path'}) + { + my @cur_dir_names = split qr{[/\\]}, cwd; + my $rel_cd = $cur_dir_names[-1]; + push @rel_dirs, $rel_cd; + $cvs_dir = "$options{'cvs-module-path'}/" + . join "/", @rel_dirs; + if (!-d $cvs_dir) + { + print "Creating directory \`$cvs_dir'\n"; + if (!mkpath ($cvs_dir)) + { + pop @rel_dirs; + error_count 'error', \$errors, +"failed to make directory \`$cvs_dir' - skipping directory \`$cd'"; + chdir $old_dir or die +"Failed to restore original directory (\`$old_dir'): ", $!, ", stopped"; + return ($errors, $warnings); + # after all, we have nowhere to put + # them... + } + } + } + + } + elsif ( -f $dir ) # we recieved a single file + { + push @filenames, $dir; + $skipdirlock = 1; + } + else + { + $cd = cwd; + error_count 'error', \$errors, "no such directory/file $dir from $cd\n"; + chdir $old_dir or die +"Failed to restore original directory ($old_dir): ", $!, ", stopped"; + return ($errors, $warnings); + } + + # save the current directory + $cd = cwd; + + # increment the global $curlevel variable + $curlevel = $curlevel +1; + + # initialize a list for any subdirectories and any files + # we need to process + my $vcsdir = ""; + my (@subdirs, $fn, $file, @files, @pvcsarchives); + + # print "$cd: " . join (", ", @filenames) . "\n"; + # hit_any_key; + + (@files, @pvcsarchives) = ( (), () ); + # begin a for loop to execute on each filename in the list @filename + foreach $fn (@filenames) + { + # if the file is a directory... + if (-d $fn) + { + # then if we are not expecting a flat arrangement of pvcs files + # and we found a vcs directory add its files to @pvcsarchives + if (!$options{'pvcs-dirs-flat'} and $fn =~ /^vcs$/i) + { + if ($options{verify} =~ /^locks$/ ) { + if ( -f $donefile_name ) { + print "Verified existence of lockfile $cd/$donefile_name." + . ( ($options{mode} =~ /^convert$/) ? " Skipping directory." : "" ) + . "\n" if ($options{verbose}); + next; + } elsif ( $options{mode} =~ /^verify$/ ) { + print "No lockfile found for $cd .\n"; + next; + } + } + + # else add the files in the vcs dir to our list of files to process + error_count 'warning', \$warnings, "Found two vcs dirs in directory $cd.\n" + if ($vcsdir and $options{warnings}); + + $vcsdir = $fn; + + unless ( ( opendir VCSDIR, $vcsdir ) and ( @files = readdir VCSDIR ) ) + { + error_count 'error', \$errors, "skipping directory &cd/$fn"; + next; + } + closedir VCSDIR; + + # and so we don't need to worry about where these + # files came from later... + foreach $file (@files) + { + push @pvcsarchives, "$vcsdir/$file" if (-f "$vcsdir/$file"); + } + + # don't want recursion here... + @pvcsarchives = grep !/^\.\.?$/, @pvcsarchives; + } + elsif ($fn !~ /^\.\.?$/) + { + next if (!$options{'rcs-dirs-flat'} and $fn =~ /^rcs$/i); + # include it in @subdir if it's not a parent directory + push(@subdirs,$fn); + } + } + # else if we are processing a flat arrangement of pvcs files... + elsif ($options{'pvcs-dirs-flat'} and -f $fn) + { + if ($options{verify} =~ /^locks$/) { + if ( -f $donefile_name) { + print "Found lockfile $cd/$donefile_name." + . ( ($options{mode} =~ /^convert$/) ? " Skipping directory." : "" ) + . "\n" if ($options{verbose}); + last; + } elsif ($options{mode} =~ /^verify$/) { + print "No lockfile found for $cd .\n"; + last; + } + } + # else add this to the list of files to process + push (@pvcsarchives, $fn); + } + } + + # print "pvcsarchives: " . join (", ", @pvcsarchives) . "\n"; + # print "subdirs: " . join (", ", @subdirs) . "\n"; + # hit_any_key; + + # for loop of subdirs + foreach (@subdirs) + { + # run execdir on each sub dir + if ($maxlevel >= $curlevel) + { + my ($e, $w) = execdir ($_); + $errors += $e; + $warnings += $w; + } + } + + # Print output header for each directory + print("Directory: $cd\n"); + + # the @files variable should already contain the list of files + # we should attempt to process + if ( @pvcsarchives && ( $options{mode} =~ /^convert$/ ) ) + { + # create an RCS directory in parent to store RCS files in + if ( !( $options{'rcs-dirs-flat'} or (-d "RCS") or mkpath ( "RCS" ) ) ) + { + error_count 'error', \$errors, "failed to make directory $cd/RCS - skipping directory $cd"; + @pvcsarchives = (); + # after all, we have nowhere to put them... + } + } + + # begin a for loop to execute on each filename in the list @files + foreach $pvcsarchive (@pvcsarchives) + { + my $got_workfile = 0; + my $got_version_labels = 0; + my $got_description = 0; + my $got_rev_count = 0; + + my $abs_file = $cd . "/" . $pvcsarchive; + + print("Verifying $abs_file...\n") if ($options{verbose}); + + print "vlog $pvcsarchive\n"; + # FIXME: Quoting this is better than no quotes, but quotes in + # filenames remain unquoted. + my $vlog_output = `vlog \"$pvcsarchive\"`; + + # Split the vcs status output into individual lines + my @vlog_strings = split /\n/, $vlog_output; + my $num_vlog_strings = @vlog_strings; + $_ = $vlog_strings[0]; + if ( /^\s*$/ || /^vlog: warning/ ) + { + error_count 'warning', \$warnings, "$abs_file is NOT a valid PVCS archive!!!\n"; + next; + } + + my $num; + # Collect all vlog output into appropriate variables + # + # This will ignore at the very least the /^\s*Archive:\s*/ field + # and maybe more. This should not be a problem. + for ( $num = 0; $num < $num_vlog_strings; $num++ ) + { + # print("$vlog_strings[$num]\n"); + $_ = $vlog_strings[$num]; + + if( ( /^Workfile:\s*/ ) && (!$got_workfile ) ) + { + my $num_fields; + + $got_workfile = 1; + # get the string to the right of the above search (with any path stripped) + $workfile = $'; + $num_fields = split /[\/\\]/, $workfile; + if ( $num_fields > 1 ) + { + $workfile = $_[$num_fields - 1 ]; + } + + $rcsarchive = $options{'rcs-dirs-flat'} ? "" : "RCS/"; + $rcsarchive .= $workfile; + $rcsarchive .= $options{'rcs-extension'} if ($options{'rcs-extension'}); + print "Workfile is $workfile\n" if ($options{debug}); + } + + elsif ( ( /^Rev count:\s*/ ) && (!$got_rev_count ) ) + { + $got_rev_count = 1; + # get the string to the right of the above search + $rev_count = $'; + print "Revision count is $rev_count\n"; + } + + elsif ( ( /^Version labels:\s*/ ) && (!$got_version_labels ) ) + { + $got_version_labels = 1; + $first_vl = $num+1; + print "Version labels start at $first_vl\n" if ($options{debug}); + } + + elsif ( ( /^Description:\s*/ ) && (!$got_description ) ) + { + $got_description = 1; + $description = $vlog_strings[$num+1]; + print "Description is `$description'\n" if ($options{debug}); + $last_vl = $num++ - 1; + } + + elsif ( /^Rev\s+/ ) # get all the revision information at once + { + $rev_index = 0; + @rev_num = (); + while ( $rev_index < $rev_count ) + { + $_ = $vlog_strings[$num]; + /^\s*Rev\s+(\d+\.(\d+\.\d+\.)*\d+)$/; + $rev_num[$rev_index] = $1; + print "Found revision: $rev_num[$rev_index]\n" if ($options{debug}); + die "Not a valid revision ($rev_num[$rev_index]).\n" + if ($rev_num[$rev_index] !~ /^(\d+\.)(\d+\.\d+\.)*\d+$/); + + $_ = $vlog_strings[$num+1]; + /^\s*Locked\s*/ and $num++; + + $_ = $vlog_strings[$num+1]; + /^\s*Checked in:\s*/; + $checked_in{$rev_num[$rev_index]} = "\"" . $' . "\""; + print "Checked in: $checked_in{$rev_num[$rev_index]}\n" if ($options{debug}); + + $_ = $vlog_strings[$num+3]; + /^\s*Author id:\s*/; + my @fields = split; + $author{$rev_num[$rev_index]} = "\"" . $fields[2] . "\""; + print "Author: $author{$rev_num[$rev_index]}\n" if ($options{debug}); + + my @branches = (); + $_ = $vlog_strings[$num+1]; + if (/^\s*Branches:\s*/) + { + $num++; + @branches = split /\s+/, $'; + } + + $relative_comment_index = 0; + @comment_string = (); + while (($num + 4 + $relative_comment_index) < @vlog_strings) + { + last if $vlog_strings[$num+4+$relative_comment_index] + =~ /^\s*Rev\s+(\d+\.(\d+\.\d+\.)*\d+)$/ + && $vlog_strings[$num+3+$relative_comment_index] + =~ /^-{35}$/; + + # We need the \n added for multi-line comments. There is no effect for + # single-line comments since RCS inserts the \n if it doesn't exist already + # print "Found commment line: $vlog_strings[$num+4+$relative_comment_index]\n" + # if ($options{debug}); + push @comment_string, $vlog_strings[$num+4+$relative_comment_index], "\n"; + $relative_comment_index += 1; + } + # print "Popped from comment: " . join ("", splice (@comment_string, -2)) + # . "\n" + # if ($options{debug}); + # Pop the "-+" or "=+" line from the comment + while ( (pop @comment_string) !~ /^-{35}|={35}$/ ) + {} + $comment{$rev_num[$rev_index]} = join "", @comment_string; + + $num += ( 4 + $relative_comment_index ); + print "Got comment for $rev_num[$rev_index]\n" if ($options{debug}); + print "comment string: $comment{$rev_num[$rev_index]}\n" if ($options{debug}); + $rev_index += 1; + } # while ( $rev_index < $rev_count ) + $num -= 1; #although there should be nothing left for this to matter + } # Get Rev information + } # for ($num = 0; $num < $num_vlog_strings; $num++) + # hit_any_key if ($options{debug}); + # Create RCS revision numbers corresponding to PVCS version numbers + my @rcs_rev_nums; + foreach $revision (@rev_num) + { + $rcs_rev_num{ $revision } = &pvcs_to_rcs_rev_number( $revision ); + push @rcs_rev_nums, $rcs_rev_num{$revision}; + print"PVCS revision is $revision; RCS revision is $rcs_rev_num{ $revision }\n" + if ($options{debug}); + } + + # Sort the revision numbers - PVCS and RCS store them in different orders + # Clear @_ so we don't pass anything in by accident... + @_ = (); + @rev_num = sort revisions @rev_num; + print "Sorted rev_nums:\n" . join ("\n", @rev_num) . "\n" if ($options{debug}); + # hit_any_key; + + # Loop through each version label, checking for need to relabel ' ' with '_'. + $num_version_labels = $last_vl - $first_vl + 1; + print "Version label count is $num_version_labels\n"; + for( $i = $first_vl; $i <= $last_vl; $i += 1 ) + { + # print("$vlog_strings[$i]\n"); + $label_index = $i - $first_vl; + $_=$vlog_strings[$i]; + print "Starting with string '$_'\n" if ($options{debug}); + my @fields = split /\"/; + $label = $fields[1]; + print "Got label '$label'\n" if ($options{debug}); + @fields = split /\s+/, $fields[2]; + $label_revision[$label_index] = $fields[2]; + print "Original label is $label_revision[$label_index]\n" if ($options{debug}); + + # Create RCS revision numbers corresponding to PVCS version numbers by + # adding 1 to the revision number (# after last .) + $label_revision[ $label_index ] = pvcs_to_rcs_rev_number( $label_revision [ $label_index ] ); + # replace ' ' with '_', if needed + $_=$label; + $new_label[$label_index] = $label; + $new_label[$label_index] =~ s/ /_/g; + $new_label[$label_index] =~ s/\./_/g; + $new_label[$label_index] = "\"" . $new_label[$label_index] . "\""; + print"Label $new_label[$label_index] is for revision $label_revision[$label_index]\n" if ($options{debug}); + } + + ########## + # + # See if the RCS archive is up to date with the PVCS archive + # + ########## + my $cvsarchive; + $cvsarchive = "$cvs_dir/$rcsarchive" if $options{'cvs-module-path'}; + $cvsarchive .= $rcsarchive; + if ($options{verify} =~ /^locks|exists$/ and -f $cvsarchive) + { + print "Verified existence of " + . ($options{'cvs-module-path'} ? $cvsarchive : "$cd/$rcsarchive") + . "." + . ( ($options{mode} =~ /^convert$/) ? " Skipping." : "" ) + . "\n" if ($options{verbose}); + next; + } + + # Create RCS archive and check in all revisions, then label. + my $first_time = 1; + foreach $revision (@rev_num) + { + # print "get -p$revision $pvcsarchive >$workfile\n"; + print "get -r$revision $pvcsarchive\n"; + # $vcs_output = `vcs -u -r$revision $pvcsarchive`; + # $get_output = `get -p$revision $pvcsarchive >$workfile`; + # FIXME: Doesn't handle quotes in filenames as FIXME above. + $get_output = `get -r$revision \"$pvcsarchive\"`; + + # if this is the first time, delete the rcs archive if it exists + # need for $options{verify} == none + unlink $rcsarchive if ($first_time and $options{verify} =~ /^none$/ and -f $rcsarchive); + + # Also check here whether this file ought to be "binary" + if ( $first_time ) + { + $rcs_command = "$rcs_base_command -i"; + if ( ( @hits = grep { $workfile =~ /$_/ } keys %bin_ext ) || $options{'force-binary'} ) + { + $rcs_command .= " -kb"; + $workfile =~ /$hits[0]/ if (@hits); + print "Binary attribute -kb added (" + . (@hits ? "file type is '$bin_ext{$hits[0]}' for extension '$&'" : "forced") + . ")\n"; + } + + # FIXME: Doesn't handle quotes and other special characters in + # filenames as two FIXMEs above. + $rcs_command .= " \"$workfile\""; + + # print and execute the rcs archive initialization command + print "$rcs_command\n"; + $wtr = new IO::File "|$rcs_command"; + $wtr->print ($description); + $wtr->print ("\n") unless ($description =~ /\n$/s); + $wtr->print (".\n"); + $wtr->close; + + # $rcs_output = `$rcs_base_command -i -kb $workfile`; + } + + # if this isn't the first time, we need to lock the rcs branch + # + # This is a little messy, but it works. Some extra locking is attempted. + # (This happens the first time a branch is used, at the least) + my $branch = ""; + my @branch; + @branch = split /\./, $rcs_rev_num{$revision}; + pop @branch; + $branch = join ".", @branch if @branch != 1; + + # FIXME: Quotes around file names handles spaces but not shell + # metacharacters in file names. + unless ($first_time) + { + print "$rcs_base_command -l$branch \"$workfile\"\n" + if $options{'debug'}; + $rcs_output = `$rcs_base_command -l$branch \"$workfile\"`; + } + + # If an empty comment is specified, RCS will not check in the file; + # check for this case. (but an empty -t- description is fine - go figure!) + # Since RCS will pause and ask for a comment if one is not given, + # substitute a dummy comment "no comment". + $comment{$revision} =~ /^\s*$/ and $comment{$revision} = "no comment\n"; + + $ci_command = $ci_base_command; + $ci_command .= " -f -r$rcs_rev_num{$revision} -d$checked_in{$revision}" + . " -w$author{$revision}"; + + $ci_command .= " \"$workfile\""; + + # print and execute the ci command + print "$ci_command\n"; + $wtr = new IO::File "|$ci_command"; + $wtr->print ($comment{$revision}); + $wtr->print ("\n") unless ($comment{$revision} =~ /\n$/s); + $wtr->print (".\n"); + $wtr->close; + # $ci_output = `$ci_command`; + # $ci_output = `cat $tmpdir/ci.out`; + + $first_time = 0 if ($first_time); + } # foreach revision + + # Keep track of 1.*, 2.*, etc. branches as they are created. + my %trunk_branches; + + # Attach version labels + for( $i = $num_version_labels - 1; $i >= 0; $i -= 1 ) + { + print "$rcs_base_command -n$new_label[$i]:$label_revision[$i] \"$workfile\"\n" + if $options{'debug'}; + $rcs_output = `$rcs_base_command -n$new_label[$i]:$label_revision[$i] \"$workfile\"`; + print "Version label $new_label[$i] added to revision $label_revision[$i]\n"; + + # If the label revision is attached to a 1.* revision on the trunk + # when a 2.* revision exists, then 1.MAX needs to be branched to + # allow commits to this label. This applies to 2.* when 3.* + # exists, as well. + if ($label_revision[$i] !~ /\./) + { + # This revision is attached to the trunk. + # $rcs_rev_nums[0] will always be the max revision. + print "Label `$new_label[$i]' moved from $label_revision[$i] to "; + if (exists $trunk_branches{$label_revision[$i]}) + { + $label_revision[$i] = $trunk_branches{$label_revision[$i]}; + } + else + { + # Attached to X.* with X < M + my @X_revs = grep /^$label_revision[$i]\./, @rcs_rev_nums; + # Need a _NEW_ branch from $X_revs[0] to attach + # to. CVS could do this easily, but our archive + # isn't in a CVS repository yet. + my @tmp_lbl = @label_revision; + my @branch_nums = grep s/^\Q$X_revs[0]\E\.0\.(\d+)$/$1/, @tmp_lbl; + @tmp_lbl = @rcs_rev_nums; + push @branch_nums, + grep (s/^\Q$X_revs[0]\E\.(\d+)\.\d+$/$1/, @tmp_lbl); + my $max = 0; + foreach my $num (@branch_nums) + { + $max = $num if $num > $max; + } + $max += 2; + $trunk_branches{$label_revision[$i]} = "$X_revs[0].0.$max"; + $label_revision[$i] = "$X_revs[0].0.$max"; + } + print "$label_revision[$i].\n"; + } + + $rcs_output = `$rcs_base_command -n$new_label[$i]:$label_revision[$i] \"$workfile\"`; + print "Version label $new_label[$i] added to revision $label_revision[$i]\n"; + + if ($label_revision[$i] =~ /^(.*)\.0\./) + { + my $base = $1; + my $rootlbl = $new_label[$i]; + $rootlbl =~ s/.$/_broot$&/; + $rcs_output = `$rcs_base_command -n$rootlbl:$base \"$workfile\"`; + print "Version label $rootlbl added to revision $base\n"; + } + + } # foreach label + + if ($options{'cvs-module-path'}) + { + print "Moving $rcsarchive to $cvsarchive\n"; + move $rcsarchive, $cvsarchive or warn "Move failed: $!"; + } + + # hit_any_key; + } # foreach pvcs archive file + + # We processed a vcs directory, so if there were any files, lock it. + # We are guaranteed to have made the attempt at + # + # $skipdirlock gets set if a single file name was passed to this function to enable + # a '$0 *' operation... + if ( @pvcsarchives && !$skipdirlock) + { + my $fh = new IO::File ">>$donefile_name" or new IO::File ">$donefile_name"; + if ($fh) + { + $fh->close; + } + else + { + error_count 'error', \$errors, "couldn't create lockfile $cd/$donefile_name"; + } + } + + $curlevel = $curlevel - 1; + + chdir $old_dir + or die "Failed to restore original directory ($old_dir): ", $!, ", stopped"; + + # Update the relative directory path. + pop @rel_dirs if -d $dir; + + return ($errors, $warnings); + } + + + +# +# This function effectively does a cmp between two revision numbers +# It is intended to be passed into Perl's sort routine. +# +# the pvcs_out is not implemented well. It should probably be +# returnning $b[0] <=> $a[0] rather than $a[0] <=> $b[0] +# +# The @_ argument implementation was going to be used for revision +# comparison as an aid to remove the /^\sRev/ in revision comment +# error. The effort was fruitless at the time. +sub revisions + { + my @a = split /\./, (defined $a) ? $a : shift; + my @b = split /\./, (defined $b) ? $b : shift; + my $function = @_ ? shift : 'rcs_in'; + my ($i, $ret_val); + + die "Not enough arguments to revisions : a = ", join (".", @a), + "; b = ", join (".", @b), ", stopped" + unless (@a and @b); + + for ($i = 0; $i < scalar( @a ) && $i < scalar( @b ); $i++) + { + $a[$i] == $b[$i] or return ($a[$i] <=> $b[$i]); + } + + return 0 if (scalar (@a) == scalar (@b)); + + if ($function eq 'rcs_in') + { + return (($i == @b) || -1); + } + elsif ($function eq 'pvcs_out') + { + return (($i == @a) || -1); + } + else + { + die "error - Invalid function type passed to revisions ($function)", ", stopped"; + } + } + + + +sub pvcs_to_rcs_rev_number + { + my($input, $num_fields, @rev_string, $return_rev_num, $i); + + $input = $_[0]; + $num_fields = split /\./, $input; + @rev_string = @_; + # @rev_string[$num_fields-1] += 1; + + for( $i = 1; $i < $num_fields; $i += 1 ) + { + if ( $i % 2 ) + { + # DRP: 10/1 + # RCS does not allow revision zero + $rev_string[ $i ] += 1; + } + elsif ( $i ) + { + # DRP: 10/1 + # Branches must have even references for compatibility + # with CVS's magic branch numbers. + # (Indexes 2, 4, 6...) + $rev_string[ $i ] *= 2; + } + } + + # If this is a branch revision # (PVCS: a.b.c.*) then we want the CVS + # revision # instead. It's okay to do this conversion here since we + # never commit to branches. We'll only get a PVCS revision # in that + # form when looking through the revision labels. + if ($input =~ /\*$/) + { + pop @rev_string; + # If there is only one entry in @rev_string, this is a + # revision that needs to be attached to the trunk. Let it be + # for now. It might require a new branch, but we can't decide + # which branches are valid to create before we know what + # branches already exist. + push @rev_string, splice (@rev_string, -1, 1, "0") + unless @rev_string == 1; + } + + $return_rev_num = join ".", @rev_string; + return $return_rev_num; + } + + + + + +### +### +### +### +### +### MAIN program: checks to see if there are command line parameters +### +### +### +### +### + + + + + +# and read the options +die $usage + unless GetOptions (\%options, "h|help" => \&exit_help, + "recurse!", "mode|m=s", "errorfiles!", "l", + "rcs-dirs|rcs-directories|r=s", + "pvcs-dirs|pvcs-directories|p=s", "test-binaries|t!", + "rcs-extension=s", "verify|v=s", "vcsid|i=s", "verbose!", + "debug!", "force-binary!", "cvs-branch-labels!", + "warnings|w!", "cvs-module-path|d=s"); + + + +# +# Special processing for -l !^#%$^@#$%#$ +# +# At the moment, -l overrides --recurse, regardless of the order the +# options were passed in +# +$options{recurse} = 0 if defined $options{l}; +delete $options{l}; + + + +# Make sure we got acceptable values for rcs-dirs and pvcs-dirs +my @hits = grep /^$options{'rcs-dirs'}/i, ("leaf", "flat"); +@hits == 1 or die + "$0: $options{'rcs-dirs'} invalid argument to --rcs-dirs or ambiguous\n" + . " abbreviation.\n" + . " Must be one of: 'leaf' or 'flat'.\n" + . $usage; +$options{'rcs-dirs'} = $hits[0]; +$options{'rcs-dirs-flat'} = ($options{'rcs-dirs'} =~ /flat/); +delete $options{'rcs-dirs'}; + +@hits = grep /^$options{'pvcs-dirs'}/i, ("leaf", "flat"); +@hits == 1 or die + "$0: $options{'pvcs-dirs'} invalid argument to --pvcs-dirs or ambiguous\n" + . " abbreviation.\n" + . " Must be one of: 'leaf' or 'flat'.\n" + . $usage; +$options{'pvcs-dirs'} = $hits[0]; +$options{'pvcs-dirs-flat'} = ($options{'pvcs-dirs'} =~ /flat/); +delete $options{'pvcs-dirs'}; + +# and for verify +@hits = grep /^$options{verify}/i, ("none", "locks", "exists", "lockdates", "revs", "full"); +@hits == 1 or die + "$0: $options{verify} invalid argument to --verify or ambiguous\n" + . " abbreviation.\n" + . " Must be one of: 'none', 'locks', 'exists', 'lockdates', 'revs',\n" + . " or 'full'.\n" + . $usage; +$options{verify} = $hits[0]; +$options{verify} =~ /^none|locks|exists$/ or die + "$0: --verify=$options{verify} unimplemented.\n" + . $usage; + +# and mode +@hits = grep /^$options{mode}/i, ("convert", "verify"); +@hits == 1 or die + "$0: $options{mode} invalid argument to --mode or ambiguous abbreviation.\n" + . " Must be 'convert' or 'verify'.\n" + . $usage; +$options{mode} = $hits[0]; + +$options{'cvs-branch-labels'} or die + "$0: RCS Branch Labels unimplemented.\n" + . $usage; + +# export VCSID into th environment for ourselves and our children +$ENV{VCSID} = $options{vcsid}; + + + +# +# Verify we have all the binary executables we need to run this script +# +# Allowed this feature to be disabled in case which is missing or we are +# running on a system which does not return error codes properly (e.g. WIN95) +# +# -- i.e. I don't feel like grepping output yet. -- +# +my @missing_binaries = (); +if ($options{'test-binaries'}) + { + foreach (@bin_dependancies) + { + my $output = qx/which $_ 2>&1/; + print $output if $options{verbose} && $output; + if ($? || $output =~ /^no/) + { + push @missing_binaries, $_; + } + } + + if (scalar @missing_binaries) + { + print STDERR "The following executables were not found in your PATH: " + . join ( " ", @missing_binaries ) + . "\n" + . "You must correct this before continuing.\n"; + exit 1; + } + } +delete $options{'test-binaries'}; + + + +# +# set up our base archive manipulation commands +# + +# set up our rcs_command mods +$rcs_base_command = "rcs"; +$rcs_base_command .= " -x$options{'rcs-extension'}" + if $options{'rcs-extension'}; + +# set up our rcs_command mods +$ci_base_command = "ci"; +$ci_base_command .= " -x$options{'rcs-extension'}" + if $options{'rcs-extension'}; + + + +# +# So our logs fill in a manner we can monitor with 'tail -f' fairly easily: +# +STDERR->autoflush (1); +STDOUT->autoflush (1); + + + +# Initialize the globals we use to keep track of recursion +if ($options{recurse}) + { + $maxlevel = 10000; # Arbitrary recursion limit + } +else + { + $maxlevel = 1; + } +delete $options{recurse}; + +# So we can lock the directories behind us +$donefile_name = $options{'rcs-dirs-flat'} ? "" : "RCS/"; +$errorfile_name = $donefile_name . "#conv.errors"; +$donefile_name .= "#conv.done"; + + + +# +# start the whole thing and drop the return code on exit +# +push @ARGV, "." unless (@ARGV); +while ($_ = shift) + { + # reset the recursion level (corresponds to directory depth) + # level 0 is the first directory we enter... + $curlevel = -1; + my ($e, $w) = execdir($_); + $errors += $e; + $warnings += $w; + } + + + +print STDERR "$0: " . ($errors ? "Aborted" : "Done") . ".\n"; +print STDERR "$0: "; +print STDERR ($errors ? $errors : "No") . " error" . (($errors != 1) ? "s" : ""); +print STDERR ", " . ($warnings ? $warnings : "no") . " warning" . (($warnings != 1) ? "s" : "") + if ($options{warnings}); +print STDERR ".\n"; + + + +# +# Woo-hoo! We made it! +# +exit $errors; |