1107484Speter#! @PERL@ -w
2107484Speter########################################################################
3107484Speter#  Copyright (c) 2000, 2001 by Donald Sharp <sharpd@cisco.com>
4107484Speter#  All Rights Reserved
5107484Speter#
6107484Speter#  Permission is granted to copy and/or distribute this file, with or
7107484Speter#  without modifications, provided this notice is preserved.
8107484Speter#
9107484Speter########################################################################
10107484Speter
11107484Speter=head1 check_cvs.pl
12107484Speter
13107484Speter    Script to check the integrity of the Repository
14107484Speter
15107484Speter=head1 SYNOPSIS
16107484Speter
17107484Speter    check_cvs.pl
18107484Speter
19107484Speter=head1 DESCRIPTION
20107484Speter
21107484Speter    This script will search through a repository and determine if
22107484Speter    any of the files in it are corrupted.
23107484Speter
24107484Speter    Please do not run this script inside of the repository itself,
25107484Speter    it will cause it too fail.
26107484Speter
27107484Speter    Also it currently can only be run over the entire repository,
28107484Speter    so only point your CVSROOT at the actual CVSROOT.
29107484Speter
30107484Speter=head1 OPTIONS
31107484Speter
32107484Speter    There are no options.
33107484Speter
34107484Speter=head1 EXAMPLES
35107484Speter
36107484Speter    setenv CVSROOT /release/111/cvs
37107484Speter
38107484Speter    # To see more verbose output
39107484Speter    setenv CVSDEBUGEDIT 1
40107484Speter
41107484Speter    check_cvs.pl
42107484Speter
43107484Speter=head1 SEE ALSO
44107484Speter
45107484Speter    None
46107484Speter
47107484Speter=cut
48107484Speter
49107484Speter######################################################################
50107484Speter#                    MODULES                                         #
51107484Speter######################################################################
52107484Speteruse strict;
53107484Speter
54107484Speteruse File::Find;
55107484Speteruse File::Basename;
56107484Speteruse File::Path;
57107484Speteruse Cwd;
58107484Speter
59107484Speter######################################################################
60107484Speter#                    GLOBALS                                         #
61107484Speter######################################################################
62107484Speter
63107484Spetermy @list_of_broken_files;
64107484Spetermy @extra_files;
65107484Spetermy $verbose = 0;
66107484Speter
67107484Spetermy $total_revisions;
68107484Spetermy $total_interesting_revisions;
69107484Spetermy $total_files;
70128266Spetermy @ignore_files;
71107484Speter
72107484Speter######################################################################
73107484Speter#                    SUBROUTINES                                     #
74107484Speter######################################################################
75107484Speter
76107484Speter######################################################################
77107484Speter#
78107484Speter#    NAME :
79107484Speter#      main
80107484Speter#
81107484Speter#    PURPOSE :
82107484Speter#      To search the repository for broken files
83107484Speter#
84107484Speter#    PARAMETERS :
85107484Speter#      NONE
86107484Speter#
87107484Speter#    GLOBALS :
88107484Speter#      $ENV{ CVSROOT }       - The CVS repository to search through
89107484Speter#      $ENV{ CVSDEBUGEDIT }  - Turn on Debugging.
90107484Speter#      @list_of_broken_files - The list of files that need to
91107484Speter#                              be fixed.
92107484Speter#      $verbose              - is verbose mode on?
93107484Speter#      $total_revisions      - The number of revisions considered
94107484Speter#      $total_interesting_revisions - The number of revisions used
95107484Speter#      $total_files          - The total number of files looked at.
96107484Speter#
97107484Speter#    RETURNS :
98107484Speter#      A list of broken files
99107484Speter#
100107484Speter#    COMMENTS :
101107484Speter#      Do not run this script inside the repository.  Choose
102107484Speter#      a nice safe spot( like /tmp ) outside of the repository.
103107484Speter#
104107484Speter######################################################################
105107484Spetermy $directory_to_look_at;
106107484Speter
107107484Speterselect (STDOUT); $| = 1;    # make unbuffered
108107484Speter
109107484Speter$total_revisions = 0;
110107484Speter$total_interesting_revisions = 0;
111107484Speter$total_files = 0;
112107484Speter
113107484Speterif( !exists( $ENV{ CVSROOT } ) )
114107484Speter{
115107484Speter    die( "The script should be run with the CVSROOT environment variable set" );
116107484Speter}
117107484Speter
118107484Speterif( exists( $ENV{ CVSDEBUGEDIT } ) )
119107484Speter{
120107484Speter    $verbose = 1;
121107484Speter    print( "Verbose Mode Turned On\n" );
122107484Speter}
123107484Speter
124107484Speter$directory_to_look_at = $ENV{ CVSROOT };
125128266Spetermy $sym_count = 0;
126128266Speterwhile( -l $directory_to_look_at )
127107484Speter{
128107484Speter    $directory_to_look_at = readlink( $directory_to_look_at );
129128266Speter    $sym_count += 1;
130128266Speter    if( $sym_count > 5 )
131128266Speter    {
132128266Speter        die( "Encountered too many symlinks for $ENV{ CVSROOT }\n" );
133128266Speter    }
134107484Speter}
135107484Speter
136107484Speterprint( "Processing: $directory_to_look_at\n" ) if( $verbose );
137128266Speter@ignore_files = &get_ignore_files_from_cvsroot( $directory_to_look_at );
138107484Speterfind( \&process_file, $directory_to_look_at );
139107484Speter
140107484Spetermy $num_files = @list_of_broken_files;
141107484Speterprint( "List of corrupted files\n" ) if( $num_files > 0 );
142107484Speterforeach my $broken ( @list_of_broken_files )
143107484Speter{
144107484Speter    print( "**** File: $broken\n" );
145107484Speter}
146107484Speter
147107484Speter$num_files = @extra_files;
148107484Speterprint( "List of Files That Don't belong in Repository:\n" ) if( $num_files > 0 );
149107484Speterforeach my $extra ( @extra_files )
150107484Speter{
151107484Speter    print( "**** File: $extra\n" );
152107484Speter}
153107484Speterprint( "Total Files: $total_files\n" );
154107484Speterprint( "Total Revisions: $total_revisions Interesting Revisions: $total_interesting_revisions\n" );
155107484Speter
156107484Speter######################################################################
157107484Speter#
158107484Speter#    NAME :
159107484Speter#      process_file
160107484Speter#
161107484Speter#    PURPOSE :
162107484Speter#      This function is called by the find function, it's purpose
163107484Speter#      is to decide if it is important to look at a file or not.
164107484Speter#      We only care about files that have the ,v at the end.
165107484Speter#
166107484Speter#    PARAMETERS :
167107484Speter#      NONE
168107484Speter#
169107484Speter#    GLOBALS :
170107484Speter#      $ENV{ CVSROOT } - The CVS repository to search through
171107484Speter#
172107484Speter#    RETURNS :
173107484Speter#      NONE
174107484Speter#
175107484Speter#    COMMENTS :
176107484Speter#      NONE
177107484Speter#
178107484Speter######################################################################
179107484Spetersub process_file
180107484Speter{
181107484Speter    my $path = $File::Find::name;
182107484Speter
183107484Speter    $total_files += 1;
184107484Speter    $path =~ s/^$directory_to_look_at\///;
185107484Speter
186107484Speter    print( "\tProcessing File: $path\n" ) if( $verbose );
187107484Speter    if( $path =~ /,v$/ )
188107484Speter    {
189107484Speter        $path =~ s/,v$//;
190107484Speter        look_at_cvs_file( $path );
191107484Speter    }
192107484Speter    elsif( ! -d $File::Find::name )
193107484Speter    {
194107484Speter        my $save = 0;
195107484Speter
196107484Speter        foreach my $ignore ( @ignore_files )
197107484Speter        {
198107484Speter            if( $path =~ /$ignore/ )
199107484Speter            {
200107484Speter                $save = 1;
201107484Speter                last;
202107484Speter            }
203107484Speter        }
204107484Speter
205107484Speter        if( !$save )
206107484Speter        {
207107484Speter            push( @extra_files, $path );
208107484Speter        }
209107484Speter    }
210107484Speter}
211107484Speter
212107484Speter######################################################################
213107484Speter#
214107484Speter#    NAME :
215107484Speter#      look_at_cvs_file
216107484Speter#
217107484Speter#    PURPOSE :
218107484Speter#      To decide if a file is broken or not.  The algorithm is:
219107484Speter#      a)  Get the revision history for the file.
220107484Speter#              - If that fails the file is broken, save the fact
221107484Speter#                and continue processing other files.
222107484Speter#              - If that succeeds we have a list of revisions.
223107484Speter#      b)  For Each revision try to retrieve that version
224107484Speter#              - If that fails the file is broken, save the fact
225107484Speter#                and continue processing other files.
226107484Speter#      c)  Continue on 
227107484Speter#
228107484Speter#    PARAMETERS :
229107484Speter#      $file - The file to look at.
230107484Speter#
231107484Speter#    GLOBALS :
232107484Speter#      NONE
233107484Speter#
234107484Speter#    RETURNS :
235107484Speter#      NONE
236107484Speter#
237107484Speter#    COMMENTS :
238107484Speter#      We have to handle Attic files in a special manner.
239107484Speter#      Basically remove the Attic from the string if it
240107484Speter#      exists at the end of the $path variable.
241107484Speter#
242107484Speter######################################################################
243107484Spetersub look_at_cvs_file
244107484Speter{
245107484Speter    my( $file ) = @_;
246107484Speter    my( $name, $path, $suffix ) = fileparse( $file );
247107484Speter
248107484Speter    if( $path =~ s/Attic\/$// )
249107484Speter    {
250107484Speter        $file = $path . $name;
251107484Speter    }
252107484Speter
253107484Speter    my $revisions = get_history( $name );
254107484Speter
255107484Speter    if( !defined( $revisions ) )
256107484Speter    {
257107484Speter        print( "\t$file is corrupted, this was determined via a cvs log command\n" ) if( $verbose );
258107484Speter        push( @list_of_broken_files, $file );
259107484Speter        return();
260107484Speter    }
261107484Speter
262107484Speter    my @int_revisions = find_interesting_revisions( @$revisions );
263107484Speter
264107484Speter    foreach my $revision ( @int_revisions )
265107484Speter    {
266107484Speter        print( "\t\tLooking at Revision: $revision\n" ) if( $verbose );
267107484Speter        if( !check_revision( $file, $revision ) )
268107484Speter        {
269107484Speter            print( "\t$file is corrupted in revision: $revision\n" ) if( $verbose );
270107484Speter            push( @list_of_broken_files, $file );
271107484Speter            return();
272107484Speter        }
273107484Speter    }
274107484Speter
275107484Speter}
276107484Speter
277107484Speter######################################################################
278107484Speter#
279107484Speter#    NAME :
280107484Speter#      get_history
281107484Speter#
282107484Speter#    PURPOSE :
283107484Speter#      To retrieve a array of revision numbers.
284107484Speter#
285107484Speter#    PARAMETERS :
286107484Speter#      $file - The file to retrieve the revision numbers for
287107484Speter#
288107484Speter#    GLOBALS :
289107484Speter#      NONE
290107484Speter#
291107484Speter#    RETURNS :
292107484Speter#      On Success - Reference to the list of revision numbers
293107484Speter#      On Failure - undef.
294107484Speter#
295107484Speter#    COMMENTS :
296107484Speter#      The $_ is saved off because The File::find functionality
297107484Speter#      expects the $_ to not have been changed.
298107484Speter#      The -N option for the rlog command means to spit out 
299107484Speter#      tags or branch names.
300107484Speter#
301107484Speter######################################################################
302107484Spetersub get_history
303107484Speter{
304107484Speter    my( $file ) = @_;
305128266Speter    $file =~ s/(["\$`\\])/\\$1/g;
306107484Speter    my @revisions;
307107484Speter    my $revision;
308128266Speter    my $ignore = 1;
309107484Speter    my $save_ = $_;
310107484Speter
311128266Speter    open( FILE, "rlog -N \"$file\" 2>&1 |" ) or die( "unable to run rlog, help" );
312107484Speter
313107484Speter    while( <FILE> )
314107484Speter    {
315128266Speter        #rlog outputs a "----" line before the actual revision
316128266Speter        #without this we'll pick up peoples comments if they 
317128266Speter        #happen to start with revision
318128266Speter	if( /^----------------------------$/ )
319128266Speter	{
320128266Speter	    $ignore = 0;
321128266Speter            next;
322107484Speter        }
323128266Speter
324128266Speter	if( ( !$ignore ) && ( ( $revision ) = m/^revision (\S+)/ ) )
325128266Speter	{
326128266Speter            push( @revisions, $revision );
327128266Speter            $ignore = 1;
328128266Speter        }
329107484Speter    }
330107484Speter
331107484Speter    $_ = $save_;
332107484Speter
333107484Speter    if( !close( FILE ) )
334107484Speter    {
335107484Speter        return( undef );
336107484Speter    }
337107484Speter
338107484Speter    return( \@revisions );
339107484Speter}
340107484Speter
341107484Speter######################################################################
342107484Speter#
343107484Speter#    NAME :
344107484Speter#      check_revision
345107484Speter#
346107484Speter#    PURPOSE :
347107484Speter#      Given a file and a revision number ensure that we can 
348107484Speter#      check out that file
349107484Speter#
350107484Speter#    PARAMETERS :
351107484Speter#      $file     - The file to look at.
352107484Speter#      $revision - The revision to look at.
353107484Speter#
354107484Speter#    GLOBALS :
355107484Speter#      NONE
356107484Speter#
357107484Speter#    RETURNS :
358107484Speter#      If we can get the File - 1
359107484Speter#      If we can not get the File - 0
360107484Speter#
361107484Speter#    COMMENTS :
362107484Speter#      cvs command line options are as followed:
363107484Speter#        -n - Do not run any checkout program as specified by the -o
364107484Speter#             option in the modules file
365107484Speter#        -p - Put all output to standard out.
366107484Speter#        -r - The revision of the file that we would like to look at.
367107484Speter#      Please note that cvs will return 0 for being able to successfully
368107484Speter#      read the file and 1 for failure to read the file.
369107484Speter#
370107484Speter######################################################################
371107484Spetersub check_revision
372107484Speter{
373107484Speter    my( $file, $revision ) = @_;
374128266Speter    $file =~ s/(["\$`\\])/\\$1/g;
375107484Speter
376107484Speter    my $cwd = getcwd();
377107484Speter    chdir( "/tmp" );
378107484Speter
379128266Speter    my $ret_code = 0xffff & system( "cvs co -n -p -r $revision \"$file\" > /dev/null 2>&1" );
380107484Speter
381107484Speter    chdir( $cwd );
382107484Speter    return( 1 ) if ( $ret_code == 0 );
383107484Speter    return( 0 );
384107484Speter
385107484Speter    return( $ret_code );
386107484Speter}
387107484Speter
388107484Speter######################################################################
389107484Speter#
390107484Speter#    NAME :
391107484Speter#      find_interesting_revisions
392107484Speter#
393107484Speter#    PURPOSE :
394107484Speter#      CVS stores information in a logical manner.  We only really
395107484Speter#      need to look at some interestin revisions.  These are:
396107484Speter#      The first version
397107484Speter#      And the last version on every branch.
398107484Speter#      This is because cvs stores changes descending from 
399107484Speter#      main line. ie suppose the last version on mainline is 1.6
400107484Speter#      version 1.6 of the file is stored in toto.  version 1.5
401107484Speter#      is stored as a diff between 1.5 and 1.6.  1.4 is stored 
402107484Speter#      as a diff between 1.5 and 1.4.
403107484Speter#      branches are stored a little differently.  They are 
404107484Speter#      stored in ascending order.  Suppose there is a branch
405107484Speter#      on 1.4 of the file.  The first branches revision number
406107484Speter#      would be 1.4.1.1.  This is stored as a diff between 
407107484Speter#      version 1.4 and 1.4.1.1.  The 1.4.1.2 version is stored
408107484Speter#      as a diff between 1.4.1.1 and 1.4.1.2.  Therefore
409107484Speter#      we are only interested in the earliest revision number
410107484Speter#      and the highest revision number on a branch.
411107484Speter#
412107484Speter#    PARAMETERS :
413107484Speter#      @revisions - The list of revisions to find interesting ones
414107484Speter#
415107484Speter#    GLOBALS :
416107484Speter#      NONE
417107484Speter#
418107484Speter#    RETURNS :
419107484Speter#      @new_revisions - The list of revisions that we find interesting
420107484Speter#
421107484Speter#    COMMENTS :
422107484Speter#
423107484Speter######################################################################
424107484Spetersub find_interesting_revisions
425107484Speter{
426107484Speter    my( @revisions ) = @_;
427107484Speter    my @new_revisions;
428107484Speter    my %branch_revision;
429107484Speter    my $branch_number;
430107484Speter    my $branch_rev;
431107484Speter    my $key;
432107484Speter    my $value;
433107484Speter
434107484Speter    START_OVER:
435107484Speter    foreach my $revision( @revisions )
436107484Speter    {
437107484Speter        my $start_over = 0;
438107484Speter        ( $branch_number, $branch_rev ) = branch_split( $revision );
439107484Speter
440107484Speter        #if the number of elements in the branch is 1
441107484Speter        #and the new branch is less than the old branch
442107484Speter        if( elements_in_branch( $branch_number ) == 1 )
443107484Speter        {
444107484Speter            ( $start_over,
445107484Speter              %branch_revision ) = find_int_mainline_revision( $branch_number,
446107484Speter                                                               $branch_rev,
447107484Speter                                                               %branch_revision );
448107484Speter            next START_OVER if( $start_over );
449107484Speter        }
450107484Speter
451107484Speter        %branch_revision = find_int_branch_revision( $branch_number,
452107484Speter                                                     $branch_rev,
453107484Speter                                                     %branch_revision );
454107484Speter
455107484Speter    }
456107484Speter
457107484Speter    %branch_revision = remove_duplicate_branches( %branch_revision );
458107484Speter
459107484Speter    while( ( $key, $value ) = each ( %branch_revision ) )
460107484Speter    {
461107484Speter        push( @new_revisions, $key . "." . $value );
462107484Speter    }
463107484Speter
464107484Speter    my $nrc;
465107484Speter    my $rc;
466107484Speter
467107484Speter    $rc = @revisions;
468107484Speter    $nrc = @new_revisions;
469107484Speter
470107484Speter    $total_revisions += $rc;
471107484Speter    $total_interesting_revisions += $nrc;
472107484Speter
473107484Speter    print( "\t\tTotal Revisions: $rc Interesting Revisions: $nrc\n" ) if( $verbose );
474107484Speter
475107484Speter    return( @new_revisions );
476107484Speter}
477107484Speter
478107484Speter########################################################################
479107484Speter#
480107484Speter#    NAME :
481107484Speter#      remove_duplicate_branches
482107484Speter#
483107484Speter#    PURPOSE :
484107484Speter#      To remove from the list of branches that we are interested
485107484Speter#      in duplication that will cause cvs to check a revision multiple
486107484Speter#      times.  For Instance revision 1.1.1.1 should be prefered
487107484Speter#      to be checked over revision 1.1, as that v1.1.1.1 can
488107484Speter#      only be retrieved by going through v1.1.  Therefore
489107484Speter#      we should remove v1.1 from the list of branches that
490107484Speter#      are interesting.
491107484Speter#
492107484Speter#    PARAMETERS :
493107484Speter#      %branch_revisions - The hash of the interesting revisions
494107484Speter#
495107484Speter#    GLOBALS :
496107484Speter#      NONE
497107484Speter#
498107484Speter#    RETURNS :
499107484Speter#      %branch_revisions - The hash of the modified interesting revisions
500107484Speter#
501107484Speter#    COMMENTS :
502107484Speter#      NONE
503107484Speter#
504107484Speter########################################################################
505107484Spetersub remove_duplicate_branches
506107484Speter{
507107484Speter    my( %branch_revisions ) = @_;
508107484Speter    my $key;
509107484Speter    my $value;
510107484Speter    my $branch_comp;
511107484Speter    my $branch;
512107484Speter
513107484Speter
514107484Speter  RESTART:
515107484Speter    {
516107484Speter        my @keys = keys( %branch_revisions );
517107484Speter        while( ( $key, $value ) = each ( %branch_revisions ) )
518107484Speter        {
519107484Speter            $branch_comp = $key . "." . $value;
520107484Speter            foreach $branch ( @keys )
521107484Speter            {
522107484Speter                if( $branch eq $key )
523107484Speter                {
524107484Speter                    next;
525107484Speter                }
526107484Speter                if( elements_in_branch( $branch_comp ) ==
527107484Speter                    elements_in_branch( $branch ) - 1 )
528107484Speter                {
529107484Speter                    if( $branch =~ /^$branch_comp/ )
530107484Speter                    {
531107484Speter                        delete( $branch_revisions{ $key } );
532107484Speter                        goto RESTART;
533107484Speter                    }
534107484Speter                }
535107484Speter            }
536107484Speter        }
537107484Speter    }
538107484Speter
539107484Speter    return( %branch_revisions );
540107484Speter}
541107484Speter
542107484Speter######################################################################
543107484Speter#
544107484Speter#    NAME :
545107484Speter#      find_int_branch_revision
546107484Speter#
547107484Speter#    PURPOSE :
548107484Speter#      To Find a interesting branch revision.
549107484Speter#      Algorithm:
550107484Speter#        If the $branch_revision exists in the interesting branch
551107484Speter#        hash and the new $branch_rev is less than currently saved
552107484Speter#        one replace it with the new $branch_rev.
553107484Speter#        else if the $branch_revision doesn't exist in the interesting
554107484Speter#        branch hash, then just store the $branch_number and $branch_rev
555107484Speter#
556107484Speter#    PARAMETERS :
557107484Speter#      $branch_number - The branch that we are looking at
558107484Speter#      $branch_rev    - The particular revision we are looking
559107484Speter#                       at on the $branch_number.
560107484Speter#      %branch_revision - The hash storing the interesting branches
561107484Speter#                         and the revisions on them.
562107484Speter#
563107484Speter#    GLOBALS :
564107484Speter#      NONE
565107484Speter#
566107484Speter#    RETURNS :
567107484Speter#      %branch_revision - The modified hash that stores interesting
568107484Speter#                         branches.
569107484Speter#
570107484Speter#    COMMENTS :
571107484Speter#      NONE
572107484Speter#
573107484Speter######################################################################
574107484Spetersub find_int_branch_revision
575107484Speter{
576107484Speter    my( $branch_number, $branch_rev, %branch_revision ) = @_;
577107484Speter
578107484Speter    if( exists( $branch_revision{ $branch_number } ) )
579107484Speter    {
580107484Speter        if( $branch_rev > $branch_revision{ $branch_number } )
581107484Speter        {
582107484Speter            $branch_revision{ $branch_number } = $branch_rev;
583107484Speter        }
584107484Speter    }
585107484Speter    else
586107484Speter    {
587107484Speter        $branch_revision{ $branch_number } = $branch_rev;
588107484Speter    }
589107484Speter
590107484Speter    return( %branch_revision );
591107484Speter}
592107484Speter
593107484Speter######################################################################
594107484Speter#
595107484Speter#    NAME :
596107484Speter#      find_int_mainline_revision
597107484Speter#
598107484Speter#    PURPOSE :
599107484Speter#      To Find a interesting mainline revision.
600107484Speter#      Algorithm:
601107484Speter#        if the $branch_number is less then a branch number
602107484Speter#        with one element in it, then delete the old branch_number
603107484Speter#        and return.
604107484Speter#        if the $branch_number is greater than a branch number
605107484Speter#        then return, and tell the calling function that we
606107484Speter#        should skip this element, as that it's not important.
607107484Speter#        if the $branch_number is the same as a branch number
608107484Speter#        with one element in it, then check to see if the
609107484Speter#        $branch_rev is less than the stored branch rev if
610107484Speter#        it is replace with new $branch_rev.  Else ignore revision
611107484Speter#
612107484Speter#    PARAMETERS :
613107484Speter#      $branch_number - The branch that we are looking at
614107484Speter#      $branch_rev    - The particular revision we are looking
615107484Speter#                       at on the $branch_number.
616107484Speter#      %branch_revision - The hash storing the interesting branches
617107484Speter#                         and the revisions on them.
618107484Speter#
619107484Speter#    GLOBALS :
620107484Speter#      NONE
621107484Speter#
622107484Speter#    RETURNS :
623107484Speter#      ( $skip, %branch_revision ) -
624107484Speter#      $skip - 1 if we need to ignore this particular $branch_number
625107484Speter#              $branch_rev combo.  Else 0.
626107484Speter#      %branch_revision - The modified hash that stores interesting
627107484Speter#                         branches.
628107484Speter#
629107484Speter#    COMMENTS :
630107484Speter#      NONE
631107484Speter#
632107484Speter######################################################################
633107484Spetersub find_int_mainline_revision
634107484Speter{
635107484Speter    my( $branch_number, $branch_rev, %branch_revision ) = @_;
636107484Speter
637107484Speter    foreach my $key ( keys %branch_revision )
638107484Speter    {
639107484Speter        if( elements_in_branch( $key ) == 1 )
640107484Speter        {
641107484Speter            if( $branch_number < $key )
642107484Speter            {
643107484Speter                delete( $branch_revision{ $key } );
644107484Speter                next;
645107484Speter            }
646107484Speter
647107484Speter            if( $branch_number > $key )
648107484Speter            {
649107484Speter                return( 1, %branch_revision );
650107484Speter            }
651107484Speter            if( ( exists( $branch_revision{ $branch_number } ) ) &&
652107484Speter                ( $branch_rev < $branch_revision{ $branch_number } ) )
653107484Speter            {
654107484Speter                $branch_revision{ $branch_number } = $branch_rev;
655107484Speter                return( 1, %branch_revision );
656107484Speter            }
657107484Speter        }
658107484Speter    }
659107484Speter
660107484Speter    return( 0, %branch_revision );
661107484Speter}
662107484Speter
663107484Speter######################################################################
664107484Speter#
665107484Speter#    NAME :
666107484Speter#      elements_in_branch
667107484Speter#
668107484Speter#    PURPOSE :
669107484Speter#      Determine the number of elements in a revision number
670107484Speter#      Elements are defined by numbers seperated by ".".
671107484Speter#      the revision 1.2.3.4 would have 4 elements
672107484Speter#      the revision 1.2.4.5.6.7 would have 6 elements
673107484Speter#
674107484Speter#    PARAMETERS :
675107484Speter#      $branch - The revision to look at.
676107484Speter#
677107484Speter#    GLOBALS :
678107484Speter#      NONE
679107484Speter#
680107484Speter#    RETURNS :
681107484Speter#      $count - The number of elements
682107484Speter#
683107484Speter#    COMMENTS :
684107484Speter#      NONE
685107484Speter#
686107484Speter######################################################################
687107484Spetersub elements_in_branch
688107484Speter{
689107484Speter    my( $branch ) = @_;
690107484Speter    my @split_rev;
691107484Speter
692107484Speter    @split_rev = split /\./, $branch;
693107484Speter
694107484Speter    my $count = @split_rev;
695107484Speter    return( $count );
696107484Speter}
697107484Speter
698107484Speter######################################################################
699107484Speter#
700107484Speter#    NAME :
701107484Speter#      branch_split
702107484Speter#
703107484Speter#    PURPOSE :
704107484Speter#      To split up a revision number up into the branch part and
705107484Speter#      the number part.  For Instance:
706107484Speter#      1.1.1.1 - is split 1.1.1 and 1
707107484Speter#      2.1     - is split 2 and 1
708107484Speter#      1.3.4.5.7.8 - is split 1.3.4.5.7 and 8
709107484Speter#
710107484Speter#    PARAMETERS :
711107484Speter#      $revision - The revision to look at.
712107484Speter#
713107484Speter#    GLOBALS :
714107484Speter#      NONE
715107484Speter#
716107484Speter#    RETURNS :
717107484Speter#      ( $branch, $revision ) - 
718107484Speter#      $branch - The branch part of the revision number 
719107484Speter#      $revision - The revision part of the revision number
720107484Speter#
721107484Speter#    COMMENTS :
722107484Speter#      NONE
723107484Speter#
724107484Speter######################################################################
725107484Spetersub branch_split
726107484Speter{
727107484Speter    my( $revision ) = @_;
728107484Speter    my $branch;
729107484Speter    my $version;
730107484Speter    my @split_rev;
731107484Speter    my $count;
732107484Speter
733107484Speter    @split_rev = split /\./, $revision;
734107484Speter
735107484Speter    my $numbers = @split_rev;
736107484Speter    @split_rev = reverse( @split_rev );
737107484Speter    $branch = pop( @split_rev );
738107484Speter    for( $count = 0; $count < $numbers - 2 ; $count++ )
739107484Speter    {
740107484Speter        $branch .= "." . pop( @split_rev );
741107484Speter    }
742107484Speter
743107484Speter    return( $branch, pop( @split_rev ) );
744107484Speter}
745128266Speter
746128266Speter######################################################################
747128266Speter#
748128266Speter#    NAME :
749128266Speter#      get_ignore_files_from_cvsroot
750128266Speter#
751128266Speter#    PURPOSE :
752128266Speter#      Retrieve the list of files from the CVSROOT/ directory
753128266Speter#      that should be ignored. 
754128266Speter#      These are the regular files (e.g., commitinfo, loginfo)
755128266Speter#      and those specified in the checkoutlist file.
756128266Speter#
757128266Speter#    PARAMETERS :
758128266Speter#      The CVSROOT
759128266Speter#
760128266Speter#    GLOBALS :
761128266Speter#      NONE
762128266Speter#
763128266Speter#    RETURNS :
764128266Speter#      @ignore - the list of files to ignore
765128266Speter#
766128266Speter#    COMMENTS :
767128266Speter#      NONE
768128266Speter#
769128266Speter######################################################################
770128266Spetersub get_ignore_files_from_cvsroot {
771128266Speter    my( $cvsroot ) = @_;
772128266Speter    my @ignore = ( 'CVS\/fileattr$',
773128266Speter                   '^CVSROOT\/loginfo',
774128266Speter                   '^CVSROOT\/.#loginfo',
775128266Speter                   '^CVSROOT\/rcsinfo',
776128266Speter                   '^CVSROOT\/.#rcsinfo',
777128266Speter                   '^CVSROOT\/editinfo',
778128266Speter                   '^CVSROOT\/.#editinfo',
779128266Speter                   '^CVSROOT\/verifymsg',
780128266Speter                   '^CVSROOT\/.#verifymsg',
781128266Speter                   '^CVSROOT\/commitinfo',
782128266Speter                   '^CVSROOT\/.#commitinfo',
783128266Speter                   '^CVSROOT\/taginfo',
784128266Speter                   '^CVSROOT\/.#taginfo',
785128266Speter                   '^CVSROOT\/cvsignore',
786128266Speter                   '^CVSROOT\/.#cvsignore',
787128266Speter                   '^CVSROOT\/checkoutlist',
788128266Speter                   '^CVSROOT\/.#checkoutlist',
789128266Speter                   '^CVSROOT\/cvswrappers',
790128266Speter                   '^CVSROOT\/.#cvswrappers',
791128266Speter                   '^CVSROOT\/notify',
792128266Speter                   '^CVSROOT\/.#notify',
793128266Speter                   '^CVSROOT\/modules',
794128266Speter                   '^CVSROOT\/.#modules',
795128266Speter                   '^CVSROOT\/readers',
796128266Speter                   '^CVSROOT\/.#readers',
797128266Speter                   '^CVSROOT\/writers',
798128266Speter                   '^CVSROOT\/.#writers',
799128266Speter                   '^CVSROOT\/passwd',
800128266Speter                   '^CVSROOT\/config',
801128266Speter                   '^CVSROOT\/.#config',
802128266Speter                   '^CVSROOT\/val-tags',
803128266Speter                   '^CVSROOT\/.#val-tags',
804128266Speter                   '^CVSROOT\/history' );
805128266Speter    my $checkoutlist_file = "$cvsroot\/CVSROOT\/checkoutlist";
806128266Speter    open( CHECKOUTLIST, "<$cvsroot\/CVSROOT\/checkoutlist" )
807128266Speter        or die( "Unable to read checkoutlist file: $!\n" );
808128266Speter
809128266Speter    my @list = <CHECKOUTLIST>;
810128266Speter    chomp( @list );
811128266Speter    close( CHECKOUTLIST )
812128266Speter        or die( "Unable to close checkoutlist file: $!\n" );
813128266Speter
814128266Speter    foreach my $line( @list )
815128266Speter    {
816128266Speter        next if( $line =~ /^#/ || $line =~ /^$/ );
817128266Speter        if( $line =~ /^\s*(\S*)\s*/ ) { $line = $1 };
818128266Speter        push( @ignore, "^CVSROOT\/$line", "^CVSROOT\/\.#$line" );
819128266Speter    }	
820128266Speter
821128266Speter    return @ignore;
822128266Speter}
823