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