From 3f492ba1fc4e63073378cc097154bed1ed2563c8 Mon Sep 17 00:00:00 2001 From: Ryan Anderson Date: Mon, 3 Jul 2006 21:30:02 -0400 Subject: annotate: Correct most merge following to annotate correctly. There is still a bug involving octopus merges, somewhere, but this gets normal merges correct, so it's still an improvement over the existing version. Signed-off-by: Ryan Anderson Signed-off-by: Junio C Hamano --- git-annotate.perl | 197 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 139 insertions(+), 58 deletions(-) (limited to 'git-annotate.perl') diff --git a/git-annotate.perl b/git-annotate.perl index a6a7a482cd..6db2f48241 100755 --- a/git-annotate.perl +++ b/git-annotate.perl @@ -102,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()), ) @@ -154,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 '') { @@ -252,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 $diff = open_pipe("git-diff-tree","-M","-p",$rev,$parent,"--", - $revs{$rev}{'filename'}, $revs{$parent}{'filename'}) + my @filenames = ( $revs{$rev}{'filename'} ); + foreach my $parent (@$parents) { + push @filenames, $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; } -- cgit v1.2.1 From d4ad9b0484fa17de3d161f48ae58190ec2cccd45 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 2 Aug 2006 12:34:44 -0400 Subject: git-annotate: remove extraneous debugging line Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- git-annotate.perl | 1 - 1 file changed, 1 deletion(-) (limited to 'git-annotate.perl') diff --git a/git-annotate.perl b/git-annotate.perl index 6db2f48241..505b5ccb28 100755 --- a/git-annotate.perl +++ b/git-annotate.perl @@ -317,7 +317,6 @@ sub _git_diff_parse { $gotheader = 1; - 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]; -- cgit v1.2.1 From 7c49cb288173ab5264b3b9e4257aeeb13388334c Mon Sep 17 00:00:00 2001 From: Ryan Anderson Date: Mon, 7 Aug 2006 05:11:24 -0700 Subject: annotate: Fix bug when parsing merges with differing real and logical parents. Signed-off-by: Ryan Anderson Signed-off-by: Junio C Hamano --- git-annotate.perl | 128 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 122 insertions(+), 6 deletions(-) (limited to 'git-annotate.perl') diff --git a/git-annotate.perl b/git-annotate.perl index 505b5ccb28..215ed26f3a 100755 --- a/git-annotate.perl +++ b/git-annotate.perl @@ -147,7 +147,7 @@ sub init_claim { sub handle_rev { - my $i = 0; + my $revseen = 0; my %seen; while (my $rev = shift @revqueue) { next if $seen{$rev}++; @@ -247,22 +247,129 @@ sub git_find_parent { return $parent; } +sub git_find_all_parents { + my ($rev) = @_; + + my $revparent = open_pipe("git-rev-list","--remove-empty", "--parents","--max-count=1","$rev") + or die "Failed to open git-rev-list to find a single parent: $!"; + + my $parentline = <$revparent>; + chomp $parentline; + my ($origrev, @parents) = split m/\s+/, $parentline; + + close($revparent); + + return @parents; +} + +sub git_merge_base { + my ($rev1, $rev2) = @_; + + my $mb = open_pipe("git-merge-base", $rev1, $rev2) + or die "Failed to open git-merge-base: $!"; + + my $base = <$mb>; + chomp $base; + + close($mb); + + return $base; +} + +# Construct a set of pseudo parents that are in the same order, +# and the same quantity as the real parents, +# but whose SHA1s are as similar to the logical parents +# as possible. +sub get_pseudo_parents { + my ($all, $fake) = @_; + + my @all = @$all; + my @fake = @$fake; + + my @pseudo; + + my %fake = map {$_ => 1} @fake; + my %seenfake; + + my $fakeidx = 0; + foreach my $p (@all) { + if (exists $fake{$p}) { + if ($fake[$fakeidx] ne $p) { + die sprintf("parent mismatch: %s != %s\nall:%s\nfake:%s\n", + $fake[$fakeidx], $p, + join(", ", @all), + join(", ", @fake), + ); + } + + push @pseudo, $p; + $fakeidx++; + $seenfake{$p}++; + + } else { + my $base = git_merge_base($fake[$fakeidx], $p); + if ($base ne $fake[$fakeidx]) { + die sprintf("Result of merge-base doesn't match fake: %s,%s != %s\n", + $fake[$fakeidx], $p, $base); + } + + # The details of how we parse the diffs + # mean that we cannot have a duplicate + # revision in the list, so if we've already + # seen the revision we would normally add, just use + # the actual revision. + if ($seenfake{$base}) { + push @pseudo, $p; + } else { + push @pseudo, $base; + $seenfake{$base}++; + } + } + } + + return @pseudo; +} + # Get a diff between the current revision and a parent. # Record the commit information that results. sub git_diff_parse { my ($parents, $rev, %revinfo) = @_; + my @pseudo_parents; + my @command = ("git-diff-tree"); + my $revision_spec; + + if (scalar @$parents == 1) { + + $revision_spec = join("..", $parents->[0], $rev); + @pseudo_parents = @$parents; + } else { + my @all_parents = git_find_all_parents($rev); + + if (@all_parents != @$parents) { + @pseudo_parents = get_pseudo_parents(\@all_parents, $parents); + } else { + @pseudo_parents = @$parents; + } + + $revision_spec = $rev; + push @command, "-c"; + } + my @filenames = ( $revs{$rev}{'filename'} ); + foreach my $parent (@$parents) { push @filenames, $revs{$parent}{'filename'}; } - my $diff = open_pipe("git-diff-tree","-M","-p","-c",$rev,"--", - @filenames ) + push @command, "-p", "-M", $revision_spec, "--", @filenames; + + + my $diff = open_pipe( @command ) or die "Failed to call git-diff for annotation: $!"; - _git_diff_parse($diff, $parents, $rev, %revinfo); + _git_diff_parse($diff, \@pseudo_parents, $rev, %revinfo); close($diff); } @@ -283,6 +390,7 @@ sub _git_diff_parse { $diff_header_regexp .= "@" x @$parents; $diff_header_regexp .= ' -\d+,\d+' x @$parents; $diff_header_regexp .= ' \+(\d+),\d+'; + $diff_header_regexp .= " " . ("@" x @$parents); my %claim_regexps; my $allparentplus = '^' . '\\+' x @$parents . '(.*)$'; @@ -311,6 +419,7 @@ sub _git_diff_parse { DIFF: while(<$diff>) { chomp; + #printf("%d:%s:\n", $gotheader, $_); if (m/$diff_header_regexp/) { $remstart = $1 - 1; # (0-based arrays) @@ -391,10 +500,17 @@ sub _git_diff_parse { printf("parent %s is on line %d\n", $parent, $pi{$parent}); } + my @context; + for (my $i = -2; $i < 2; $i++) { + push @context, get_line($slines, $ri + $i); + } + my $context = join("\n", @context); + + my $justline = substr($_, scalar @$parents); die sprintf("Line %d, does not match:\n|%s|\n|%s|\n%s\n", $ri, - substr($_,scalar @$parents), - get_line($slines,$ri), $rev); + $justline, + $context); } foreach my $parent (@$parents) { $plines{$parent}[$pi{$parent}++] = $slines->[$ri]; -- cgit v1.2.1