annotate: Handle dirty state and arbitrary revisions.

Also, use Getopt::Long and only process each rev once.

(Thanks to Morten Welinder for spotting the performance problems.)

Signed-off-by: Ryan Anderson <ryan@michonline.com>
Signed-off-by: Junio C Hamano <junkio@cox.net>
This commit is contained in:
Ryan Anderson 2006-02-25 20:48:33 -05:00 committed by Junio C Hamano
parent 9d7f73d43f
commit 87475f4dfc

View File

@ -8,44 +8,62 @@
use warnings;
use strict;
use Getopt::Std;
use Getopt::Long;
use POSIX qw(strftime gmtime);
sub usage() {
print STDERR 'Usage: ${\basename $0} [-s] [-S revs-file] file
-l show long rev
-r follow renames
-S commit use revs from revs-file instead of calling git-rev-list
print STDERR 'Usage: ${\basename $0} [-s] [-S revs-file] file [ revision ]
-l, --long
Show long rev (Defaults off)
-r, --rename
Follow renames (Defaults on).
-S, --rev-file revs-file
use revs from revs-file instead of calling git-rev-list
-h, --help
This message.
';
exit(1);
}
our ($opt_h, $opt_l, $opt_r, $opt_S);
getopts("hlrS:") or usage();
$opt_h && usage();
our ($help, $longrev, $rename, $starting_rev, $rev_file) = (0, 0, 1);
my $rc = GetOptions( "long|l" => \$longrev,
"help|h" => \$help,
"rename|r" => \$rename,
"rev-file|S" => \$rev_file);
if (!$rc or $help) {
usage();
}
my $filename = shift @ARGV;
if (@ARGV) {
$starting_rev = shift @ARGV;
}
my @stack = (
{
'rev' => "HEAD",
'rev' => defined $starting_rev ? $starting_rev : "HEAD",
'filename' => $filename,
},
);
our (@lineoffsets, @pendinglineoffsets);
our @filelines = ();
open(F,"<",$filename)
or die "Failed to open filename: $!";
while(<F>) {
chomp;
push @filelines, $_;
if (defined $starting_rev) {
@filelines = git_cat_file($starting_rev, $filename);
} else {
open(F,"<",$filename)
or die "Failed to open filename: $!";
while(<F>) {
chomp;
push @filelines, $_;
}
close(F);
}
close(F);
our $leftover_lines = @filelines;
our %revs;
our @revqueue;
our $head;
@ -66,7 +84,7 @@ while (my $bound = pop @stack) {
next;
}
if (!$opt_r) {
if (!$rename) {
next;
}
@ -78,8 +96,18 @@ while (my $bound = pop @stack) {
}
}
push @revqueue, $head;
init_claim($head);
$revs{$head}{'lineoffsets'} = {};
init_claim( defined $starting_rev ? $starting_rev : 'dirty');
unless (defined $starting_rev) {
open(DIFF,"-|","git","diff","-R", "HEAD", "--",$filename)
or die "Failed to call git diff to check for dirty state: $!";
_git_diff_parse(*DIFF, $head, "dirty", (
'author' => gitvar_name("GIT_AUTHOR_IDENT"),
'author_date' => sprintf("%s +0000",time()),
)
);
close(DIFF);
}
handle_rev();
@ -88,7 +116,7 @@ foreach my $l (@filelines) {
my ($output, $rev, $committer, $date);
if (ref $l eq 'ARRAY') {
($output, $rev, $committer, $date) = @$l;
if (!$opt_l && length($rev) > 8) {
if (!$longrev && length($rev) > 8) {
$rev = substr($rev,0,8);
}
} else {
@ -102,7 +130,6 @@ foreach my $l (@filelines) {
sub init_claim {
my ($rev) = @_;
my %revinfo = git_commit_info($rev);
for (my $i = 0; $i < @filelines; $i++) {
$filelines[$i] = [ $filelines[$i], '', '', '', 1];
# line,
@ -117,7 +144,9 @@ sub init_claim {
sub handle_rev {
my $i = 0;
my %seen;
while (my $rev = shift @revqueue) {
next if $seen{$rev}++;
my %revinfo = git_commit_info($rev);
@ -143,8 +172,8 @@ sub handle_rev {
sub git_rev_list {
my ($rev, $file) = @_;
if ($opt_S) {
open(P, '<' . $opt_S);
if ($rev_file) {
open(P, '<' . $rev_file);
} else {
open(P,"-|","git-rev-list","--parents","--remove-empty",$rev,"--",$file)
or die "Failed to exec git-rev-list: $!";
@ -216,24 +245,31 @@ sub git_find_parent {
sub git_diff_parse {
my ($parent, $rev, %revinfo) = @_;
my ($ri, $pi) = (0,0);
open(DIFF,"-|","git-diff-tree","-M","-p",$rev,$parent,"--",
$revs{$rev}{'filename'}, $revs{$parent}{'filename'})
or die "Failed to call git-diff for annotation: $!";
_git_diff_parse(*DIFF, $parent, $rev, %revinfo);
close(DIFF);
}
sub _git_diff_parse {
my ($diff, $parent, $rev, %revinfo) = @_;
my ($ri, $pi) = (0,0);
my $slines = $revs{$rev}{'lines'};
my @plines;
my $gotheader = 0;
my ($remstart, $remlength, $addstart, $addlength);
my ($hunk_start, $hunk_index, $hunk_adds);
my ($remstart);
my ($hunk_start, $hunk_index);
while(<DIFF>) {
chomp;
if (m/^@@ -(\d+),(\d+) \+(\d+),(\d+)/) {
($remstart, $remlength, $addstart, $addlength) = ($1, $2, $3, $4);
$remstart = $1;
# Adjust for 0-based arrays
$remstart--;
$addstart--;
# Reinit hunk tracking.
$hunk_start = $remstart;
$hunk_index = 0;
@ -279,7 +315,6 @@ sub git_diff_parse {
}
$hunk_index++;
}
close(DIFF);
for (my $i = $ri; $i < @{$slines} ; $i++) {
push @plines, $slines->[$ri++];
}
@ -295,13 +330,13 @@ sub get_line {
}
sub git_cat_file {
my ($parent, $filename) = @_;
return () unless defined $parent && defined $filename;
my $blobline = `git-ls-tree $parent $filename`;
my ($mode, $type, $blob, $tfilename) = split(/\s+/, $blobline, 4);
my ($rev, $filename) = @_;
return () unless defined $rev && defined $filename;
open(C,"-|","git-cat-file", "blob", $blob)
or die "Failed to git-cat-file blob $blob (rev $parent, file $filename): " . $!;
my $blob = git_ls_tree($rev, $filename);
open(C,"-|","git","cat-file", "blob", $blob)
or die "Failed to git-cat-file blob $blob (rev $rev, file $filename): " . $!;
my @lines;
while(<C>) {
@ -313,6 +348,25 @@ sub git_cat_file {
return @lines;
}
sub git_ls_tree {
my ($rev, $filename) = @_;
open(T,"-|","git","ls-tree",$rev,$filename)
or die "Failed to call git ls-tree: $!";
my ($mode, $type, $blob, $tfilename);
while(<T>) {
($mode, $type, $blob, $tfilename) = split(/\s+/, $_, 4);
last if ($tfilename eq $filename);
}
close(T);
return $blob if $filename eq $filename;
die "git-ls-tree failed to find blob for $filename";
}
sub claim_line {
my ($floffset, $rev, $lines, %revinfo) = @_;
@ -354,3 +408,25 @@ sub format_date {
return strftime("%Y-%m-%d %H:%M:%S " . $timezone, gmtime($timestamp));
}
# Copied from git-send-email.perl - We need a Git.pm module..
sub gitvar {
my ($var) = @_;
my $fh;
my $pid = open($fh, '-|');
die "$!" unless defined $pid;
if (!$pid) {
exec('git-var', $var) or die "$!";
}
my ($val) = <$fh>;
close $fh or die "$!";
chomp($val);
return $val;
}
sub gitvar_name {
my ($name) = @_;
my $val = gitvar($name);
my @field = split(/\s+/, $val);
return join(' ', @field[0...(@field-4)]);
}