|
use strict;
use File::Basename;
use Getopt::Long;
use vars qw($PROG $P4 %OPT);
use constant GETOPT_OPTS =>
qw(auto_abbrev no_getopt_compat require_order bundling);
use constant TAXES =>
sub
{
my $i = 0;
my @stack;
my $stamp = scalar(gmtime());
my($pa,$fi,$li,$fu);
STACKFRAME:
while(caller(++$i))
{
my($pack,$file,$line,$func) = caller($i);
if( !$pa ) { ($pa,$fi,$li,$fu) = ($pack,$file,$line,$func) }
push( @stack, $func );
}
CORE::warn( "[$stamp] $fi\@$li: ",join(' <- ', @stack)," - ", @_ );
};
use constant DEATH =>
sub
{
if( $^S ) { die( @_ ) }
else
{
my($pack,$file,$line,$func) = caller(1);
my $stamp = scalar(gmtime());
my $locn = ($file && $line) ? join('', ' ', $file, '@', $line) : '';
die( "[$stamp]$locn: $func - ", @_ );
}
};
BEGIN
{
if( $ENV{DEBUG} > 1 )
{
$SIG{__WARN__} = TAXES;
$SIG{__DIE__ } = DEATH;
}
$PROG = File::Basename::basename( $0 );
$P4 = ( $ENV{P4} || 'p4' );
Getopt::Long::Configure( GETOPT_OPTS );
}
sub command (;@);
sub usage (;$);
sub getopts (\%@);
sub find_branch_part ($$);
sub main ();
sub DEBUG (;@) { $ENV{DEBUG} && warn(@_) }
main();
sub command (;@)
{
my(@command) = map { quotemeta } @_;
my(@results) = `@command`;
if ($?)
{
print(STDERR @results);
die($PROG, qq|: "@command" exited with status |,($? >> 8), ".\n");
}
return @results;
}
sub usage (;$)
{
my $err = $_[0];
$err && warn("$PROG: $err\n");
die(<<TXT);
Usage:
$PROG [ --after=YYYY/MM/DD ] <file> | <file>#<rev> | <file>\@<change>
<file> may be a client file name or depot file name.
TXT}
sub getopts (\%@)
{
my $opt = shift(@_);
grep { s/^--?no-/--no/ } @ARGV;
Getopt::Long::GetOptions($opt, @_);
}
sub find_branch_part ($$)
{
my $origname = $_[0];
my $branchname = $_[1];
my(@origname) = split( '/', $origname );
my(@branchname) = split( '/', $branchname );
while( @origname && ($origname[0] eq $branchname[0]) )
{
shift(@origname );
shift(@branchname);
}
while( @origname &&
($origname[$#origname] eq $branchname[$#branchname]) )
{
pop( @origname );
pop( @branchname );
}
return join( '/', @branchname );
}
sub main ()
{
my %author;
my @change;
my %change;
my $change;
my $head;
my $check_file;
if (! getopts(%OPT,"after=s") ) { usage() }
if ( $OPT{after} && $OPT{after} !~ /^\d{4}(\/\d{2}){2}$/ )
{
usage(qq|invalid date "$OPT{after}"|);
}
if( !@ARGV ) { usage("file name expected") }
if( @ARGV > 1 ) { usage("invalid argument" ) }
my $file = $ARGV[0];
if( $file =~ s/@(\d+)// ) { $change = $1 }
if( $file =~ s/\#(\d+)// ) { $head = $1 }
if ( $change ) { $check_file = "$file\@$change" }
elsif ( $head ) { $check_file = "$file\#$head" }
else { $check_file = $file }
my @list = command( $P4, 'files', $check_file );
if(@list > 1)
{
die("$PROG: the specified file pattern maps to more than one file.\n");
}
if ($list[0] =~ /(.*\#\d+) - delete change/)
{
die("$PROG: revision $1 is deleted.\n")
}
my ($fullname, @history) = command($P4, 'filelog', '-i', $check_file);
chop( $fullname );
$fullname =~ s/\#.*//;
@fullname
my $thisname = $fullname;
my $thisrev;
my $thisbranch;
my $headseen;
for (@history)
{
if ( m,^//, )
{
chop( $thisname = $_ );
next;
}
if(/^\.{3} #(\d+)\s+change\s+(\d+)\s+(\S+)\s+on\s+(\S+)\s+by\s+(\S+)@/)
{
my $this_rev = $1;
my $this_change = $2;
my $this_type = $3;
my $this_date = $4;
my $this_author = $5;
DEBUG("last branch: $thisbranch\n");
DEBUG("f/this name: $fullname/$thisname\n");
( $this_type eq "delete" ) && last;
( $OPT{after} && ($this_date lt $OPT{after}) ) && last;
($change && ($change < $this_change) ) && next;
($head && ($fullname eq $thisname) && ($head < $this_rev)) && next;
$change{"$thisname#$this_rev"} = $this_change;
push(@change, "$thisname#$this_rev");
$author{"$thisname#$this_rev"} =
$thisbranch ? join('@',$this_author,$thisbranch) : $this_author;
$head ||= $this_rev;
$thisrev = "$thisname#$this_rev";
$headseen = 1;
}
elsif( $headseen && /^\.\.\. \.\.\. branch from (\/\/[^#]*)\#/ )
{
my($fromfile) = $1;
my(@from) = split(m,/,, $fromfile);
$thisbranch = find_branch_part( $fullname, $fromfile );
$author{$thisrev} = $thisbranch;
}
}
if( $OPT{after} && !@change ){ usage("no revisions after $OPT{after}") }
my($base, @revs) = reverse( @change );
DEBUG("BASE : $base\n");
DEBUG("REVS : @revs\n");
DEBUG("CHANGE: ", join(", ", %change), "\n");
my( @txt ) = command($P4, 'print', '-q', $base);
my @lines = ($base) x scalar(@txt);
DEBUG("\@lines = ",scalar(@lines)," [initially]\n");
my $lastrev = $base;
foreach my $rev (@revs)
{
DEBUG("diff2 $lastrev $rev\n");
foreach (reverse( command($P4, 'diff2', $lastrev, $rev) ) )
{
my( $la, $lb, $op, $ra, $rb ) = /^(\d+),?(\d*)([acd])(\d+),?(\d*)/;
defined($ra) || next;
DEBUG(" REVM: [$ra] matched: ($lastrev,$rev): $_");
( $lb ) || ( $lb = $la ); ( $op eq 'a' ) && ( ++$la ); ( $rb ) || ( $rb = $ra ); ( $op eq 'd' ) && ( ++$ra );
splice( @lines ,
( $la - 1 ) ,
( $lb - $la + 1 ) ,
( ($rev) x ($rb - $ra + 1) ) );
}
$lastrev = $rev;
}
DEBUG("\@lines = ",scalar(@lines)," [postmangle]\n");
my ($header, @text) = command($P4, 'print', "$file#$head");
DEBUG("filespec: $file#$head\n");
if (@text != @lines)
{
die("$PROG: error applying diffs: Please contact the author\n");
}
my $fmt = '%5s %15s %6s %4s %s';
my(@fields) = (qw(line author/branch change rev), $header);
printf(STDOUT $fmt, @fields );
printf(STDOUT "$fmt\n", map('-' x length($_), @fields) );
my $line = 1;
while (@text)
{
my($rev ) = shift(@lines);
my($revno);
($revno = $rev) =~ s,.*\#,,;
printf(STDOUT
$fmt ,
$line++ ,
$author{$rev} ,
$change{$rev} ,
$revno, shift(@text) );
}
}
|