Merge branch 'pw/add-p-recount'

"git add -p" has been lazy in coalescing split patches before
passing the result to underlying "git apply", leading to corner
case bugs; the logic to prepare the patch to be applied after hunk
selections has been tightened.

* pw/add-p-recount:
  add -p: don't rely on apply's '--recount' option
  add -p: fix counting when splitting and coalescing
  add -p: calculate offset delta for edited patches
  add -p: adjust offsets of subsequent hunks when one is skipped
  t3701: add failing test for pathological context lines
  t3701: don't hard code sha1 hash values
  t3701: use test_write_lines and write_script
  t3701: indent here documents
  add -i: add function to format hunk header
This commit is contained in:
Junio C Hamano
2018-03-14 12:01:04 -07:00
2 changed files with 249 additions and 150 deletions

View File

@ -677,7 +677,7 @@ sub add_untracked_cmd {
sub run_git_apply {
my $cmd = shift;
my $fh;
open $fh, '| git ' . $cmd . " --recount --allow-overlap";
open $fh, '| git ' . $cmd . " --allow-overlap";
print $fh @_;
return close $fh;
}
@ -751,6 +751,15 @@ sub parse_hunk_header {
return ($o_ofs, $o_cnt, $n_ofs, $n_cnt);
}
sub format_hunk_header {
my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) = @_;
return ("@@ -$o_ofs" .
(($o_cnt != 1) ? ",$o_cnt" : '') .
" +$n_ofs" .
(($n_cnt != 1) ? ",$n_cnt" : '') .
" @@\n");
}
sub split_hunk {
my ($text, $display) = @_;
my @split = ();
@ -784,6 +793,11 @@ sub split_hunk {
while (++$i < @$text) {
my $line = $text->[$i];
my $display = $display->[$i];
if ($line =~ /^\\/) {
push @{$this->{TEXT}}, $line;
push @{$this->{DISPLAY}}, $display;
next;
}
if ($line =~ /^ /) {
if ($this->{ADDDEL} &&
!defined $next_hunk_start) {
@ -838,11 +852,7 @@ sub split_hunk {
my $o_cnt = $hunk->{OCNT};
my $n_cnt = $hunk->{NCNT};
my $head = ("@@ -$o_ofs" .
(($o_cnt != 1) ? ",$o_cnt" : '') .
" +$n_ofs" .
(($n_cnt != 1) ? ",$n_cnt" : '') .
" @@\n");
my $head = format_hunk_header($o_ofs, $o_cnt, $n_ofs, $n_cnt);
my $display_head = $head;
unshift @{$hunk->{TEXT}}, $head;
if ($diff_use_color) {
@ -886,6 +896,9 @@ sub merge_hunk {
$n_cnt++;
push @line, $line;
next;
} elsif ($line =~ /^\\/) {
push @line, $line;
next;
}
last if ($o1_ofs <= $ofs);
@ -904,6 +917,9 @@ sub merge_hunk {
$n_cnt++;
push @line, $line;
next;
} elsif ($line =~ /^\\/) {
push @line, $line;
next;
}
$ofs++;
$o_cnt++;
@ -912,11 +928,7 @@ sub merge_hunk {
}
push @line, $line;
}
my $head = ("@@ -$o0_ofs" .
(($o_cnt != 1) ? ",$o_cnt" : '') .
" +$n0_ofs" .
(($n_cnt != 1) ? ",$n_cnt" : '') .
" @@\n");
my $head = format_hunk_header($o0_ofs, $o_cnt, $n0_ofs, $n_cnt);
@{$prev->{TEXT}} = ($head, @line);
}
@ -925,14 +937,35 @@ sub coalesce_overlapping_hunks {
my @out = ();
my ($last_o_ctx, $last_was_dirty);
my $ofs_delta = 0;
for (grep { $_->{USE} } @in) {
for (@in) {
if ($_->{TYPE} ne 'hunk') {
push @out, $_;
next;
}
my $text = $_->{TEXT};
my ($o_ofs) = parse_hunk_header($text->[0]);
my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) =
parse_hunk_header($text->[0]);
unless ($_->{USE}) {
$ofs_delta += $o_cnt - $n_cnt;
# If this hunk has been edited then subtract
# the delta that is due to the edit.
if ($_->{OFS_DELTA}) {
$ofs_delta -= $_->{OFS_DELTA};
}
next;
}
if ($ofs_delta) {
$n_ofs += $ofs_delta;
$_->{TEXT}->[0] = format_hunk_header($o_ofs, $o_cnt,
$n_ofs, $n_cnt);
}
# If this hunk was edited then adjust the offset delta
# to reflect the edit.
if ($_->{OFS_DELTA}) {
$ofs_delta += $_->{OFS_DELTA};
}
if (defined $last_o_ctx &&
$o_ofs <= $last_o_ctx &&
!$_->{DIRTY} &&
@ -1004,6 +1037,30 @@ marked for discarding."),
marked for applying."),
);
sub recount_edited_hunk {
local $_;
my ($oldtext, $newtext) = @_;
my ($o_cnt, $n_cnt) = (0, 0);
for (@{$newtext}[1..$#{$newtext}]) {
my $mode = substr($_, 0, 1);
if ($mode eq '-') {
$o_cnt++;
} elsif ($mode eq '+') {
$n_cnt++;
} elsif ($mode eq ' ') {
$o_cnt++;
$n_cnt++;
}
}
my ($o_ofs, undef, $n_ofs, undef) =
parse_hunk_header($newtext->[0]);
$newtext->[0] = format_hunk_header($o_ofs, $o_cnt, $n_ofs, $n_cnt);
my (undef, $orig_o_cnt, undef, $orig_n_cnt) =
parse_hunk_header($oldtext->[0]);
# Return the change in the number of lines inserted by this hunk
return $orig_o_cnt - $orig_n_cnt - $o_cnt + $n_cnt;
}
sub edit_hunk_manually {
my ($oldtext) = @_;
@ -1102,25 +1159,32 @@ sub prompt_yesno {
}
sub edit_hunk_loop {
my ($head, $hunk, $ix) = @_;
my $text = $hunk->[$ix]->{TEXT};
my ($head, $hunks, $ix) = @_;
my $hunk = $hunks->[$ix];
my $text = $hunk->{TEXT};
while (1) {
$text = edit_hunk_manually($text);
if (!defined $text) {
my $newtext = edit_hunk_manually($text);
if (!defined $newtext) {
return undef;
}
my $newhunk = {
TEXT => $text,
TYPE => $hunk->[$ix]->{TYPE},
TEXT => $newtext,
TYPE => $hunk->{TYPE},
USE => 1,
DIRTY => 1,
};
$newhunk->{OFS_DELTA} = recount_edited_hunk($text, $newtext);
# If this hunk has already been edited then add the
# offset delta of the previous edit to get the real
# delta from the original unedited hunk.
$hunk->{OFS_DELTA} and
$newhunk->{OFS_DELTA} += $hunk->{OFS_DELTA};
if (diff_applies($head,
@{$hunk}[0..$ix-1],
@{$hunks}[0..$ix-1],
$newhunk,
@{$hunk}[$ix+1..$#{$hunk}])) {
$newhunk->{DISPLAY} = [color_diff(@{$text})];
@{$hunks}[$ix+1..$#{$hunks}])) {
$newhunk->{DISPLAY} = [color_diff(@{$newtext})];
return $newhunk;
}
else {