#!/usr/bin/perl

use POSIX qw(mktime ctime);
use Time::Local qw( timegm );

# Offline check for changed, merged or missing files in a checked-out
# CVS module. Sirtaj Singh Kang <taj@kde.org> Nov 1998.
# Usage:
# 	cvschanged <module dir>...

@dirqueue = @ARGV;
@merged = ();
@uncommitted = ();
@missing = ();
@tagged = ();
@removed = ();

$reportmissing = 1;
$reporttagged = 0;

@monthlist = ( "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug",
 "Sep", "Oct", "Nov", "Dec" );
%months = ();


# convert text stamp to GMT
sub strToTime
{
	my( $timestr ) = @_;

	if( ! ($timestr =~ 
		/^(\w+)\s*(\w+)\s*(\d+)\s*(\d+):(\d+):(\d+)\s*(\d+)/) ) {

		return -1;
	}

	# CVS timestamps are in GMT.

	my( $tm ) = timegm( $6, $5, $4, $3, $months{ $2 }, $7 - 1900);

	return $tm;
}

sub processEntries
{
	my ( $dir ) = @_;

	open( ENTRIES, $dir."/CVS/Entries" ) 
		|| warn "Couldn't read '$dir/CVS/Entries'";

	while( <ENTRIES> ) {
		if ( m#^\s*D/([^/]+)/# ) {
			push ( @dirqueue, "$dir/$1" ) if (-d "$dir/$1");
			next;
		}

		next if !m#^\s*/([^/]+)/([-]*[\d\.]*)/([^/]+)/(/T)?(\w+)?#;

		$fname = $1;
		$ver = $2;
		$stamp = $3;
		$tag = $5;

		if ( $tag ne "" ) {
			push @tagged, "$dir/$fname - $tag";
		}

		if ( $stamp =~ /merge/ ) {
			push @merged, "$dir/$fname";
			next;
		}

                if ( $ver =~ /^\-.*/ ) {
                  	push @removed, "$dir/$fname";
			next;
                }        

		$mtm = strToTime( $stamp );

		if( $mtm < 0 ) {
			if ( $stamp =~ /dummy/ ) {
				push @uncommitted, "$dir/$fname";
			}
			else {
				warn "Bad mod time for $dir/$fname: $stamp\n";
			}
			next;
		}

		@sparams = stat( "$dir/$fname" );

		if ( $#sparams < 0 ) {
			push @missing, "$dir/$fname";
			next;
		}

		if( $mtm < $sparams[ 9 ] ) {
			print "$dir/$fname\n";
		}
	}

	close( ENTRIES );
}

# month assoc array for name -> index lookups
$mctr = 0;
$reportmissing = 0 if ($ENV{"REPORTMISSING"} eq 'no');
$reporttagged = 0 if ($ENV{"REPORTTAGGED"} eq 'no');

foreach $month ( @monthlist ) {
	$months{ $month } = $mctr;
	$mctr++;
}

# Try current directory if none specified

if( $#dirqueue < 0 ) {
	push( @dirqueue, "." );
}

# process directory queue
foreach $dir ( @dirqueue ) {
	processEntries( $dir );
}

if ( $#missing >= 0 && $reportmissing) {
	print "\nMissing files:\n\n";
	foreach $f ( @missing ) {
		print $f,"\n";
	}
}


if ( $#merged >= 0 ) {
	print "\nMerged files:\n\n";
	foreach $f ( @merged ) {
		print $f,"\n";
	}
}

if ( $#tagged >= 0 && $reporttagged ) {
	print "\nTagged files:\n\n";
	foreach $f ( @tagged ) {
		print $f,"\n";
	}
}

if ( $#uncommitted >= 0 ) {
	print "\nAdded but not committed:\n\n";
	foreach $f ( @uncommitted ) {
		print $f,"\n";
	}
}

if( $#removed >= 0 ) {
 	print "\nRemoved but not committed:\n\n";
        foreach $f ( @removed ) {
		print $f,"\n";
	}
}

=head1 NAME

cvschanged -- Lists all files in checked out CVS modules that have been
edited or changed locally. No connection is required to the CVS server.

=head1 SYNOPSIS

When the current directory is a CVS module:

	cvschanged

Checking checked out subdirectories:

	cvschanged [<dir>...]

=head1 DESCRIPTION

cvschanged will list all files in each checked-out module (and
subdirectories) that have been edited or merged. This is done without
needing to contact the CVS server, which greatly increases the speed
for people who have a slow connection.

=head1 EXAMPLES

	cd baseline/kdelibs; cvschanged
	cd baseline; cvschanged kdelibs kdebase

=head1 AUTHOR

Sirtaj Singh Kang <taj@kde.org>

=cut
