1 1.1 christos #! @PERL@ -w 2 1.1 christos ######################################################################## 3 1.1 christos # 4 1.1 christos # Copyright (c) 2000, 2001 by Donald Sharp <sharpd (at] cisco.com> 5 1.1 christos # All Rights Reserved 6 1.1 christos # 7 1.1 christos # Some portions Copyright (c) 2002, 2003 by 8 1.1 christos # Derek R. Price <mailto:derek (at] ximbiot.com> 9 1.1 christos # & Ximbiot <http://ximbiot.com>. 10 1.1 christos # All rights reserved. 11 1.1 christos # 12 1.1 christos # Permission is granted to copy and/or distribute this file, with or 13 1.1 christos # without modifications, provided this notice is preserved. 14 1.1 christos # 15 1.1 christos # This program is free software; you can redistribute it and/or modify 16 1.1 christos # it under the terms of the GNU General Public License as published by 17 1.1 christos # the Free Software Foundation; either version 2, or (at your option) 18 1.1 christos # any later version. 19 1.1 christos # 20 1.1 christos # This program is distributed in the hope that it will be useful, 21 1.1 christos # but WITHOUT ANY WARRANTY; without even the implied warranty of 22 1.1 christos # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 1.1 christos # GNU General Public License for more details. 24 1.1 christos # 25 1.1 christos ######################################################################## 26 1.1 christos 27 1.1 christos =head1 validate_repo.pl 28 1.1 christos 29 1.1 christos Script to check the integrity of the Repository. 30 1.1 christos 31 1.1 christos =head1 SYNOPSIS 32 1.1 christos 33 1.1 christos perldoc validate_repo.pl 34 1.1 christos validate_repo.pl --help [--verbose!] 35 1.1 christos validate_repo.pl [--verbose!] [--cvsroot=CVSROOT] [--exec=SCRIPT]... 36 1.1 christos [--all-revisions!] [module]... 37 1.1 christos 38 1.1 christos =head1 DESCRIPTION 39 1.1 christos 40 1.1 christos This script will search through a repository and determine if any of the 41 1.1 christos files in it are corrupted. 42 1.1 christos 43 1.1 christos This is normally accomplished by checking out all I<important> revisions, where 44 1.1 christos I<important> revisions are defined as the smallest set which, when checked out, 45 1.1 christos will cause each and every revision's integrity to be verified. This resolves 46 1.1 christos to the most recent revision on each branch and the first and last revisions on 47 1.1 christos the trunk. 48 1.1 christos 49 1.1 christos Please do not run this script inside of the repository itself. This will cause 50 1.1 christos it too fail. 51 1.1 christos 52 1.1 christos =head1 OPTIONS 53 1.1 christos 54 1.1 christos =over 55 1.1 christos 56 1.1 christos =item C<--help> 57 1.1 christos 58 1.1 christos Print this very help text (or, with C<--verbose>, act like 59 1.1 christos C<perldoc validate_repo.pl>). 60 1.1 christos 61 1.1 christos =item C<-a> or C<--all-revisions> 62 1.1 christos 63 1.1 christos Check out each and every revision rather than just the I<important> ones. 64 1.1 christos This flag is useful with C<--exec> to execute the C<SCRIPT> (from C<--exec> 65 1.1 christos below) on a checked out copy of each and every revision. 66 1.1 christos 67 1.1 christos =item C<-d> or C<--cvsroot=CVSROOT> 68 1.1 christos 69 1.1 christos Use repository specified by C<CVSROOT>. Defaults to the contents of the 70 1.1 christos F<./CVS/Root> file when it exists and is readable, then to the contents of the 71 1.1 christos C<$CVSROOT> environment variable when it is set and non-empty. 72 1.1 christos 73 1.1 christos =item C<-e> or C<--exec=SCRIPT> 74 1.1 christos 75 1.1 christos Execute (as from command prompt) C<SCRIPT> if it exists as a file, is readable, 76 1.1 christos and is executable, or evaluate (as a perl script) C<SCRIPT> for a checked out 77 1.1 christos copy of each I<important> revision of each RCS archive in CVSROOT. Executed 78 1.1 christos scripts are passed C<CVSROOT FILE REVISION FNO>, where C<CVSROOT> is what 79 1.1 christos you'd think, C<FILE> is the path to the file relative to C<CVSROOT> and 80 1.1 christos suitable for use as an argument to C<cvs co>, C<cvs rlog>, and so on, 81 1.1 christos C<REVISION> is the revision of the checked out file, and C<FNO> is the file 82 1.1 christos number of the open, read-only file descriptor containing the checked out 83 1.1 christos contents of revision C<REVISION> of C<FILE>. An evaluated C<SCRIPT> will find 84 1.1 christos the same four arguments in the same order in C<@_>, except that C<FNO> will be 85 1.1 christos an open file handle. 86 1.1 christos 87 1.1 christos With C<--all-revisions>, execute or evaluate C<SCRIPT> for a checked out 88 1.1 christos version of each revsion in the RCS archive. 89 1.1 christos 90 1.1 christos =item C<-v> or C<--verbose> 91 1.1 christos 92 1.1 christos Print verbose debugging information (or, when specified with C<--help>, act 93 1.1 christos like C<perldoc validate_repo.pl>). 94 1.1 christos 95 1.1 christos =head1 ARGUMENTS 96 1.1 christos 97 1.1 christos =over 98 1.1 christos 99 1.1 christos =item C<modules> 100 1.1 christos 101 1.1 christos The module in the repository to examine. Defaults to the contents of the 102 1.1 christos F<./CVS/Repository> file when it exists and is readable, then to F<.> 103 1.1 christos (all modules). 104 1.1 christos 105 1.1 christos =head1 EXAMPLES 106 1.1 christos 107 1.1 christos setenv CVSROOT /release/111/cvs 108 1.1 christos validate_repo.pl 109 1.1 christos 110 1.1 christos 111 1.1 christos validate_repo.pl -d /another/cvsroot --verbose --exec ' 112 1.1 christos system "grep \"This string means Im a bad, bad file!\" <&" 113 1.1 christos . fileno( $_[3] ) 114 1.1 christos . ">/dev/null" 115 1.1 christos or die "Revision $_[2] of $_[0]/$_[1],v is bad, bad, bad!"' 116 1.1 christos 117 1.1 christos =head1 SEE ALSO 118 1.1 christos 119 1.1 christos None. 120 1.1 christos 121 1.1 christos =cut 122 1.1 christos 123 1.1 christos ###################################################################### 124 1.1 christos # MODULES # 125 1.1 christos ###################################################################### 126 1.1 christos use strict; 127 1.1 christos 128 1.1 christos use Fcntl qw( F_GETFD F_SETFD ); 129 1.1 christos use File::Find; 130 1.1 christos use File::Basename; 131 1.1 christos use File::Path; 132 1.1 christos use File::Spec; 133 1.1 christos use Getopt::Long; 134 1.1 christos use IO::File; 135 1.1 christos use Pod::Usage; 136 1.1 christos 137 1.1 christos ###################################################################### 138 1.1 christos # GLOBALS # 139 1.1 christos ###################################################################### 140 1.1 christos 141 1.1 christos use vars qw( 142 1.1 christos $all_revisions 143 1.1 christos $cvsroot 144 1.1 christos @extra_files 145 1.1 christos @ignore_files 146 1.1 christos $ignored_files 147 1.1 christos @invalid_revs 148 1.1 christos @list_of_broken_files 149 1.1 christos @scripts 150 1.1 christos $total_files 151 1.1 christos $total_interesting_revisions 152 1.1 christos $total_revisions 153 1.1 christos $verbose 154 1.1 christos ); 155 1.1 christos 156 1.1 christos 157 1.1 christos 158 1.1 christos ###################################################################### 159 1.1 christos # SUBROUTINES # 160 1.1 christos ###################################################################### 161 1.1 christos 162 1.1 christos ###################################################################### 163 1.1 christos # 164 1.1 christos # NAME : 165 1.1 christos # main 166 1.1 christos # 167 1.1 christos # PURPOSE : 168 1.1 christos # To search the repository for broken files 169 1.1 christos # 170 1.1 christos # PARAMETERS : 171 1.1 christos # NONE 172 1.1 christos # 173 1.1 christos # GLOBALS : 174 1.1 christos # $cvsroot - The CVS repository to search through. 175 1.1 christos # $ENV{ CVSROOT } - The default CVS repository to search through. 176 1.1 christos # @list_of_broken_files - The list of files that need to 177 1.1 christos # be fixed. 178 1.1 christos # $verbose - is verbose mode on? 179 1.1 christos # @scripts - scripts to run on checked out files. 180 1.1 christos # $total_revisions - The number of revisions considered 181 1.1 christos # $total_interesting_revisions - The number of revisions used 182 1.1 christos # $total_files - The total number of files looked at. 183 1.1 christos # 184 1.1 christos # RETURNS : 185 1.1 christos # A list of broken files 186 1.1 christos # 187 1.1 christos # COMMENTS : 188 1.1 christos # Do not run this script inside the repository. Choose 189 1.1 christos # a nice safe spot( like /tmp ) outside of the repository. 190 1.1 christos # 191 1.1 christos ###################################################################### 192 1.1 christos sub main 193 1.1 christos { 194 1.1 christos my $help; 195 1.1 christos 196 1.1 christos $ignored_files = 0; 197 1.1 christos $total_files = 0; 198 1.1 christos $total_interesting_revisions = 0; 199 1.1 christos $total_revisions = 0; 200 1.1 christos 201 1.1 christos Getopt::Long::Configure( "bundling" ); 202 1.1 christos unless( GetOptions( 203 1.1 christos 'all-revisions|a!' => \$all_revisions, 204 1.1 christos 'cvsroot|d=s' => \$cvsroot, 205 1.1 christos 'exec|e=s' => \@scripts, 206 1.1 christos 'help|h|?!' => \$help, 207 1.1 christos 'verbose|v!' => \$verbose 208 1.1 christos ) 209 1.1 christos ) 210 1.1 christos { 211 1.1 christos pod2usage( 2 ); 212 1.1 christos exit 2; 213 1.1 christos } 214 1.1 christos 215 1.1 christos pod2usage( -exitval => 2, 216 1.1 christos -verbose => $verbose ? 2 : 1, 217 1.1 christos -output => \*STDOUT ) 218 1.1 christos if $help; 219 1.1 christos 220 1.1 christos verbose( "Verbose Mode Turned On\n" ); 221 1.1 christos 222 1.1 christos if( !$cvsroot && -f "CVS/Root" && -r "CVS/Root" ) 223 1.1 christos { 224 1.1 christos my $file = new IO::File "< CVS/Root"; 225 1.1 christos $cvsroot = $file->getline; 226 1.1 christos chomp $cvsroot; 227 1.1 christos } 228 1.1 christos $cvsroot = $ENV{'CVSROOT'} unless $cvsroot; 229 1.1 christos pod2usage( "error: Must set CVSROOT" ) unless $cvsroot; 230 1.1 christos 231 1.1 christos if( $cvsroot =~ /^:\w+:/ && $cvsroot !~ /^:local:/ 232 1.1 christos || $cvsroot =~ /@/ ) 233 1.1 christos { 234 1.1 christos print STDERR "CVSROOT must be :local:\n"; 235 1.1 christos exit 2; 236 1.1 christos } 237 1.1 christos 238 1.1 christos for (@scripts) 239 1.1 christos { 240 1.1 christos $_ = File::Spec->rel2abs( $_ ) unless /\n/ || !-x $_; 241 1.1 christos } 242 1.1 christos 243 1.1 christos 244 1.1 christos if( !scalar( @ARGV ) && -f "CVS/Repository" && -r "CVS/Repository" ) 245 1.1 christos { 246 1.1 christos my $file = new IO::File "< CVS/Repository"; 247 1.1 christos my $module = $file->getline; 248 1.1 christos chomp $module; 249 1.1 christos push @ARGV, $module; 250 1.1 christos } 251 1.1 christos 252 1.1 christos push @ARGV, "." unless( scalar @ARGV ); 253 1.1 christos 254 1.1 christos foreach my $directory_to_look_at ( @ARGV ) 255 1.1 christos { 256 1.1 christos $directory_to_look_at = File::Spec->catfile( $cvsroot, 257 1.1 christos $directory_to_look_at ); 258 1.1 christos 259 1.1 christos my $sym_count = 0; 260 1.1 christos while( -l $directory_to_look_at ) 261 1.1 christos { 262 1.1 christos $directory_to_look_at = readlink( $directory_to_look_at ); 263 1.1 christos $sym_count += 1; 264 1.1 christos die( "Encountered too many symlinks for CVSROOT ($cvsroot)\n" ) 265 1.1 christos if( $sym_count > 5 ); 266 1.1 christos } 267 1.1 christos 268 1.1 christos # Remove indirections. 269 1.1 christos $directory_to_look_at =~ s#(/+.)*$##o; 270 1.1 christos 271 1.1 christos verbose( "Processing: $directory_to_look_at\n" ); 272 1.1 christos @ignore_files = get_ignore_files_from_cvsroot( $directory_to_look_at ); 273 1.1 christos find( \&process_file, $directory_to_look_at ); 274 1.1 christos } 275 1.1 christos 276 1.1 christos print "List of corrupted files\n" if @list_of_broken_files; 277 1.1 christos foreach my $broken ( @list_of_broken_files ) 278 1.1 christos { 279 1.1 christos print( "**** File: $broken\n" ); 280 1.1 christos } 281 1.1 christos 282 1.1 christos print "List of Files containing invalid revisions:\n" 283 1.1 christos if @invalid_revs; 284 1.1 christos foreach ( @invalid_revs ) 285 1.1 christos { 286 1.1 christos print( "**** File: ($_->{'rev'}) $_->{'file'}\n" ); 287 1.1 christos } 288 1.1 christos 289 1.1 christos print "List of Files That Don't belong in Repository:\n" 290 1.1 christos if @extra_files; 291 1.1 christos foreach my $extra ( @extra_files ) 292 1.1 christos { 293 1.1 christos print( "**** File: $extra\n" ); 294 1.1 christos } 295 1.1 christos print( "Total Files: $total_files Corrupted files: " 296 1.1 christos . scalar( @list_of_broken_files ) 297 1.1 christos . " Invalid revs: " 298 1.1 christos . scalar( @invalid_revs ) 299 1.1 christos . " Extra files: " 300 1.1 christos . scalar( @extra_files ) 301 1.1 christos . " Ignored Files: $ignored_files\n" ); 302 1.1 christos print( "Total Revisions: $total_revisions Interesting Revisions: $total_interesting_revisions\n" ); 303 1.1 christos } 304 1.1 christos 305 1.1 christos 306 1.1 christos 307 1.1 christos sub verbose 308 1.1 christos { 309 1.1 christos print STDERR @_ if $verbose; 310 1.1 christos } 311 1.1 christos 312 1.1 christos 313 1.1 christos 314 1.1 christos ###################################################################### 315 1.1 christos # 316 1.1 christos # NAME : 317 1.1 christos # process_file 318 1.1 christos # 319 1.1 christos # PURPOSE : 320 1.1 christos # This function is called by the find function, its purpose 321 1.1 christos # is to decide if it is important to look at a file or not. When 322 1.1 christos # a file is important, we log it or call &look_at_cvs_file on it. 323 1.1 christos # 324 1.1 christos # ALGORITHM 325 1.1 christos # 1) If the file is an archive file, we call &look_at_cvs_file on 326 1.1 christos # it. 327 1.1 christos # 2) Else, if the file is not in the ignore list, we store its name 328 1.1 christos # for later. 329 1.1 christos # 330 1.1 christos # PARAMETERS : 331 1.1 christos # NONE 332 1.1 christos # 333 1.1 christos # GLOBALS : 334 1.1 christos # $cvsroot - The CVS repository to search through 335 1.1 christos # @ignore_files - File patterns we can afford to ignore. 336 1.1 christos # $File::Find::name - The absolute path of the file being examined. 337 1.1 christos # 338 1.1 christos # RETURNS : 339 1.1 christos # NONE 340 1.1 christos # 341 1.1 christos # COMMENTS : 342 1.1 christos # NONE 343 1.1 christos # 344 1.1 christos ###################################################################### 345 1.1 christos sub process_file 346 1.1 christos { 347 1.1 christos if( ! -d $File::Find::name ) 348 1.1 christos { 349 1.1 christos my $path = $File::Find::name; 350 1.1 christos $path =~ s#^$cvsroot/(\./)*##; 351 1.1 christos $total_files++; 352 1.1 christos 353 1.1 christos verbose( "Examining `$path'\n" ); 354 1.1 christos 355 1.1 christos if( $path =~ s/,v$// ) 356 1.1 christos { 357 1.1 christos look_at_cvs_file( $path ); 358 1.1 christos } 359 1.1 christos elsif( !grep { $path =~ $_ } @ignore_files ) 360 1.1 christos { 361 1.1 christos push @extra_files, $path; 362 1.1 christos verbose( "Adding unrecognized file `$path' to corrupted list.\n" ); 363 1.1 christos } 364 1.1 christos else 365 1.1 christos { 366 1.1 christos $ignored_files++; 367 1.1 christos verbose( "Ignoring `$path'\n" ); 368 1.1 christos } 369 1.1 christos } 370 1.1 christos } 371 1.1 christos 372 1.1 christos ###################################################################### 373 1.1 christos # 374 1.1 christos # NAME : 375 1.1 christos # look_at_cvs_file 376 1.1 christos # 377 1.1 christos # PURPOSE : 378 1.1 christos # To decide if a file is broken or not. The algorithm is: 379 1.1 christos # a) Get the revision history for the file. 380 1.1 christos # - If that fails the file is broken, save the fact 381 1.1 christos # and continue processing other files. 382 1.1 christos # - If that succeeds we have a list of revisions. 383 1.1 christos # b) For each revision call &check_revision on the file. 384 1.1 christos # - If that fails the file is broken, save the fact 385 1.1 christos # and continue processing other files. 386 1.1 christos # c) Continue on 387 1.1 christos # 388 1.1 christos # PARAMETERS : 389 1.1 christos # $file - The path of the file to look at, relative to $cvsroot and 390 1.1 christos # suitable for use as an argument to `cvs co', `cvs rlog', and 391 1.1 christos # the rest of CVS's r* commands. 392 1.1 christos # 393 1.1 christos # GLOBALS : 394 1.1 christos # NONE 395 1.1 christos # 396 1.1 christos # RETURNS : 397 1.1 christos # NONE 398 1.1 christos # 399 1.1 christos # COMMENTS : 400 1.1 christos # We have to handle Attic files in a special manner. 401 1.1 christos # Basically remove the Attic from the string if it 402 1.1 christos # exists at the end of the $path variable. 403 1.1 christos # 404 1.1 christos ###################################################################### 405 1.1 christos sub look_at_cvs_file 406 1.1 christos { 407 1.1 christos my( $file ) = @_; 408 1.1 christos my( $name, $path ) = fileparse( $file ); 409 1.1 christos 410 1.1 christos $file = $path . $name if $path =~ s#Attic/$##; 411 1.1 christos 412 1.1 christos my( $finfo, $rinfo ) = get_history( $file ); 413 1.1 christos 414 1.1 christos unless( defined $rinfo ) 415 1.1 christos { 416 1.1 christos verbose( "\t`$file' is corrupted. It was determined to contain no\n" 417 1.1 christos . "\trevisions via a cvs rlog command\n" ); 418 1.1 christos push( @list_of_broken_files, $file ); 419 1.1 christos return(); 420 1.1 christos } 421 1.1 christos 422 1.1 christos my @int_revisions = 423 1.1 christos $all_revisions ? keys %$rinfo 424 1.1 christos : find_interesting_revisions( keys %$rinfo ); 425 1.1 christos 426 1.1 christos foreach my $revision ( @int_revisions ) 427 1.1 christos { 428 1.1 christos verbose( "\t\tLooking at Revision: $revision\n" ); 429 1.1 christos if( !check_revision( $file, $revision, $finfo, $rinfo ) ) 430 1.1 christos { 431 1.1 christos verbose( "\t$file is corrupted in revision: $revision\n" ); 432 1.1 christos push( @list_of_broken_files, $file ); 433 1.1 christos return(); 434 1.1 christos } 435 1.1 christos } 436 1.1 christos } 437 1.1 christos 438 1.1 christos ###################################################################### 439 1.1 christos # 440 1.1 christos # NAME : 441 1.1 christos # get_history 442 1.1 christos # 443 1.1 christos # PURPOSE : 444 1.1 christos # To retrieve an array of revision numbers. 445 1.1 christos # 446 1.1 christos # PARAMETERS : 447 1.1 christos # $file - The file to retrieve the revision numbers for 448 1.1 christos # 449 1.1 christos # GLOBALS : 450 1.1 christos # $cvsroot - the CVSROOT we are examining 451 1.1 christos # 452 1.1 christos # RETURNS : 453 1.1 christos # On Success - A hash of revision info, indexed by revision numbers. 454 1.1 christos # On Failure - undef. 455 1.1 christos # 456 1.1 christos # COMMENTS : 457 1.1 christos # The $_ is saved off because The File::find functionality 458 1.1 christos # expects the $_ to not have been changed. 459 1.1 christos # The -N option for the rlog command means to spit out 460 1.1 christos # tags or branch names. 461 1.1 christos # 462 1.1 christos ###################################################################### 463 1.1 christos sub get_history 464 1.1 christos { 465 1.1 christos my( $file ) = @_; 466 1.1 christos $file =~ s/(["\$`\\])/\\$1/g; 467 1.1 christos my %finfo; # Info about the file. 468 1.1 christos my %rinfo; # Info about revisions in the file. 469 1.1 christos my $revision; 470 1.1 christos 471 1.1 christos my $fh = new IO::File( "cvs -d $cvsroot rlog -N \"$file\"" 472 1.1 christos . ($verbose ? "" : " 2>&1") . " |" ) 473 1.1 christos or die( "unable to run `cvs rlog', help" ); 474 1.1 christos 475 1.1 christos my $ignore = -1; 476 1.1 christos while( my $line = $fh->getline ) 477 1.1 christos { 478 1.1 christos if( $ignore == 1 ) 479 1.1 christos { 480 1.1 christos if( ( $revision ) = $line =~ /^revision (.*?)(\tlocked by: \S+;)?$/ ) 481 1.1 christos { 482 1.1 christos unless($revision =~ m/^\d+\.\d+(?:\.\d+\.\d+)*$/) 483 1.1 christos { 484 1.1 christos push @invalid_revs, { 'file' => $file, 'rev' => $revision }; 485 1.1 christos verbose( "Adding invalid revision `$revision' of file `$file' to invalid revs list.\n" ); 486 1.1 christos } 487 1.1 christos 488 1.1 christos $ignore++; 489 1.1 christos next; 490 1.1 christos } 491 1.1 christos 492 1.1 christos # We require ---- before a ^revision tag, not a revision 493 1.1 christos # after every ----. 494 1.1 christos $ignore = 0; 495 1.1 christos } 496 1.1 christos if( $ignore == 2 ) 497 1.1 christos { 498 1.1 christos if( my ( $date, $author, $state ) = 499 1.1 christos $line =~ /^date: (\S+ \S+); author: ([^;]+); state: (\S+);/ ) 500 1.1 christos { 501 1.1 christos $rinfo{$revision} = 502 1.1 christos { 503 1.1 christos 'date' => $date, 504 1.1 christos 'author' => $author, 505 1.1 christos 'state' => $state 506 1.1 christos } 507 1.1 christos } 508 1.1 christos else 509 1.1 christos { 510 1.1 christos die "Couldn't read date/author/state for revision $revision\n" 511 1.1 christos . "of $file from `cvs rlog'.\n" 512 1.1 christos . "line = $line"; 513 1.1 christos } 514 1.1 christos $ignore = 0; 515 1.1 christos next; 516 1.1 christos } 517 1.1 christos if( $ignore == -1 ) 518 1.1 christos { 519 1.1 christos # Until we find the first ---- below, we can read general file info 520 1.1 christos if( my ( $kwmode ) = 521 1.1 christos $line =~ /^keyword substitution: (\S+)$/ ) 522 1.1 christos { 523 1.1 christos $finfo{'kwmode'} = $kwmode; 524 1.1 christos next; 525 1.1 christos } 526 1.1 christos } 527 1.1 christos # rlog outputs a "----" line before the actual revision 528 1.1 christos # without this we'll pick up peoples comments if they 529 1.1 christos # happen to start with revision 530 1.1 christos if( $line =~ /^----------------------------$/ ) 531 1.1 christos { 532 1.1 christos # Catch this case when $ignore == -1 or 0 533 1.1 christos $ignore = 1; 534 1.1 christos next; 535 1.1 christos } 536 1.1 christos } 537 1.1 christos if( $verbose ) 538 1.1 christos { 539 1.1 christos for (keys %rinfo) 540 1.1 christos { 541 1.1 christos verbose( "Revision $_: " ); 542 1.1 christos verbose( join( ", ", %{$rinfo{$_}} ) ); 543 1.1 christos verbose( "\n" ); 544 1.1 christos } 545 1.1 christos } 546 1.1 christos 547 1.1 christos die "Syserr closing pipe from `cvs co': $!" 548 1.1 christos if !$fh->close && $!; 549 1.1 christos return if $?; 550 1.1 christos 551 1.1 christos return( \%finfo, %rinfo ? \%rinfo : undef ); 552 1.1 christos } 553 1.1 christos 554 1.1 christos ###################################################################### 555 1.1 christos # 556 1.1 christos # NAME : 557 1.1 christos # check_revision 558 1.1 christos # 559 1.1 christos # PURPOSE : 560 1.1 christos # Given a file and a revision number ensure that we can check out that 561 1.1 christos # file. 562 1.1 christos # 563 1.1 christos # If the user has specified any scripts (passed in as arguments to --exec 564 1.1 christos # and stored in @scripts), run them on the checked out revision. If 565 1.1 christos # executable scripts exit with a non-zero status or evaluated scripts set 566 1.1 christos # $@ (die), print $status or $@ as a warning. 567 1.1 christos # 568 1.1 christos # PARAMETERS : 569 1.1 christos # $file - The file to look at. 570 1.1 christos # $revision - The revision to look at. 571 1.1 christos # $rinfo - A reference to a hash containing information about the 572 1.1 christos # revisions in $file. 573 1.1 christos # For instance, $rinfo->{$revision}->{'date'} contains the 574 1.1 christos # date revision $revision was committed. 575 1.1 christos # 576 1.1 christos # GLOBALS : 577 1.1 christos # NONE 578 1.1 christos # 579 1.1 christos # RETURNS : 580 1.1 christos # If we can get the File - 1 581 1.1 christos # If we can not get the File - 0 582 1.1 christos # 583 1.1 christos # COMMENTS : 584 1.1 christos # cvs command line options are as followed: 585 1.1 christos # -n - Do not run any checkout program as specified by the -o 586 1.1 christos # option in the modules file 587 1.1 christos # -p - Put all output to standard out. 588 1.1 christos # -r - The revision of the file that we would like to look at. 589 1.1 christos # -ko - Get the revision exactly as checked in - do not allow 590 1.1 christos # RCS keyword substitution. 591 1.1 christos # Please note that cvs will return 0 for being able to successfully 592 1.1 christos # read the file and 1 for failure to read the file. 593 1.1 christos # 594 1.1 christos ###################################################################### 595 1.1 christos sub check_revision 596 1.1 christos { 597 1.1 christos my( $file, $revision, $finfo, $rinfo ) = @_; 598 1.1 christos $file =~ s/(["\$`\\])/\\$1/g; 599 1.1 christos 600 1.1 christos # Allow binaries to be checked out as such. Otherwise, use -ko to avoid 601 1.1 christos # replacing keywords in the files. 602 1.1 christos my $kwmode = $finfo->{'kwmode'} eq 'b' ? '' : ' -ko'; 603 1.1 christos my $command = "cvs -d $cvsroot co$kwmode -npr $revision \"$file\""; 604 1.1 christos my $ret_code; 605 1.1 christos verbose( "Executing `$command'.\n" ); 606 1.1 christos if( @scripts ) 607 1.1 christos { 608 1.1 christos my $fh = new IO::File $command . ($verbose ? "" : " 2>&1") . " |"; 609 1.1 christos fcntl( $fh, F_SETFD, 0 ) 610 1.1 christos or die "Can't clear close-on-exec flag on filehandle: $!"; 611 1.1 christos my $count; 612 1.1 christos foreach my $script (@scripts) 613 1.1 christos { 614 1.1 christos $count++; 615 1.1 christos if( $script !~ /\n/ && -x $script ) 616 1.1 christos { 617 1.1 christos # exec external script 618 1.1 christos my $status = system $script, $cvsroot, $file, $revision, 619 1.1 christos fileno( $fh ); 620 1.1 christos warn "`$script $cvsroot $file $revision " 621 1.1 christos . fileno( $fh ) 622 1.1 christos . "' exited with code $status" 623 1.1 christos if $status; 624 1.1 christos } 625 1.1 christos else 626 1.1 christos { 627 1.1 christos # eval script 628 1.1 christos @_ = ($cvsroot, $file, $revision, $fh); 629 1.1 christos eval $script; 630 1.1 christos warn "script $count ($cvsroot, $file, $revision, $fh) exited abnormally: $@" 631 1.1 christos if $@; 632 1.1 christos } 633 1.1 christos } 634 1.1 christos # Read any data left so the close will work even if our called script 635 1.1 christos # didn't finish reading the data. 636 1.1 christos () = $fh->getlines; # force list context 637 1.1 christos die "Syserr closing pipe from `cvs co': $!" 638 1.1 christos if !$fh->close && $!; 639 1.1 christos $ret_code = $?; 640 1.1 christos } 641 1.1 christos else 642 1.1 christos { 643 1.1 christos $ret_code = 0xffff & system "$command >/dev/null 2>&1"; 644 1.1 christos } 645 1.1 christos 646 1.1 christos return !$ret_code; 647 1.1 christos } 648 1.1 christos 649 1.1 christos ###################################################################### 650 1.1 christos # 651 1.1 christos # NAME : 652 1.1 christos # find_interesting_revisions 653 1.1 christos # 654 1.1 christos # PURPOSE : 655 1.1 christos # CVS stores information in a logical manner. We only really 656 1.1 christos # need to look at some interestin revisions. These are: 657 1.1 christos # The first version 658 1.1 christos # And the last version on every branch. 659 1.1 christos # This is because cvs stores changes descending from 660 1.1 christos # main line. ie suppose the last version on mainline is 1.6 661 1.1 christos # version 1.6 of the file is stored in toto. version 1.5 662 1.1 christos # is stored as a diff between 1.5 and 1.6. 1.4 is stored 663 1.1 christos # as a diff between 1.5 and 1.4. 664 1.1 christos # branches are stored a little differently. They are 665 1.1 christos # stored in ascending order. Suppose there is a branch 666 1.1 christos # on 1.4 of the file. The first branches revision number 667 1.1 christos # would be 1.4.1.1. This is stored as a diff between 668 1.1 christos # version 1.4 and 1.4.1.1. The 1.4.1.2 version is stored 669 1.1 christos # as a diff between 1.4.1.1 and 1.4.1.2. Therefore 670 1.1 christos # we are only interested in the earliest revision number 671 1.1 christos # and the highest revision number on a branch. 672 1.1 christos # 673 1.1 christos # PARAMETERS : 674 1.1 christos # @revisions - The list of revisions to find interesting ones 675 1.1 christos # 676 1.1 christos # GLOBALS : 677 1.1 christos # NONE 678 1.1 christos # 679 1.1 christos # RETURNS : 680 1.1 christos # @new_revisions - The list of revisions that we find interesting 681 1.1 christos # 682 1.1 christos # COMMENTS : 683 1.1 christos # 684 1.1 christos ###################################################################### 685 1.1 christos sub find_interesting_revisions 686 1.1 christos { 687 1.1 christos my( @revisions ) = @_; 688 1.1 christos my @new_revisions; 689 1.1 christos my %max_branch_revision; 690 1.1 christos my $branch_number; 691 1.1 christos my $branch_rev; 692 1.1 christos my $key; 693 1.1 christos my $value; 694 1.1 christos 695 1.1 christos foreach my $revision( @revisions ) 696 1.1 christos { 697 1.1 christos ( $branch_number, $branch_rev ) = branch_split( $revision ); 698 1.1 christos $max_branch_revision{$branch_number} = $branch_rev 699 1.1 christos if( !exists $max_branch_revision{$branch_number} 700 1.1 christos || $max_branch_revision{$branch_number} < $branch_rev ); 701 1.1 christos } 702 1.1 christos 703 1.1 christos push( @new_revisions, "1.1" ) unless (exists $max_branch_revision{1} 704 1.1 christos && $max_branch_revision{1} == 1); 705 1.1 christos while( ( $key, $value ) = each ( %max_branch_revision ) ) 706 1.1 christos { 707 1.1 christos push( @new_revisions, $key . "." . $value ); 708 1.1 christos } 709 1.1 christos 710 1.1 christos my $nrc; 711 1.1 christos my $rc; 712 1.1 christos 713 1.1 christos $rc = @revisions; 714 1.1 christos $nrc = @new_revisions; 715 1.1 christos 716 1.1 christos $total_revisions += $rc; 717 1.1 christos $total_interesting_revisions += $nrc; 718 1.1 christos 719 1.1 christos verbose( "\t\tTotal Revisions: $rc Interesting Revisions: $nrc\n" ); 720 1.1 christos 721 1.1 christos return( @new_revisions ); 722 1.1 christos } 723 1.1 christos 724 1.1 christos 725 1.1 christos 726 1.1 christos ###################################################################### 727 1.1 christos # 728 1.1 christos # NAME : 729 1.1 christos # branch_split 730 1.1 christos # 731 1.1 christos # PURPOSE : 732 1.1 christos # To split up a revision number up into the branch part and 733 1.1 christos # the number part. For Instance: 734 1.1 christos # 1.1.1.1 - is split 1.1.1 and 1 735 1.1 christos # 2.1 - is split 2 and 1 736 1.1 christos # 1.3.4.5.7.8 - is split 1.3.4.5.7 and 8 737 1.1 christos # 738 1.1 christos # PARAMETERS : 739 1.1 christos # $revision - The revision to look at. 740 1.1 christos # 741 1.1 christos # GLOBALS : 742 1.1 christos # NONE 743 1.1 christos # 744 1.1 christos # RETURNS : 745 1.1 christos # ( $branch, $revision ) - 746 1.1 christos # $branch - The branch part of the revision number 747 1.1 christos # $revision - The revision part of the revision number 748 1.1 christos # 749 1.1 christos # COMMENTS : 750 1.1 christos # NONE 751 1.1 christos # 752 1.1 christos ###################################################################### 753 1.1 christos sub branch_split 754 1.1 christos { 755 1.1 christos my( $revision ) = @_; 756 1.1 christos my $branch; 757 1.1 christos my $version; 758 1.1 christos my @split_rev; 759 1.1 christos my $count; 760 1.1 christos 761 1.1 christos @split_rev = split /\./, $revision; 762 1.1 christos 763 1.1 christos my $numbers = @split_rev; 764 1.1 christos @split_rev = reverse( @split_rev ); 765 1.1 christos $branch = pop( @split_rev ); 766 1.1 christos for( $count = 0; $count < $numbers - 2 ; $count++ ) 767 1.1 christos { 768 1.1 christos $branch .= "." . pop( @split_rev ); 769 1.1 christos } 770 1.1 christos 771 1.1 christos return( $branch, pop( @split_rev ) ); 772 1.1 christos } 773 1.1 christos 774 1.1 christos ###################################################################### 775 1.1 christos # 776 1.1 christos # NAME : 777 1.1 christos # get_ignore_files_from_cvsroot 778 1.1 christos # 779 1.1 christos # PURPOSE : 780 1.1 christos # Retrieve the list of files from the CVSROOT/ directory 781 1.1 christos # that should be ignored. 782 1.1 christos # These are the regular files (e.g., commitinfo, loginfo) 783 1.1 christos # and those specified in the checkoutlist file. 784 1.1 christos # 785 1.1 christos # PARAMETERS : 786 1.1 christos # The CVSROOT 787 1.1 christos # 788 1.1 christos # GLOBALS : 789 1.1 christos # NONE 790 1.1 christos # 791 1.1 christos # RETURNS : 792 1.1 christos # @ignore - the list of files to ignore 793 1.1 christos # 794 1.1 christos # COMMENTS : 795 1.1 christos # NONE 796 1.1 christos # 797 1.1 christos ###################################################################### 798 1.1 christos sub get_ignore_files_from_cvsroot { 799 1.1 christos my( $cvsroot ) = @_; 800 1.1 christos my @ignore = ( 801 1.1 christos qr{CVS/fileattr$}o, 802 1.1 christos qr{^(./)?CVSROOT/.#[^/]*$}o, 803 1.1 christos qr{^(./)?CVSROOT/checkoutlist$}o, 804 1.1 christos qr{^(./)?CVSROOT/commitinfo$}o, 805 1.1 christos qr{^(./)?CVSROOT/config$}o, 806 1.1 christos qr{^(./)?CVSROOT/cvsignore$}o, 807 1.1 christos qr{^(./)?CVSROOT/cvswrappers$}o, 808 1.1 christos qr{^(./)?CVSROOT/editinfo$}o, 809 1.1 christos qr{^(./)?CVSROOT/history$}o, 810 1.1 christos qr{^(./)?CVSROOT/loginfo$}o, 811 1.1 christos qr{^(./)?CVSROOT/modules$}o, 812 1.1 christos qr{^(./)?CVSROOT/notify$}o, 813 1.1 christos qr{^(./)?CVSROOT/passwd$}o, 814 1.1 christos qr{^(./)?CVSROOT/postadmin$}o, 815 1.1 christos qr{^(./)?CVSROOT/postproxy$}o, 816 1.1 christos qr{^(./)?CVSROOT/posttag$}o, 817 1.1 christos qr{^(./)?CVSROOT/postwatch$}o, 818 1.1 christos qr{^(./)?CVSROOT/preproxy$}o, 819 1.1 christos qr{^(./)?CVSROOT/rcsinfo$}o, 820 1.1 christos qr{^(./)?CVSROOT/readers$}o, 821 1.1 christos qr{^(./)?CVSROOT/taginfo$}o, 822 1.1 christos qr{^(./)?CVSROOT/val-tags$}o, 823 1.1 christos qr{^(./)?CVSROOT/verifymsg$}o, 824 1.1 christos qr{^(./)?CVSROOT/writers$}o 825 1.1 christos ); 826 1.1 christos 827 1.1 christos my $checkoutlist_file = "$cvsroot/CVSROOT/checkoutlist"; 828 1.1 christos if( -f $checkoutlist_file && -r $checkoutlist_file ) 829 1.1 christos { 830 1.1 christos my $fh = new IO::File "<$checkoutlist_file" 831 1.1 christos or die "Unable to read checkoutlist file ($checkoutlist_file): $!\n"; 832 1.1 christos 833 1.1 christos my @list = $fh->getlines; 834 1.1 christos chomp( @list ); 835 1.1 christos $fh->close or die( "Unable to close checkoutlist file: $!\n" ); 836 1.1 christos 837 1.1 christos foreach my $line( @list ) 838 1.1 christos { 839 1.1 christos next if( $line =~ /^#/ || $line =~ /^\s*$/ ); 840 1.1 christos $line =~ s/^\s*(\S+)(\s+.*)?$/$1/; 841 1.1 christos push @ignore, qr{^(./)?CVSROOT/$line$}; 842 1.1 christos } 843 1.1 christos } 844 1.1 christos 845 1.1 christos return @ignore; 846 1.1 christos } 847 1.1 christos 848 1.1 christos 849 1.1 christos 850 1.1 christos ###### 851 1.1 christos ###### Go. 852 1.1 christos ###### 853 1.1 christos 854 1.1 christos exit main @ARGV; 855 1.1 christos 856 1.1 christos # vim:tabstop=4:shiftwidth=4 857