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:
parent
9d7f73d43f
commit
87475f4dfc
@ -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)
|
|
||||||
or die "Failed to open filename: $!";
|
|
||||||
|
|
||||||
while(<F>) {
|
if (defined $starting_rev) {
|
||||||
chomp;
|
@filelines = git_cat_file($starting_rev, $filename);
|
||||||
push @filelines, $_;
|
} 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 %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)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user