X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=git-annotate.perl;h=6db2f48241d0f393e43413ddb52d0986c523e372;hb=bc483d04804888c2da64e4c6685c2860a9041122;hp=feea0a2d81c1d4841fe46d230a0ebfe87a4f82f2;hpb=2dcdb4c69737185f0b0e9fe08ba52b95b84f13da;p=git.git diff --git a/git-annotate.perl b/git-annotate.perl index feea0a2d8..6db2f4824 100755 --- a/git-annotate.perl +++ b/git-annotate.perl @@ -10,9 +10,10 @@ use warnings; use strict; use Getopt::Long; use POSIX qw(strftime gmtime); +use File::Basename qw(basename dirname); sub usage() { - print STDERR 'Usage: ${\basename $0} [-s] [-S revs-file] file [ revision ] + print STDERR "Usage: ${\basename $0} [-s] [-S revs-file] file [ revision ] -l, --long Show long rev (Defaults off) -t, --time @@ -20,10 +21,10 @@ sub usage() { -r, --rename Follow renames (Defaults on). -S, --rev-file revs-file - use revs from revs-file instead of calling git-rev-list + Use revs from revs-file instead of calling git-rev-list -h, --help This message. -'; +"; exit(1); } @@ -35,7 +36,7 @@ my $rc = GetOptions( "long|l" => \$longrev, "help|h" => \$help, "rename|r" => \$rename, "rev-file|S=s" => \$rev_file); -if (!$rc or $help) { +if (!$rc or $help or !@ARGV) { usage(); } @@ -101,10 +102,10 @@ while (my $bound = pop @stack) { push @revqueue, $head; init_claim( defined $starting_rev ? $head : 'dirty'); unless (defined $starting_rev) { - my $diff = open_pipe("git","diff","-R", "HEAD", "--",$filename) + my $diff = open_pipe("git","diff","HEAD", "--",$filename) or die "Failed to call git diff to check for dirty state: $!"; - _git_diff_parse($diff, $head, "dirty", ( + _git_diff_parse($diff, [$head], "dirty", ( 'author' => gitvar_name("GIT_AUTHOR_IDENT"), 'author_date' => sprintf("%s +0000",time()), ) @@ -153,14 +154,13 @@ sub handle_rev { my %revinfo = git_commit_info($rev); - foreach my $p (@{$revs{$rev}{'parents'}}) { - - git_diff_parse($p, $rev, %revinfo); - push @revqueue, $p; - } + if (exists $revs{$rev}{parents} && + scalar @{$revs{$rev}{parents}} != 0) { + git_diff_parse($revs{$rev}{'parents'}, $rev, %revinfo); + push @revqueue, @{$revs{$rev}{'parents'}}; - if (scalar @{$revs{$rev}{parents}} == 0) { + } else { # We must be at the initial rev here, so claim everything that is left. for (my $i = 0; $i < @{$revs{$rev}{lines}}; $i++) { if (ref ${$revs{$rev}{lines}}[$i] eq '' || ${$revs{$rev}{lines}}[$i][1] eq '') { @@ -208,6 +208,9 @@ sub find_parent_renames { while (my $change = <$patch>) { chomp $change; my $filename = <$patch>; + if (!defined $filename) { + next; + } chomp $filename; if ($change =~ m/^[AMD]$/ ) { @@ -248,89 +251,171 @@ sub git_find_parent { # Get a diff between the current revision and a parent. # Record the commit information that results. sub git_diff_parse { - my ($parent, $rev, %revinfo) = @_; + my ($parents, $rev, %revinfo) = @_; + + my @filenames = ( $revs{$rev}{'filename'} ); + foreach my $parent (@$parents) { + push @filenames, $revs{$parent}{'filename'}; + } - my $diff = open_pipe("git-diff-tree","-M","-p",$rev,$parent,"--", - $revs{$rev}{'filename'}, $revs{$parent}{'filename'}) + my $diff = open_pipe("git-diff-tree","-M","-p","-c",$rev,"--", + @filenames ) or die "Failed to call git-diff for annotation: $!"; - _git_diff_parse($diff, $parent, $rev, %revinfo); + _git_diff_parse($diff, $parents, $rev, %revinfo); close($diff); } sub _git_diff_parse { - my ($diff, $parent, $rev, %revinfo) = @_; + my ($diff, $parents, $rev, %revinfo) = @_; + + my $ri = 0; - my ($ri, $pi) = (0,0); my $slines = $revs{$rev}{'lines'}; - my @plines; + my (%plines, %pi); my $gotheader = 0; my ($remstart); - my ($hunk_start, $hunk_index); + my $parent_count = @$parents; + + my $diff_header_regexp = "^@"; + $diff_header_regexp .= "@" x @$parents; + $diff_header_regexp .= ' -\d+,\d+' x @$parents; + $diff_header_regexp .= ' \+(\d+),\d+'; + + my %claim_regexps; + my $allparentplus = '^' . '\\+' x @$parents . '(.*)$'; + + { + my $i = 0; + foreach my $parent (@$parents) { + + $pi{$parent} = 0; + my $r = '^' . '.' x @$parents . '(.*)$'; + my $p = $r; + substr($p,$i+1, 1) = '\\+'; + + my $m = $r; + substr($m,$i+1, 1) = '-'; + + $claim_regexps{$parent}{plus} = $p; + $claim_regexps{$parent}{minus} = $m; + + $plines{$parent} = []; + + $i++; + } + } + + DIFF: while(<$diff>) { chomp; - if (m/^@@ -(\d+),(\d+) \+(\d+),(\d+)/) { - $remstart = $1; - # Adjust for 0-based arrays - $remstart--; - # Reinit hunk tracking. - $hunk_start = $remstart; - $hunk_index = 0; + if (m/$diff_header_regexp/) { + $remstart = $1 - 1; + # (0-based arrays) + $gotheader = 1; - for (my $i = $ri; $i < $remstart; $i++) { - $plines[$pi++] = $slines->[$i]; - $ri++; + printf("Copying from %d to %d\n", $ri, $remstart); + foreach my $parent (@$parents) { + for (my $i = $ri; $i < $remstart; $i++) { + $plines{$parent}[$pi{$parent}++] = $slines->[$i]; + } } - next; - } elsif (!$gotheader) { - next; - } + $ri = $remstart; - if (m/^\+(.*)$/) { - my $line = $1; - $plines[$pi++] = [ $line, '', '', '', 0 ]; - next; + next DIFF; - } elsif (m/^-(.*)$/) { - my $line = $1; - if (get_line($slines, $ri) eq $line) { - # Found a match, claim - claim_line($ri, $rev, $slines, %revinfo); - } else { - die sprintf("Sync error: %d/%d\n|%s\n|%s\n%s => %s\n", - $ri, $hunk_start + $hunk_index, - $line, - get_line($slines, $ri), - $rev, $parent); - } - $ri++; + } elsif (!$gotheader) { + # Skip over the leadin. + next DIFF; + } - } elsif (m/^\\/) { + if (m/^\\/) { ; # Skip \No newline at end of file. # But this can be internationalized, so only look # for an initial \ } else { - if (substr($_,1) ne get_line($slines,$ri) ) { - die sprintf("Line %d (%d) does not match:\n|%s\n|%s\n%s => %s\n", - $hunk_start + $hunk_index, $ri, - substr($_,1), - get_line($slines,$ri), - $rev, $parent); + my %claims = (); + my $negclaim = 0; + my $allclaimed = 0; + my $line; + + if (m/$allparentplus/) { + claim_line($ri, $rev, $slines, %revinfo); + $allclaimed = 1; + + } + + PARENT: + foreach my $parent (keys %claim_regexps) { + my $m = $claim_regexps{$parent}{minus}; + my $p = $claim_regexps{$parent}{plus}; + + if (m/$m/) { + $line = $1; + $plines{$parent}[$pi{$parent}++] = [ $line, '', '', '', 0 ]; + $negclaim++; + + } elsif (m/$p/) { + $line = $1; + if (get_line($slines, $ri) eq $line) { + # Found a match, claim + $claims{$parent}++; + + } else { + die sprintf("Sync error: %d\n|%s\n|%s\n%s => %s\n", + $ri, $line, + get_line($slines, $ri), + $rev, $parent); + } + } + } + + if (%claims) { + foreach my $parent (@$parents) { + next if $claims{$parent} || $allclaimed; + $plines{$parent}[$pi{$parent}++] = $slines->[$ri]; + #[ $line, '', '', '', 0 ]; + } + $ri++; + + } elsif ($negclaim) { + next DIFF; + + } else { + if (substr($_,scalar @$parents) ne get_line($slines,$ri) ) { + foreach my $parent (@$parents) { + printf("parent %s is on line %d\n", $parent, $pi{$parent}); + } + + die sprintf("Line %d, does not match:\n|%s|\n|%s|\n%s\n", + $ri, + substr($_,scalar @$parents), + get_line($slines,$ri), $rev); + } + foreach my $parent (@$parents) { + $plines{$parent}[$pi{$parent}++] = $slines->[$ri]; + } + $ri++; } - $plines[$pi++] = $slines->[$ri++]; } - $hunk_index++; } + for (my $i = $ri; $i < @{$slines} ; $i++) { - push @plines, $slines->[$ri++]; + foreach my $parent (@$parents) { + push @{$plines{$parent}}, $slines->[$ri]; + } + $ri++; + } + + foreach my $parent (@$parents) { + $revs{$parent}{lines} = $plines{$parent}; } - $revs{$parent}{lines} = \@plines; return; }