1#! @PERL@ 2# --------------------------------- 3# This program is free software; you can redistribute it and/or modify 4# it under the terms of the GNU General Public License as published by 5# the Free Software Foundation; either version 2, or (at your option) 6# any later version. 7# 8# This program is distributed in the hope that it will be useful, 9# but WITHOUT ANY WARRANTY; without even the implied warranty of 10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11# GNU General Public License for more details. 12 13########################################################################### 14# FUNCTION: 15# To recursively walk through a PVCS archive directory tree (archives 16# located in VCS/ or vcs/ subdirectories) and convert them to RCS archives. 17# The RCS archive name is the PVCS workfile name with ",v" appended. 18# 19# SYNTAX: 20# pvcs_to_rcs.pl --help 21# 22# where -l indicates the operation is to be performed only in the current 23# directory (no recursion) 24# 25# EXAMPLE: 26# pvcs_to_rcs 27# Would walk through every VCS or vcs subdir starting at the current directory, 28# and produce corresponding RCS archives one level above the VCS or vcs subdir. 29# (VCS/../RCS/) 30# 31# NOTES: 32# * This script performs little error checking and logging 33# (i.e. USE AT YOUR OWN RISK) 34# * This script was last tested using ActiveState's port of Perl 5.005_02 35# (internalcut #507) under Win95, though it does compile under Perl-5.00404 36# for Solaris 2.4 run on a Solaris 2.6 system. The script crashed 37# occasionally under ActiveState's port of Perl 5.003_07 but this stopped 38# happening with the update so if you are having problems, try updating Perl. 39# Upgrading to cut #507 also seemed to coincide with a large speed 40# improvement, so try and keep up, hey? :) It was executed from MKS's 41# UNIX tools version 6.1 for Win32's sh. ALWAYS redirect your output to 42# a log!!! 43# * PVCS archives are left intact 44# * RCS archives are created in VCS/../RCS/ (or ./RCS using '-pflat') 45# * Branch labels in this script will be attached to the CVS magic 46# revision number. For branch a.b.c of a particular file, this means 47# the label will be attached to revision a.b.0.c of the converted 48# file. If you use the TrunkTip (1.*) label, be aware that it will convert 49# to RCS revision 0.1, which is useless to RCS and CVS. You'll probably 50# have to delete these. 51# * All revisions are saved with correct "metadata" (i.e. check-in date, 52# author, and log message). Any blank log message is replaced with 53# "no comment". This is because RCS does not allow non-interactive 54# check in of a new revision without a comment string. 55# * Revision numbers are incremented by 1 during the conversion (since 56# RCS does not allow revision 1.0). 57# * All converted branch numbers are even (the CVS paradigm) 58# * Version labels are assigned to the appropriate (incremented) revision 59# numbers. PVCS allows spaces and periods in version labels while RCS 60# does not. A global search and replace converts " " and "." to "_" 61# There may be other cases that ought to be added. 62# * Any working (checked-out) copies of PVCS archives 63# within the VCS/../ or vcs/../ (or possibly ./ with '-pflat') 64# will be deleted (or overwritten) depending on your mode of 65# operation since the current ./ is used in the checkout of each revision. 66# I suppose if development continues these files could be redirected to 67# temp space rather than ./ . 68# * Locks on PVCS archives should be removed (or the workfiles should be 69# checked-in) prior to conversion, although the script will blaze through 70# the archive nonetheless (But you would lose any checked out revision(s)) 71# * The -kb option is added to the RCS archive for workfiles with the following 72# extensions: .bin .out .btl .rom .a07 .lib .exe .tco .obj .t8u .c8u .o .lku 73# .a and a few others. The %bin_ext variable holds these values in regexp 74# form. 75# * the --force-binary option can be used to convert binary files which don't 76# have proper extensions, but I'd *probably* edit the %bin_ext variable. 77# * This script will abort occasionally with the error "invalid revision 78# number". This is known to happen when a revision comment has 79# /^\s*Rev/ (Perl regexp notation) in it. Fix the comment and start over. 80# (The directory locks and existance checking make this a fairly quick 81# process.) 82# * This script writes lockfiles in the RCS/ directories. It will also not 83# convert an archive if it finds the RCS Archive existant in the RCS/ 84# directory. This enables the conversion to quickly pick up where it left 85# off after errors or interrupts occur. If you interrupt the script make 86# sure you delete the last RCS Archive File which was being written. 87# If you recieve the "Invalid revision number" error, then the RCS archive 88# file for that particular PVCS file will not have been created yet. 89# * This script will not create lockfiles when processing single 90# filenames passed into the script, for hopefully obvious reasons. 91# (lockfiles lock directories - DRP) 92# * Log the output to a file. That makes it real easy to grep for errors 93# later. (grep for "^[ \t]*(rcs|ci):" and be aware I might have missed 94# a few cases (get? vcs?) !!!) *** Also note that this script will 95# exibit some harmless RCS errors. Namely, it will attempt to lock 96# branches which haven't been created yet. *** 97# * I tried to keep the error and warning info up to date, but it seems 98# to mean very little. This script almost always exits with a warning 99# or an error that didn't seem to cause any harm. I didn't trace it 100# and our imported source checks out and builds... 101# It is probably happening when trying to convert empty directories 102# or read files (possibly checked out workfiles ) which are not 103# pvcs_archives. 104# * You must use the -pflat option when processing single filenames 105# passed as arguments to the script. This is probably a bug. 106# * questions, comments, additions can be sent to info-cvs@nongnu.org 107######################################################################### 108 109 110 111# 112# USER Configurables 113# 114 115# %bin_ext should be editable from the command line. 116# 117# NOTE: Each possible binary extension is listed as a Perl regexp 118# 119# The value associated with each regexp key is used to print a log 120# message when a binary file is found. 121my %bin_ext = 122 ( 123 '\.(?i)bin$' => "Binary", 124 '\.(?i)out$' => "Default Compiler Output", 125 '\.(?i)btl$' => "", 126 '\.(?i)rom$' => "", 127 '\.(?i)a07$' => "", 128 '\.(?i)lib$' => "DOS/Wintel/Netware Compiler Library", 129 '\.(?i)lif$' => "Netware Binary File", 130 '\.(?i)exe$' => "DOS/Wintel Executable", 131 '\.(?i)tco$' => "", 132 '\.(?i)obj$' => "DOS/Wintel Compiler Object", 133 '\.(?i)res$' => "DOS/Wintel Resource File", 134 '\.(?i)ico$' => "DOS/Wintel Icon File", 135 '\.(?i)nlm$' => "Netware Loadable Module", 136 '\.(?i)t8u$' => "", 137 '\.(?i)c8u$' => "", 138 '\.(?i)lku$' => "", 139 '\.(?i)(bmp|gif|jpg|jpeg|jfif|tif|tiff|xbm)$' => "Image", 140 '\.(?i)dll$' => "DOS/Wintel Dynamically Linked Library", 141 '\.o$' => "UNIX Compiler Object", 142 '\.a$' => "UNIX Compiler Library", 143 '\.so(\.\d+\.\d+)?$' => "UNIX Shared Library" 144 ); 145 146# The binaries this script is dependant on: 147my @bin_dependancies = ("vcs", "vlog", "rcs", "ci"); 148 149# Where we should put temporary files 150my $tmpdir = $ENV{TMPDIR} ? $ENV{TMPDIR} : "/var/tmp"; 151 152# We use these... 153use strict; 154 155use Cwd; 156use File::Path; 157use IO::File; 158use Getopt::Long; 159 $Getopt::Long::bundling = 1; 160# $Getopt::Long::ignorecase = 0; 161 162my $usage = "\ 163usage: $0 -h 164 $0 [-lt] [-i vcsid] [-r flat|leaf] [-p flat|leaf] [-x rcs_extension] 165 [-v none|locks|exists] [options] [path...] 166"; 167 168my $help = "\ 169$usage 170 ---------------------------- ----------------------------------- 171 -h | --Help Print this text 172 173 General Settings 174 ---------------------------- ----------------------------------- 175 --Recurse Recurse through directories 176 (default) 177 -l | --NORecurse Process only . 178 --Errorfiles Save a count of conversion errors 179 in the RCS archive directory 180 (default) (unimplemented) 181 --NOErrorfiles Don't save a count of conversion 182 errors (unimplemented) 183 ( -m | --Mode ) Convert Convert PVCS files to RCS files 184 (default) 185 ( -m | --Mode ) Verify Perform verification ONLY (unimplemented) 186 ( -v | --VERIfy ) None Always replace existing RCS files 187 ( -v | --VERIfy ) LOCKS Same as exists unless a #conv.done 188 file exists in the RCS directory. 189 In that case, only the #conv.done 190 file's existance is verified for 191 that directory. (default) 192 ( -v | --VERIfy ) Exists Don't replace existing RCS files 193 ( -v | --VERIfy ) LOCKDates Verify that an existing RCS file's 194 last modification date is older 195 than that of the lockfile 196 (unimplemented) 197 ( -v | --VERIfy ) Revs Verify that the PVCS archive files 198 and RCS archive file contain the 199 same number of corresponding 200 revisions. Add only new revisions 201 to the RCS file. (unimplemented) 202 ( -v | --VERIfy ) Full Perform --verify=Revs and confirm 203 that the text of the revisions is 204 identical. Add only new revisions 205 unless an error is found. Then 206 erase the RCS archive and recreate 207 it. (unimplemented) 208 -t | --Test-binaries Use 'which' to check \$PATH for 209 the binaries required by this 210 script (default) 211 --NOTest-binaries Don't check for binaries 212 --VERBose Enable verbose output 213 --NOVerbose Disable verbose output (default) 214 -w | --Warnings Print warning messages (default) 215 --NOWarnings Don't print warning messages 216 217 RCS Settings 218 ---------------------------- ----------------------------------- 219 ( -r | --RCS-Dirs ) leaf RCS files stored in ./RCS (default) 220 ( -r | --RCS-Dirs ) flat RCS files stored in . 221 (unimplemented) 222 ( -x | --RCS-Extension ) Set RCS file extension 223 (default = ',v') 224 --Force-binary Pass '-kb' to 'rcs -i' regardless of 225 the file extension 226 --NOForce-binary Only use '-kb' when the file has 227 a binary extension (default) 228 --Cvs-branch-labels Use CVS magic branch revision 229 numbers when attaching branch 230 labels (default) 231 --NOCvs-branch-labels Attach branch labels to RCS branch 232 revision numbers (unimplemented) 233 234 PVCS Settings 235 ---------------------------- ----------------------------------- 236 ( -p | --Pvcs-dirs ) leaf PVCS files expected in ./VCS 237 (default) 238 ( -p | --Pvcs-dirs ) flat PVCS files expected in . 239 ( -i | --VCsid ) vcsid Use vcsid instead of \$VCSID 240 241 -------------------------------------------------------------------------- 242 The optional path argument should contain the name of a file or directory 243 to convert. If not given, it will default to '.'. 244 -------------------------------------------------------------------------- 245"; 246 247 248 249# 250# Initialize globals 251# 252 253my ($errors, $warnings) = (0, 0); 254my ($curlevel, $maxlevel); 255my ($rcs_base_command, $ci_base_command); 256my ($donefile_name, $errorfile_name); 257 258# set up the default options 259my %options = ( 260 recurse => 1, 261 mode => "convert", 262 errorfiles => 1, 263 'rcs-dirs' => "leaf", 264 'rcs-extension' => ",v", 265 'force-binary' => 0, 266 'cvs-branch-labels' => 1, 267 'pvcs-dirs' => "leaf", 268 verify => "locks", 269 'test-binaries' => 1, 270 vcsid => $ENV{VCSID} || "", 271 verbose => 0, 272 debug => 0, 273 warnings => 1 274 ); 275 276 277 278# This is untested except under Solaris 2.4 or 2.6 and 279# may not be portable 280# 281# I think the readline lib or some such has an interface 282# which may enable this now. The perl installer sure looks 283# like it's testing this kind of thing, anyhow. 284sub hit_any_key 285 { 286 STDOUT->autoflush; 287 system "stty", "-icanon", "min", "1"; 288 289 print "Hit any key to continue..."; 290 getc; 291 292 system "stty", "icanon", "min", "0"; 293 STDOUT->autoflush (0); 294 295 print "\nI always wondered where that key was...\n"; 296 } 297 298 299 300# print the usage 301sub print_usage 302 { 303 my $fh = shift; 304 unless (ref $fh) 305 { 306 my $fdn = $fh ? $fh : "STDERR"; 307 $fh = new IO::File; 308 $fh->fdopen ($fdn, "w"); 309 } 310 311 $fh->print ($usage); 312 } 313 314# print the help 315sub print_help 316 { 317 my $fh = shift; 318 unless (ref $fh) 319 { 320 my $fdn = $fh ? $fh : "STDOUT"; 321 $fh = new IO::File; 322 $fh->fdopen ($fdn, "w"); 323 } 324 325 $fh->print ($help); 326 } 327 328# print the help and exit $_[0] || 0 329sub exit_help 330 { 331 print_help; 332 exit shift || 0; 333 } 334 335sub error_count 336 { 337 my $type = shift or die "$0: error - error_count usage: error_count type [, ref] [, LIST]\n"; 338 my $error_count_ref; 339 my $outstring; 340 341 if (ref ($_[0]) && ref ($_[0]) == "SCALAR") 342 { 343 $error_count_ref = shift; 344 } 345 else 346 { 347 $error_count_ref = \$errors; 348 } 349 $$error_count_ref++; 350 351 push @_, "something wrong.\n" unless ( @_ > 0 ); 352 353 $outstring = sprintf "$0: $type - " . join ("", @_); 354 $outstring .= sprintf " - $!\n" unless ($outstring =~ /\n$/); 355 356 print STDERR $outstring; 357 358 if ($options{errorfiles}) 359 { 360 my $fh = new IO::File ">>$errorfile_name" or new IO::File ">$errorfile_name"; 361 if ($fh) 362 { 363 $fh->print ($$error_count_ref . "\n"); 364 $fh->print ($outstring); 365 $fh->close; 366 } 367 else 368 { 369 my $cd = cwd; 370 print STDERR "$0: error - failed to open errorfile $cd/$errorfile_name - $!\n" 371 if ($options{debug}); 372 } 373 } 374 375 return $$error_count_ref; 376 } 377 378 379 380# the main procedure that is run once in each directory 381sub execdir 382 { 383 my $dir = shift; 384 my ($errors, $warnings) = (0, 0); # We return these error counters 385 my $old_dir = cwd; 386 387 local ($_, @_); 388 389 my $i; # Generic counter 390 my ($pvcsarchive, $workfile, $rcsarchive); # .??v, checked out file, and ,v files, 391 # respectively 392 my ($rev_count, $first_vl, $last_vl, $description, 393 $rev_index, @rev_num, %checked_in, %author, 394 $relative_comment_index, @comment_string, 395 %comment); 396 my ($num_version_labels, $label_index, @label_revision, $label, 397 @new_label, $rcs_rev); 398 my ($revision, %rcs_rev_num); 399 my ($get_output, $rcs_output, $ci_output, $mv_output); 400 my ($ci_command, $rcs_command, $wtr); 401 my @hits; 402 my ($num_fields); 403 my $skipdirlock; # if true, don't write conv.out 404 # used only for single file operations 405 # at the moment 406 my $cd; 407 408 my @filenames; 409 # We may have recieved a single file name to process... 410 if ( -d $dir ) 411 { 412 # change into the directory to be processed 413 # open the current directory for listing 414 # initialize the list of filenames 415 # and set filenames equal to directory listing 416 unless ( ( chdir $dir ) and ( opendir CURDIR, "." ) and ( @filenames = readdir CURDIR ) ) 417 { 418 $cd = cwd; 419 error_count 'error', \$errors, "skipping directory $dir from $cd"; 420 chdir $old_dir or die "Failed to restore original directory ($old_dir): ", $!, ", stopped"; 421 return ($errors, $warnings); 422 } 423 424 # clean up by closing the directory 425 closedir(CURDIR); 426 } 427 elsif ( -f $dir ) # we recieved a single file 428 { 429 push @filenames, $dir; 430 $skipdirlock = 1; 431 } 432 else 433 { 434 $cd = cwd; 435 error_count 'error', \$errors, "no such directory/file $dir from $cd\n"; 436 # chdir $old_dir or die "Failed to restore original directory ($old_dir): ", $!, ", stopped"; 437 return ($errors, $warnings); 438 } 439 440 # save the current directory 441 $cd = cwd; 442 443 # increment the global $curlevel variable 444 $curlevel = $curlevel +1; 445 446 # initialize a list for any subdirectories and any files 447 # we need to process 448 my $vcsdir = ""; 449 my (@subdirs, $fn, $file, @files, @pvcsarchives); 450 451 # print "$cd: " . join (", ", @filenames) . "\n"; 452 # hit_any_key; 453 454 (@files, @pvcsarchives) = ( (), () ); 455 # begin a for loop to execute on each filename in the list @filename 456 foreach $fn (@filenames) 457 { 458 # if the file is a directory... 459 if (-d $fn) 460 { 461 # then if we are not expecting a flat arrangement of pvcs files 462 # and we found a vcs directory add its files to @pvcsarchives 463 if (!$options{'pvcs-dirs-flat'} and $fn =~ /^vcs$/i) 464 { 465 if ($options{verify} =~ /^locks$/ ) { 466 if ( -f $donefile_name ) { 467 print "Verified existence of lockfile $cd/$donefile_name." 468 . ( ($options{mode} =~ /^convert$/) ? " Skipping directory." : "" ) 469 . "\n" if ($options{verbose}); 470 next; 471 } elsif ( $options{mode} =~ /^verify$/ ) { 472 print "No lockfile found for $cd .\n"; 473 next; 474 } 475 } 476 477 # else add the files in the vcs dir to our list of files to process 478 error_count 'warning', \$warnings, "Found two vcs dirs in directory $cd.\n" 479 if ($vcsdir and $options{warnings}); 480 481 $vcsdir = $fn; 482 483 unless ( ( opendir VCSDIR, $vcsdir ) and ( @files = readdir VCSDIR ) ) 484 { 485 error_count 'error', \$errors, "skipping directory &cd/$fn"; 486 next; 487 } 488 closedir VCSDIR; 489 490 # and so we don't need to worry about where these 491 # files came from later... 492 foreach $file (@files) 493 { 494 push @pvcsarchives, "$vcsdir/$file" if (-f "$vcsdir/$file"); 495 } 496 497 # don't want recursion here... 498 @pvcsarchives = grep !/^\.\.?$/, @pvcsarchives; 499 } 500 elsif ($fn !~ /^\.\.?$/) 501 { 502 next if (!$options{'rcs-dirs-flat'} and $fn =~ /^rcs$/i); 503 # include it in @subdir if it's not a parent directory 504 push(@subdirs,$fn); 505 } 506 } 507 # else if we are processing a flat arrangement of pvcs files... 508 elsif ($options{'pvcs-dirs-flat'} and -f $fn) 509 { 510 if ($options{verify} =~ /^locks$/) { 511 if ( -f $donefile_name) { 512 print "Found lockfile $cd/$donefile_name." 513 . ( ($options{mode} =~ /^convert$/) ? " Skipping directory." : "" ) 514 . "\n" if ($options{verbose}); 515 last; 516 } elsif ($options{mode} =~ /^verify$/) { 517 print "No lockfile found for $cd .\n"; 518 last; 519 } 520 } 521 # else add this to the list of files to process 522 push (@pvcsarchives, $fn); 523 } 524 } 525 526 # print "pvcsarchives: " . join (", ", @pvcsarchives) . "\n"; 527 # print "subdirs: " . join (", ", @subdirs) . "\n"; 528 # hit_any_key; 529 530 # for loop of subdirs 531 foreach (@subdirs) 532 { 533 # run execdir on each sub dir 534 if ($maxlevel >= $curlevel) 535 { 536 my ($e, $w) = execdir ($_); 537 $errors += $e; 538 $warnings += $w; 539 } 540 } 541 542 # Print output header for each directory 543 print("Directory: $cd\n"); 544 545 # the @files variable should already contain the list of files 546 # we should attempt to process 547 if ( @pvcsarchives && ( $options{mode} =~ /^convert$/ ) ) 548 { 549 # create an RCS directory in parent to store RCS files in 550 if ( !( $options{'rcs-dirs-flat'} or (-d "RCS") or mkpath ( "RCS" ) ) ) 551 { 552 error_count 'error', \$errors, "failed to make directory $cd/RCS - skipping directory $cd"; 553 @pvcsarchives = (); 554 # after all, we have nowhere to put them... 555 } 556 } 557 558 # begin a for loop to execute on each filename in the list @files 559 foreach $pvcsarchive (@pvcsarchives) 560 { 561 my $got_workfile = 0; 562 my $got_version_labels = 0; 563 my $got_description = 0; 564 my $got_rev_count = 0; 565 566 my $abs_file = $cd . "/" . $pvcsarchive; 567 568 print("Verifying $abs_file...\n") if ($options{verbose}); 569 570 print "vlog $pvcsarchive\n"; 571 my $vlog_output = `vlog $pvcsarchive`; 572 $_ = $vlog_output; 573 574 # Split the vcs status output into individual lines 575 my @vlog_strings = split /\n/; 576 my $num_vlog_strings = @vlog_strings; 577 $_ = $vlog_strings[0]; 578 if ( /^\s*$/ || /^vlog: warning/ ) 579 { 580 error_count 'warning', \$warnings, "$abs_file is NOT a valid PVCS archive!!!\n"; 581 next; 582 } 583 584 my $num; 585 # Collect all vlog output into appropriate variables 586 # 587 # This will ignore at the very least the /^\s*Archive:\s*/ field 588 # and maybe more. This should not be a problem. 589 for ( $num = 0; $num < $num_vlog_strings; $num++ ) 590 { 591 # print("$vlog_strings[$num]\n"); 592 $_ = $vlog_strings[$num]; 593 594 if( ( /^Workfile:\s*/ ) && (!$got_workfile ) ) 595 { 596 my $num_fields; 597 598 $got_workfile = 1; 599 # get the string to the right of the above search (with any path stripped) 600 $workfile = $'; 601 $_ = $workfile; 602 $num_fields = split /[\/\\]/; 603 if ( $num_fields > 1 ) 604 { 605 $workfile = $_[$num_fields - 1 ]; 606 } 607 608 $rcsarchive = $options{'rcs-dirs-flat'} ? "" : "RCS/"; 609 $rcsarchive .= $workfile; 610 $rcsarchive .= $options{'rcs-extension'} if ($options{'rcs-extension'}); 611 print "Workfile is $workfile\n" if ($options{debug}); 612 } 613 614 elsif ( ( /^Rev count:\s*/ ) && (!$got_rev_count ) ) 615 { 616 $got_rev_count = 1; 617 # get the string to the right of the above search 618 $rev_count = $'; 619 print "Revision count is $rev_count\n"; 620 } 621 622 elsif ( ( /^Version labels:\s*/ ) && (!$got_version_labels ) ) 623 { 624 $got_version_labels = 1; 625 $first_vl = $num+1; 626 print "Version labels start at $first_vl\n" if ($options{debug}); 627 } 628 629 elsif ( ( /^Description:\s*/ ) && (!$got_description ) ) 630 { 631 $got_description = 1; 632 $description = "\"" . $vlog_strings[$num+1] . "\""; 633 print "Description is $description\n" if ($options{debug}); 634 $last_vl = $num - 1; 635 } 636 637 elsif ( /^Rev\s+/ ) # get all the revision information at once 638 { 639 $rev_index = 0; 640 @rev_num = (); 641 while ( $rev_index < $rev_count ) 642 { 643 $_ = $vlog_strings[$num]; 644 /^\s*Rev\s+(\d+\.(\d+\.\d+\.)*\d+)$/; 645 $rev_num[$rev_index] = $1; 646 print "Found revision: $rev_num[$rev_index]\n" if ($options{debug}); 647 die "Not a valid revision ($rev_num[$rev_index]).\n" 648 if ($rev_num[$rev_index] !~ /^(\d+\.)(\d+\.\d+\.)*\d+$/); 649 650 $_ = $vlog_strings[$num+1]; 651 /^\s*Locked\s*/ and $num++; 652 653 $_ = $vlog_strings[$num+1]; 654 /^\s*Checked in:\s*/; 655 $checked_in{$rev_num[$rev_index]} = "\"" . $' . "\""; 656 print "Checked in: $checked_in{$rev_num[$rev_index]}\n" if ($options{debug}); 657 658 $_ = $vlog_strings[$num+3]; 659 /^\s*Author id:\s*/; 660 split; 661 $author{$rev_num[$rev_index]} = "\"" . $_[2] . "\""; 662 print "Author: $author{$rev_num[$rev_index]}\n" if ($options{debug}); 663 664 my @branches = (); 665 $_ = $vlog_strings[$num+1]; 666 if (/^\s*Branches:\s*/) 667 { 668 $num++; 669 @branches = split /\s+/, $'; 670 } 671 672 $relative_comment_index = 0; 673 @comment_string = (); 674 while( ( ( $num + 4 + $relative_comment_index ) < @vlog_strings) 675 && ( $vlog_strings[$num+4+$relative_comment_index] 676 !~ /^\s*Rev\s+(\d+\.(\d+\.\d+\.)*\d+)$/ ) ) 677 { 678 # We need the \n added for multi-line comments. There is no effect for 679 # single-line comments since RCS inserts the \n if it doesn't exist already 680 # print "Found commment line: $vlog_strings[$num+4+$relative_comment_index]\n" 681 # if ($options{debug}); 682 push @comment_string, $vlog_strings[$num+4+$relative_comment_index], "\n"; 683 $relative_comment_index += 1; 684 } 685 # print "Popped from comment: " . join ("", splice (@comment_string, -2)) 686 # . "\n" 687 # if ($options{debug}); 688 # Pop the "-+" or "=+" line from the comment 689 while ( (pop @comment_string) !~ /^-{35}|={35}$/ ) 690 {} 691 $comment{$rev_num[$rev_index]} = join "", @comment_string; 692 693 $num += ( 4 + $relative_comment_index ); 694 print "Got comment for $rev_num[$rev_index]\n" if ($options{debug}); 695 print "comment string: $comment{$rev_num[$rev_index]}\n" if ($options{debug}); 696 $rev_index += 1; 697 } # while ( $rev_index < $rev_count ) 698 $num -= 1; #although there should be nothing left for this to matter 699 } # Get Rev information 700 } # for ($num = 0; $num < $num_vlog_strings; $num++) 701 # hit_any_key if ($options{debug}); 702 # Create RCS revision numbers corresponding to PVCS version numbers 703 foreach $revision (@rev_num) 704 { 705 $rcs_rev_num{ $revision } = &pvcs_to_rcs_rev_number( $revision ); 706 print"PVCS revision is $revision; RCS revision is $rcs_rev_num{ $revision }\n" 707 if ($options{debug}); 708 } 709 710 # Sort the revision numbers - PVCS and RCS store them in different orders 711 # Clear @_ so we don't pass anything in by accident... 712 @_ = (); 713 @rev_num = sort revisions @rev_num; 714 print "Sorted rev_nums:\n" . join ("\n", @rev_num) . "\n" if ($options{debug}); 715 # hit_any_key; 716 717 # Loop through each version label, checking for need to relabel ' ' with '_'. 718 $num_version_labels = $last_vl - $first_vl + 1; 719 print "Version label count is $num_version_labels\n"; 720 for( $i = $first_vl; $i <= $last_vl; $i += 1 ) 721 { 722 # print("$vlog_strings[$i]\n"); 723 $label_index = $i - $first_vl; 724 $_=$vlog_strings[$i]; 725 print "Starting with string '$_'\n" if ($options{debug}); 726 split /\"/; 727 $label = $_[1]; 728 print "Got label '$label'\n" if ($options{debug}); 729 split /\s+/, $_[2]; 730 $label_revision[$label_index] = $_[2]; 731 print "Original label is $label_revision[$label_index]\n" if ($options{debug}); 732 733 # Create RCS revision numbers corresponding to PVCS version numbers by 734 # adding 1 to the revision number (# after last .) 735 $label_revision[ $label_index ] = pvcs_to_rcs_rev_number( $label_revision [ $label_index ] ); 736 # replace ' ' with '_', if needed 737 $_=$label; 738 $new_label[$label_index] = $label; 739 $new_label[$label_index] =~ s/ /_/g; 740 $new_label[$label_index] =~ s/\./_/g; 741 $new_label[$label_index] = "\"" . $new_label[$label_index] . "\""; 742 print"Label $new_label[$label_index] is for revision $label_revision[$label_index]\n" if ($options{debug}); 743 } 744 745 ########## 746 # 747 # See if the RCS archive is up to date with the PVCS archive 748 # 749 ########## 750 if ($options{verify} =~ /^locks|exists$/ and -f $rcsarchive) 751 { 752 print "Verified existence of $cd/$rcsarchive." 753 . ( ($options{mode} =~ /^convert$/) ? " Skipping." : "" ) 754 . "\n" if ($options{verbose}); 755 next; 756 } 757 758 # Create RCS archive and check in all revisions, then label. 759 my $first_time = 1; 760 foreach $revision (@rev_num) 761 { 762 # print "get -p$revision $pvcsarchive >$workfile\n"; 763 print "get -r$revision $pvcsarchive\n"; 764 # $vcs_output = `vcs -u -r$revision $pvcsarchive`; 765 # $get_output = `get -p$revision $pvcsarchive >$workfile`; 766 $get_output = `get -r$revision $pvcsarchive`; 767 768 # if this is the first time, delete the rcs archive if it exists 769 # need for $options{verify} == none 770 unlink $rcsarchive if ($first_time and $options{verify} =~ /^none$/ and -f $rcsarchive); 771 772 # Also check here whether this file ought to be "binary" 773 if ( $first_time ) 774 { 775 $rcs_command = "$rcs_base_command -i"; 776 if ( ( @hits = grep { $workfile =~ /$_/ } keys %bin_ext ) || $options{'force-binary'} ) 777 { 778 $rcs_command .= " -kb"; 779 $workfile =~ /$hits[0]/ if (@hits); 780 print "Binary attribute -kb added (" 781 . (@hits ? "file type is '$bin_ext{$hits[0]}' for extension '$&'" : "forced") 782 . ")\n"; 783 } 784 $first_time and $ci_command .= " -t-$description"; 785 786 $rcs_command .= " $workfile"; 787 788 # print and execute the rcs archive initialization command 789 print "$rcs_command\n"; 790 $wtr = new IO::File "|$rcs_command"; 791 $wtr->print ($description); 792 $wtr->print ("\n") unless ($description =~ /\n$/s); 793 $wtr->print (".\n"); 794 $wtr->close; 795 796 # $rcs_output = `$rcs_base_command -i -kb $workfile`; 797 } 798 799 # if this isn't the first time, we need to lock the rcs branch 800 # 801 # This is a little messy, but it works. Some extra locking is attempted. 802 # (This happens the first time a branch is used, at the least) 803 my $branch = ""; 804 my @branch; 805 @branch = split /\./, $rcs_rev_num{$revision}; 806 pop @branch; 807 $branch = join ".", @branch; 808 809 $rcs_output = `$rcs_base_command -l$branch $workfile` if (!$first_time); 810 811 # If an empty comment is specified, RCS will not check in the file; 812 # check for this case. (but an empty -t- description is fine - go figure!) 813 # Since RCS will pause and ask for a comment if one is not given, 814 # substitute a dummy comment "no comment". 815 $comment{$revision} =~ /^\s*$/ and $comment{$revision} = "no comment\n"; 816 817 $ci_command = $ci_base_command; 818 $ci_command .= " -f -r$rcs_rev_num{$revision} -d$checked_in{$revision}" 819 . " -w$author{$revision}"; 820 821 $ci_command .= " $workfile"; 822 823 # print and execute the ci command 824 print "$ci_command\n"; 825 $wtr = new IO::File "|$ci_command"; 826 $wtr->print ($comment{$revision}); 827 $wtr->print ("\n") unless ($comment{$revision} =~ /\n$/s); 828 $wtr->print (".\n"); 829 $wtr->close; 830 # $ci_output = `$ci_command`; 831 # $ci_output = `cat $tmpdir/ci.out`; 832 833 $first_time = 0 if ($first_time); 834 } # foreach revision 835 836 # Attach version labels 837 for( $i = $num_version_labels - 1; $i >= 0; $i -= 1 ) 838 { 839 # print "rcs -x,v -n$new_label[$i]:$label_revision[$i] $workfile\n"; 840 $rcs_output = `$rcs_base_command -n$new_label[$i]:$label_revision[$i] $workfile`; 841 print "Version label $new_label[$i] added to revision $label_revision[$i]\n"; 842 } # foreach label 843 844 # hit_any_key; 845 } # foreach pvcs archive file 846 847 # We processed a vcs directory, so if there were any files, lock it. 848 # We are guaranteed to have made the attempt at 849 # 850 # $skipdirlock gets set if a single file name was passed to this function to enable 851 # a '$0 *' operation... 852 if ( @pvcsarchives && !$skipdirlock) 853 { 854 my $fh = new IO::File ">>$donefile_name" or new IO::File ">$donefile_name"; 855 if ($fh) 856 { 857 $fh->close; 858 } 859 else 860 { 861 error_count 'error', \$errors, "couldn't create lockfile $cd/$donefile_name"; 862 } 863 } 864 865 $curlevel = $curlevel - 1; 866 867 chdir $old_dir or die "Failed to restore original directory ($old_dir): ", $!, ", stopped"; 868 return ($errors, $warnings); 869 } 870 871 872 873# 874# This function effectively does a cmp between two revision numbers 875# It is intended to be passed into Perl's sort routine. 876# 877# the pvcs_out is not implemented well. It should probably be 878# returnning $b[0] <=> $a[0] rather than $a[0] <=> $b[0] 879# 880# The @_ argument implementation was going to be used for revision 881# comparison as an aid to remove the /^\sRev/ in revision comment 882# error. The effort was fruitless at the time. 883sub revisions 884 { 885 my @a = split /\./, (defined $a) ? $a : shift; 886 my @b = split /\./, (defined $b) ? $b : shift; 887 my $function = @_ ? shift : 'rcs_in'; 888 my ($i, $ret_val); 889 890 die "Not enough arguments to revisions : a = ", join (".", @a), 891 "; b = ", join (".", @b), ", stopped" 892 unless (@a and @b); 893 894 for ($i = 0; $i < scalar( @a ) && $i < scalar( @b ); $i++) 895 { 896 $a[$i] == $b[$i] or return ($a[$i] <=> $b[$i]); 897 } 898 899 return 0 if (scalar (@a) == scalar (@b)); 900 901 if ($function eq 'rcs_in') 902 { 903 return (($i == @b) || -1); 904 } 905 elsif ($function eq 'pvcs_out') 906 { 907 return (($i == @a) || -1); 908 } 909 else 910 { 911 die "error - Invalid function type passed to revisions ($function)", ", stopped"; 912 } 913 } 914 915 916 917sub pvcs_to_rcs_rev_number 918 { 919 my($input, $num_fields, @rev_string, $return_rev_num, $i); 920 921 $input = $_[0]; 922 $_ = $input; 923 $num_fields = split /\./; 924 @rev_string = @_; 925 # @rev_string[$num_fields-1] += 1; 926 927 for( $i = 1; $i < $num_fields; $i += 1 ) 928 { 929 if ( $i % 2 ) 930 { 931 # DRP: 10/1 932 # RCS does not allow revision zero 933 $rev_string[ $i ] += 1; 934 } 935 elsif ( $i ) 936 { 937 # DRP: 10/1 938 # Branches must have even references for compatibility 939 # with CVS's magic branch numbers. 940 # (Indexes 2, 4, 6...) 941 $rev_string[ $i ] *= 2; 942 } 943 } 944 945 # If this is a branch revision # (PVCS: a.b.c.*) then we want the CVS 946 # revision # instead. It's okay to do this conversion here since we 947 # never commit to branches. We'll only get a PVCS revision # in that 948 # form when looking through the revision labels. 949 if ($input =~ /\*$/) 950 { 951 pop @rev_string; 952 push @rev_string, splice (@rev_string, -1, 1, "0"); 953 } 954 955 $return_rev_num = join ".", @rev_string; 956 return $return_rev_num; 957 } 958 959 960 961 962 963### 964### 965### 966### 967### 968### MAIN program: checks to see if there are command line parameters 969### 970### 971### 972### 973### 974 975 976 977 978 979# and read the options 980die $usage unless GetOptions (\%options, "h|help" => \&exit_help, 981 "recurse!", "mode|m=s", "errorfiles!", "l", "rcs-dirs|rcs-directories|r=s", 982 "pvcs-dirs|pvcs-directories|p=s", "test-binaries|t!", 983 "rcs-extension=s", "verify|v=s", "vcsid|i=s", "verbose!", "debug!", 984 "force-binary!", "cvs-branch-labels!", "warnings|w!"); 985 986 987 988# 989# Special processing for -l !^#%$^@#$%#$ 990# 991# At the moment, -l overrides --recurse, regardless of the order the 992# options were passed in 993# 994$options{recurse} = 0 if defined $options{l}; 995delete $options{l}; 996 997 998 999# Make sure we got acceptable values for rcs-dirs and pvcs-dirs 1000my @hits = grep /^$options{'rcs-dirs'}/i, ("leaf", "flat"); 1001@hits == 1 or die 1002 "$0: $options{'rcs-dirs'} invalid argument to --rcs-dirs or ambiguous\n" 1003 . " abbreviation.\n" 1004 . " Must be one of: 'leaf' or 'flat'.\n" 1005 . $usage; 1006$options{'rcs-dirs'} = $hits[0]; 1007$options{'rcs-dirs-flat'} = ($options{'rcs-dirs'} =~ /flat/); 1008delete $options{'rcs-dirs'}; 1009 1010@hits = grep /^$options{'pvcs-dirs'}/i, ("leaf", "flat"); 1011@hits == 1 or die 1012 "$0: $options{'pvcs-dirs'} invalid argument to --pvcs-dirs or ambiguous\n" 1013 . " abbreviation.\n" 1014 . " Must be one of: 'leaf' or 'flat'.\n" 1015 . $usage; 1016$options{'pvcs-dirs'} = $hits[0]; 1017$options{'pvcs-dirs-flat'} = ($options{'pvcs-dirs'} =~ /flat/); 1018delete $options{'pvcs-dirs'}; 1019 1020# and for verify 1021@hits = grep /^$options{verify}/i, ("none", "locks", "exists", "lockdates", "revs", "full"); 1022@hits == 1 or die 1023 "$0: $options{verify} invalid argument to --verify or ambiguous\n" 1024 . " abbreviation.\n" 1025 . " Must be one of: 'none', 'locks', 'exists', 'lockdates', 'revs',\n" 1026 . " or 'full'.\n" 1027 . $usage; 1028$options{verify} = $hits[0]; 1029$options{verify} =~ /^none|locks|exists$/ or die 1030 "$0: --verify=$options{verify} unimplemented.\n" 1031 . $usage; 1032 1033# and mode 1034@hits = grep /^$options{mode}/i, ("convert", "verify"); 1035@hits == 1 or die 1036 "$0: $options{mode} invalid argument to --mode or ambiguous abbreviation.\n" 1037 . " Must be 'convert' or 'verify'.\n" 1038 . $usage; 1039$options{mode} = $hits[0]; 1040 1041$options{'cvs-branch-labels'} or die 1042 "$0: RCS Branch Labels unimplemented.\n" 1043 . $usage; 1044 1045# export VCSID into th environment for ourselves and our children 1046$ENV{VCSID} = $options{vcsid}; 1047 1048 1049 1050# 1051# Verify we have all the binary executables we need to run this script 1052# 1053# Allowed this feature to be disabled in case which is missing or we are 1054# running on a system which does not return error codes properly (e.g. WIN95) 1055# 1056# -- i.e. I don't feel like grepping output yet. -- 1057# 1058my @missing_binaries = (); 1059if ($options{'test-binaries'}) 1060 { 1061 foreach (@bin_dependancies) 1062 { 1063 if (system "which", $_) 1064 { 1065 push @missing_binaries, $_; 1066 } 1067 } 1068 1069 if (scalar @missing_binaries) 1070 { 1071 print STDERR "The following executables were not found in your PATH: " 1072 . join ( " ", @missing_binaries ) 1073 . "\n" 1074 . "You must correct this before continuing.\n"; 1075 exit 1; 1076 } 1077 } 1078delete $options{'test-binaries'}; 1079 1080 1081 1082# 1083# set up our base archive manipulation commands 1084# 1085 1086# set up our rcs_command mods 1087$rcs_base_command = "rcs"; 1088$rcs_base_command .= " -x$options{'rcs-extension'}" if ($options{'rcs-extension'}); 1089 1090# set up our rcs_command mods 1091$ci_base_command = "ci"; 1092$ci_base_command .= " -x$options{'rcs-extension'}" if ($options{'rcs-extension'}); 1093 1094 1095 1096# 1097# So our logs fill in a manner we can monitor with 'tail -f' fairly easily: 1098# 1099STDERR->autoflush (1); 1100STDOUT->autoflush (1); 1101 1102 1103 1104# Initialize the globals we use to keep track of recursion 1105if ($options{recurse}) 1106 { 1107 $maxlevel = 10000; # Arbitrary recursion limit 1108 } 1109else 1110 { 1111 $maxlevel = 1; 1112 } 1113delete $options{recurse}; 1114 1115# So we can lock the directories behind us 1116$donefile_name = $options{'rcs-dirs-flat'} ? "" : "RCS/"; 1117$errorfile_name = $donefile_name . "#conv.errors"; 1118$donefile_name .= "#conv.done"; 1119 1120 1121 1122# 1123# start the whole thing and drop the return code on exit 1124# 1125push (@ARGV, ".") unless (@ARGV); 1126while ($_ = shift) 1127 { 1128 # reset the recursion level (corresponds to directory depth) 1129 # level 0 is the first directory we enter... 1130 $curlevel = -1; 1131 my ($e, $w) = execdir($_); 1132 $errors += $e; 1133 $warnings += $w; 1134 } 1135 1136 1137 1138print STDERR "$0: " . ($errors ? "Aborted" : "Done") . ".\n"; 1139print STDERR "$0: "; 1140print STDERR ($errors ? $errors : "No") . " error" . (($errors != 1) ? "s" : ""); 1141print STDERR ", " . ($warnings ? $warnings : "no") . " warning" . (($warnings != 1) ? "s" : "") 1142 if ($options{warnings}); 1143print STDERR ".\n"; 1144 1145 1146 1147# 1148# Woo-hoo! We made it! 1149# 1150exit $errors; 1151