- #!/usr/local/bin/perl
- # -*-Fundamental-*-
-
- # $Id: //depot/user/p4/bin/p4pr#15 $
-
- # Interpolate change information into a source listing of a p4 file.
- # Takes a file name or depot filename, with #<ref> or @<change>.
- # Contributed by Bob Sidebotham.
- #
- # Mods for Netapp "b4p4" wrapper use and support for filelog -i added
- # by Richard Geiger.
- #
-
- # Netapp-isms:
- #
- # Set up "$P4", the "p4" path we'll use
- #
- if (! -d "/u/p4/VERS")
- {
- $P4 = "p4";
-
- # If your site uses a standard "P4CONFIG" name, and your users
- # don't all define it in their own environments, you might want
- # to set it here
- #
- #$ENV{"P4CONFIG"} = "";
- }
- else
- {
- # Looks like we're at NetApp...
- #
- sub nobin
- {
- print STDERR "$Myname: I don't know how to run on this \"$Osname/$Osvers\" host.\n";
- exit 1;
- }
-
- ($Osname, $Hostname, $Osvers) = split(/\s+/, `/bin/uname -a`);
- $Hostname =~ s/\..*//;
-
- if ($Osname eq "SunOS")
- {
- if ($Osvers =~ /^5\.5\b/)
- { $bin = "solaris25"; }
- elsif ($Osvers =~ /^5\.[67]\b/)
- { $bin = "solaris26"; }
- elsif ($Osvers =~ /^4\.1\./)
- { $bin = "sunos"; }
- else
- { &nobin; }
- }
- elsif ($Osname eq "OSF1" && $Osvers =~ /^V4\./)
- { $bin = "osf"; }
- elsif ($Osname eq "Linux")
- { $bin = "linuxx86"; }
- elsif ($Osname eq "HP-UX")
- { $bin = "hpux"; }
- else
- { &nobin; }
-
- $P4 = "/u/p4/VERS/bin.$bin/p4";
-
- $ENV{"P4CONFIG"} = "P4ENV";
-
- $pwd = `/bin/pwd`; chomp $pwd;
- $ENV{"PWD"} = $pwd;
- }
-
- # Simplify program name, if it is a path.
- $0 =~ s#.*/##;
-
- # Execute a command, keeping the output of the command in an array.
- # Returns the array, unless an error occured, in which case the an
- # exception is thrown (via die) with an appropriate message.
- sub command {
- my($command) = @_;
- my(@results) = `$command`;
- if ($?) {
- my($err) = ($? >> 8);
- print STDERR @results;
- die qq($0: "$command" exited with status $err.\n);
- }
- @results
- }
-
- # Fatal usage error
- sub usage {
- my($err) = @_;
- die
- "$0: $err\n" .
- "usage: $0 [-i|-b] [-n <nrevs> [-d]] [-g <grevs>] [-m <pattern>] <file> | <file>#<rev> | <file>\@<change>\n" .
- " -i [default] follow history back across branches.\n" .
- " -c follow history back across \"copy from\" integrations.\n" .
- " -b use history from this branch only (faster, but less informative).\n" .
- " -g <grevs> perform diffs at a granularity of every <grevs> revisions.\n" .
- " -n <nrevs> only gather history <nrevs> revisions back from the requested revision.\n" .
- " -d (with -n) show only lines changed in the requested revision range.\n" .
- " -m <pattern> only show lines matching <pattern> (Perl regular expressions).\n" .
- " <file> may be a client file name or depot file name.\n";
- }
-
- $dashi = '-i';
-
- # Parse options
- while (@ARGV && $ARGV[0] =~ /^-/) {
- $opt = shift;
- if ($opt eq '-i') {
- $dashi = '-i'; # Follow history beyond current branch
- } elsif ($opt eq '-help') {
- usage("");
- } elsif ($opt eq '-b') {
- $dashi = ''; # Don't follow history beyond current branch
- } elsif ($opt eq '-d') {
- $chonly = 1; # Show changed lines only
- } elsif ($opt eq '-c') {
- $followcp = 1; # Follow back through the other branch from copies.
- } elsif ($opt eq '-m') {
- if ($#ARGV == -1) {
- usage("-m option needs an argument");
- }
- $pattern = shift;
- "" =~ /$pattern/; # make sure perl likes the regexp!
- } elsif ($opt eq '-n') {
- if ($#ARGV == -1) {
- usage("-n option needs an argument");
- }
- if (($nrevs = shift) !~ /^\d+/) {
- usage("-n option needs a numeric argument");
- }
- if ($nrevs <= 0) {
- usage("-n argument must be > 0");
- }
- } elsif ($opt eq '-g') {
- if ($#ARGV == -1) {
- usage("-g option needs an argument");
- }
- if (($grevs = shift) !~ /^\d+/) {
- usage("-g option needs a numeric argument");
- }
- if ($grevs <= 0) {
- usage("-g argument must be > 0");
- }
- } else {
- usage("invalid option $opt");
- }
- }
-
- # Get file argument.
- usage("file name expected") if !@ARGV;
- usage("invalid argument") if @ARGV > 1;
-
- # Filearg is the original user file argument (with rev or change)
- $filearg = $file = shift;
-
- if ($filearg eq "help") {
- print "[For help use \"p4p4 -help\"]\n";
- }
-
- # Check that the file specification maps to exactly one file.
- @list = command qq($P4 files $filearg);
- if (@list > 1) {
- die("$0: the specified file pattern maps to more than one file.\n");
- }
-
- # Check that the revision is not deleted.
- if ($list[0] =~ /(.*#\d+) - delete change/) {
- die("$0: revision $1 is deleted.\n")
- }
-
- @have = `$P4 have $filearg 2>&1`;
- if ($have[0] =~ /^(\/\/.*) - /) {
- $file = $1;
- }
-
- # Handle # and @ notation (only for numeric changes and revisions).
- $change = $1 if $file =~ s/@(\d+)//;
- $head = $1 if $file =~ s/#(\d+)//;
-
-
- # Get the fullname of the file and the history, all from
- # the filelog for the file.
- (@history) = command qq($P4 filelog $dashi $file);
-
- my %files;
- my @files;
-
- # Extract the revision to change number mapping. Also
- # get the author of each revision, and for merged
- # or copied revisions, the "branch name", which we
- # use instead of an author.
-
- hist:
- while ($#history >= 0)
- {
- while ($_ = shift(@history)) {
- if (/^\/\//) {
- $thisfile = $_;
- chop $thisfile;
- if (! defined($files{$thisfile})) {
- push(@files, $thisfile);
- $files{$thisfile} = $#files;
- }
- } elsif (/^\.\.\. #(\d+) change (\d+) (.*?) on .*? by (.*?)@/) {
- # If a change number or revision is specified, then ignore
- # later revisions.
- next if $change && $change < $2;
- next if ((! $headseen) && $head && ($head < $1));
- if ($dashi && $3 eq 'branch') {
- $head = ""; # In case $head = #1, where #1 was a branch
- next;
- }
- # If we see a change that deleted the file, then
- # we know that previous revisions did not contribute.
- last if $3 eq "delete";
-
- $change{"$thisfile#$1"} = $2;
- $author{"$thisfile#$1"} = $4;
-
- if ($nrevs && $#revs == ($nrevs-1)) {
- $author{"$thisfile#$1"} = "(base)";
- }
- $head = $1 if !$head;
- unshift(@revs, "$thisfile#$1");
- if ($nrevs && $#revs == $nrevs) {
- last hist;
- }
- $thisrev = $1;
- $headseen = 1;
- } else {
- # If we see a branch from, then we know that
- # previous revisions did not contribute to the current
- # revision. Don't do this, however, if we haven't seen
- # the revision we've been requested to print, yet.
- # We used to do this for copy from, but I think
- # it's better not to.
- next unless $headseen;
- if (/^\.\.\. \.\.\. (copy|branch|merge) from (\/\/[^#]*).*#([0-9]+)/) {
- my $fromfile = "$2#$3";
- # If merged or copied from another part of the
- # tree, then we use the first component of the
- # name that is different, and call that the "branch"
- # Further, we make the "author" be the name of the
- # branch.
- my($type) = $1;
- # If branched, we don't bother getting any more
- # history. We treat this as starting with the branch.
- if (! $dashi) {
- @history = ();
- $author{"$thisfile#$thisrev"} = "($type)";
- last;
- }
- if ($type eq "copy" && $followcp) {
- (@history) = command qq($P4 filelog $dashi $fromfile);
- last;
- }
- }
- }
- }
- }
-
- foreach $r (@allrevs)
- { print $r."\n"; }
-
- if ($grevs) {
- my @grevs;
- for (my $i = $#revs; $i >= 0; $i -= $grevs) { unshift(@grevs, $revs[$i]); }
- @revs = @grevs;
- }
-
- # Get first revision, and list of remaining revisions
- ($base, @revs) = @revs;
-
- # Get the contents of the base revision of the file,
- # purely for the purposes of counting the lines.
- @text = command qq($P4 print -q $base);
-
- # For each line in the file, set the change revision
- # to be the base revision.
- @lines = ($base) x @text;
-
- # For each revision from the base to the selected revision
- # "apply" the diffs by manipulating the array of revision
- # numbers. If lines are added, we add a corresponding
- # set of entries with the revision number that added it.
- # We ignore the actual revision text--that will be merged
- # with the change information later.
- $prevrev = $base;
-
- for $rev (@revs) {
- # Apply the diffs in reverse order to maintain correctness
- # of line numbers for each range as we apply it.
- for (reverse command qq($P4 diff2 $prevrev $rev)) {
- my( $la, $lb, $op, $ra, $rb ) = /^(\d+),?(\d*)([acd])(\d+),?(\d*)/;
- next unless defined($ra);
- $lb = $la if ! $lb;
- ++$la if $op eq 'a';
- $rb = $ra if ! $rb;
- ++$ra if $op eq 'd';
- splice @lines, $la - 1, $lb - $la + 1, ($rev) x ($rb - $ra + 1);
- }
- $prevrev = $rev
- }
-
- # Get the text of the selected revision. The number of lines
- # resulting from applying the diffs should equal the number of
- # of lines in this revision.
- ($header, @text) = command qq($P4 print $prevrev);
- if (@text != @lines) {
- die("$0: internal error applying diffs - please contact the author\n")
- }
-
- my $dokeys = ($#files > 0);
-
- if ($dokeys) {
- # Print a legend showing the correspondence between "file keys" and
- # file pathnames in the depot.
- my($fmt) = "%5s %s";
- @fields = (" key", "file". ' ' x 66);
- printf("$fmt\n", @fields);
- printf("$fmt\n", map('-' x length($_), @fields));
- for (my $i = 0; $i <= $#files; $i++) {
- printf("$fmt\n", $i, @files[$i]);
- }
- print "\n";
- }
-
- # Print a pretty header. Note that the interpolated information
- # at the beginning of the line is a multiple of 8 bytes (currently 24)
- # so that the default tabbing of 8 characters works correctly.
- my($fmt) = "%5s %10s %6s %6s %s";
- @fields = (" line", " author", "change", " rev", $header);
- printf($fmt, @fields);
- printf("$fmt\n", map('-' x length($_), @fields));
-
- # Interpolate the change author and number into the text.
- my($line) = 1;
- my($inbase) = 0;
- while (@text) {
- my($revkey) = shift(@lines);
- my($path, $rev) = split(/#/, $revkey);
- $rev = ($dokeys ? "$files{$path}" : ""). "#" . $rev;
- my $text = shift @text;
- if (($chonly && $author{$revkey} eq "(base)") || ($pattern && $text !~ /$pattern/)) {
- if (! $inbase) {
- print ".....\n";
- $inbase = 1;
- }
- } else {
- if ($grevs) { $author{$revkey} = "*"; }
- printf($fmt, $line, $author{$revkey}, $change{$revkey}, $rev, $text);
- $inbase = 0;
- }
- $line++;
- }
-
-