Merge branch 'jk/add-i-diff-filter'
authorJunio C Hamano <gitster@pobox.com>
Wed, 14 Mar 2018 19:01:05 +0000 (12:01 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 14 Mar 2018 19:01:05 +0000 (12:01 -0700)
The "interactive.diffFilter" used by "git add -i" must retain
one-to-one correspondence between its input and output, but it was
not enforced and caused end-user confusion. We now at least make
sure the filtered result has the same number of lines as its input
to detect a broken filter.

* jk/add-i-diff-filter:
add--interactive: detect bogus diffFilter output
t3701: add a test for interactive.diffFilter

1  2 
git-add--interactive.perl
t/t3701-add-interactive.sh
index 7a0c95fd0deb685a43fd3aa4bd88515ce0eb9fcd,ff02008abeceada0f2e1e423393b6119c4b731af..d190469cd8b5e1dc427b4029d0d1fb937faef584
@@@ -677,7 -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;
  }
@@@ -705,6 -705,14 +705,14 @@@ sub parse_diff 
        }
        my (@hunk) = { TEXT => [], DISPLAY => [], TYPE => 'header' };
  
+       if (@colored && @colored != @diff) {
+               print STDERR
+                 "fatal: mismatched output from interactive.diffFilter\n",
+                 "hint: Your filter must maintain a one-to-one correspondence\n",
+                 "hint: between its input and output lines.\n";
+               exit 1;
+       }
        for (my $i = 0; $i < @diff; $i++) {
                if ($diff[$i] =~ /^@@ /) {
                        push @hunk, { TEXT => [], DISPLAY => [],
@@@ -751,15 -759,6 +759,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 = ();
                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) {
                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) {
@@@ -896,9 -894,6 +904,9 @@@ sub merge_hunk 
                        $n_cnt++;
                        push @line, $line;
                        next;
 +              } elsif ($line =~ /^\\/) {
 +                      push @line, $line;
 +                      next;
                }
  
                last if ($o1_ofs <= $ofs);
                        $n_cnt++;
                        push @line, $line;
                        next;
 +              } elsif ($line =~ /^\\/) {
 +                      push @line, $line;
 +                      next;
                }
                $ofs++;
                $o_cnt++;
                }
                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);
  }
  
@@@ -937,35 -933,14 +945,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} &&
@@@ -1037,30 -1012,6 +1045,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) = @_;
  
@@@ -1159,32 -1110,25 +1167,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 {
@@@ -1248,13 -1192,7 +1256,13 @@@ d - do not apply this hunk or any of th
  );
  
  sub help_patch_cmd {
 -      print colored $help_color, __($help_patch_modes{$patch_mode}), "\n", __ <<EOF ;
 +      local $_;
 +      my $other = $_[0] . ",?";
 +      print colored $help_color, __($help_patch_modes{$patch_mode}), "\n",
 +              map { "$_\n" } grep {
 +                      my $c = quotemeta(substr($_, 0, 1));
 +                      $other =~ /,$c/
 +              } split "\n", __ <<EOF ;
  g - select a hunk to go to
  / - search for a hunk matching the given regex
  j - leave this hunk undecided, see next undecided hunk
@@@ -1372,39 -1310,39 +1380,39 @@@ sub display_hunks 
  
  my %patch_update_prompt_modes = (
        stage => {
 -              mode => N__("Stage mode change [y,n,q,a,d,/%s,?]? "),
 -              deletion => N__("Stage deletion [y,n,q,a,d,/%s,?]? "),
 -              hunk => N__("Stage this hunk [y,n,q,a,d,/%s,?]? "),
 +              mode => N__("Stage mode change [y,n,q,a,d%s,?]? "),
 +              deletion => N__("Stage deletion [y,n,q,a,d%s,?]? "),
 +              hunk => N__("Stage this hunk [y,n,q,a,d%s,?]? "),
        },
        stash => {
 -              mode => N__("Stash mode change [y,n,q,a,d,/%s,?]? "),
 -              deletion => N__("Stash deletion [y,n,q,a,d,/%s,?]? "),
 -              hunk => N__("Stash this hunk [y,n,q,a,d,/%s,?]? "),
 +              mode => N__("Stash mode change [y,n,q,a,d%s,?]? "),
 +              deletion => N__("Stash deletion [y,n,q,a,d%s,?]? "),
 +              hunk => N__("Stash this hunk [y,n,q,a,d%s,?]? "),
        },
        reset_head => {
 -              mode => N__("Unstage mode change [y,n,q,a,d,/%s,?]? "),
 -              deletion => N__("Unstage deletion [y,n,q,a,d,/%s,?]? "),
 -              hunk => N__("Unstage this hunk [y,n,q,a,d,/%s,?]? "),
 +              mode => N__("Unstage mode change [y,n,q,a,d%s,?]? "),
 +              deletion => N__("Unstage deletion [y,n,q,a,d%s,?]? "),
 +              hunk => N__("Unstage this hunk [y,n,q,a,d%s,?]? "),
        },
        reset_nothead => {
 -              mode => N__("Apply mode change to index [y,n,q,a,d,/%s,?]? "),
 -              deletion => N__("Apply deletion to index [y,n,q,a,d,/%s,?]? "),
 -              hunk => N__("Apply this hunk to index [y,n,q,a,d,/%s,?]? "),
 +              mode => N__("Apply mode change to index [y,n,q,a,d%s,?]? "),
 +              deletion => N__("Apply deletion to index [y,n,q,a,d%s,?]? "),
 +              hunk => N__("Apply this hunk to index [y,n,q,a,d%s,?]? "),
        },
        checkout_index => {
 -              mode => N__("Discard mode change from worktree [y,n,q,a,d,/%s,?]? "),
 -              deletion => N__("Discard deletion from worktree [y,n,q,a,d,/%s,?]? "),
 -              hunk => N__("Discard this hunk from worktree [y,n,q,a,d,/%s,?]? "),
 +              mode => N__("Discard mode change from worktree [y,n,q,a,d%s,?]? "),
 +              deletion => N__("Discard deletion from worktree [y,n,q,a,d%s,?]? "),
 +              hunk => N__("Discard this hunk from worktree [y,n,q,a,d%s,?]? "),
        },
        checkout_head => {
 -              mode => N__("Discard mode change from index and worktree [y,n,q,a,d,/%s,?]? "),
 -              deletion => N__("Discard deletion from index and worktree [y,n,q,a,d,/%s,?]? "),
 -              hunk => N__("Discard this hunk from index and worktree [y,n,q,a,d,/%s,?]? "),
 +              mode => N__("Discard mode change from index and worktree [y,n,q,a,d%s,?]? "),
 +              deletion => N__("Discard deletion from index and worktree [y,n,q,a,d%s,?]? "),
 +              hunk => N__("Discard this hunk from index and worktree [y,n,q,a,d%s,?]? "),
        },
        checkout_nothead => {
 -              mode => N__("Apply mode change to index and worktree [y,n,q,a,d,/%s,?]? "),
 -              deletion => N__("Apply deletion to index and worktree [y,n,q,a,d,/%s,?]? "),
 -              hunk => N__("Apply this hunk to index and worktree [y,n,q,a,d,/%s,?]? "),
 +              mode => N__("Apply mode change to index and worktree [y,n,q,a,d%s,?]? "),
 +              deletion => N__("Apply deletion to index and worktree [y,n,q,a,d%s,?]? "),
 +              hunk => N__("Apply this hunk to index and worktree [y,n,q,a,d%s,?]? "),
        },
  );
  
@@@ -1460,7 -1398,7 +1468,7 @@@ sub patch_update_file 
                        $other .= ',J';
                }
                if ($num > 1) {
 -                      $other .= ',g';
 +                      $other .= ',g,/';
                }
                for ($i = 0; $i < $num; $i++) {
                        if (!defined $hunk[$i]{USE}) {
                                }
                                next;
                        }
 -                      elsif ($other =~ /g/ && $line =~ /^g(.*)/) {
 +                      elsif ($line =~ /^g(.*)/) {
                                my $response = $1;
 +                              unless ($other =~ /g/) {
 +                                      error_msg __("No other hunks to goto\n");
 +                                      next;
 +                              }
                                my $no = $ix > 10 ? $ix - 10 : 0;
                                while ($response eq '') {
                                        $no = display_hunks(\@hunk, $no);
                        }
                        elsif ($line =~ m|^/(.*)|) {
                                my $regex = $1;
 +                              unless ($other =~ m|/|) {
 +                                      error_msg __("No other hunks to search\n");
 +                                      next;
 +                              }
                                if ($1 eq "") {
                                        print colored $prompt_color, __("search for regex? ");
                                        $regex = <STDIN>;
                                        next;
                                }
                        }
 -                      elsif ($other =~ /s/ && $line =~ /^s/) {
 +                      elsif ($line =~ /^s/) {
 +                              unless ($other =~ /s/) {
 +                                      error_msg __("Sorry, cannot split this hunk\n");
 +                                      next;
 +                              }
                                my @split = split_hunk($hunk[$ix]{TEXT}, $hunk[$ix]{DISPLAY});
                                if (1 < @split) {
                                        print colored $header_color, sprintf(
                                $num = scalar @hunk;
                                next;
                        }
 -                      elsif ($other =~ /e/ && $line =~ /^e/) {
 +                      elsif ($line =~ /^e/) {
 +                              unless ($other =~ /e/) {
 +                                      error_msg __("Sorry, cannot edit this hunk\n");
 +                                      next;
 +                              }
                                my $newhunk = edit_hunk_loop($head, \@hunk, $ix);
                                if (defined $newhunk) {
                                        splice @hunk, $ix, 1, $newhunk;
index a9a9478a293f97920c6e3fb6d31653d8b37989a6,9bb17f91b267224244ba1fe71ca06a33bbd2e677..b170fb02b80356455d03dcf379636778149665a6
@@@ -10,19 -10,6 +10,19 @@@ the
        test_done
  fi
  
 +diff_cmp () {
 +      for x
 +      do
 +              sed  -e '/^index/s/[0-9a-f]*[1-9a-f][0-9a-f]*\.\./1234567../' \
 +                   -e '/^index/s/\.\.[0-9a-f]*[1-9a-f][0-9a-f]*/..9abcdef/' \
 +                   -e '/^index/s/ 00*\.\./ 0000000../' \
 +                   -e '/^index/s/\.\.00*$/..0000000/' \
 +                   -e '/^index/s/\.\.00* /..0000000 /' \
 +                   "$x" >"$x.filtered"
 +      done
 +      test_cmp "$1.filtered" "$2.filtered"
 +}
 +
  test_expect_success 'setup (initial)' '
        echo content >file &&
        git add file &&
@@@ -35,20 -22,20 +35,20 @@@ test_expect_success 'status works (init
  '
  
  test_expect_success 'setup expected' '
 -cat >expected <<EOF
 -new file mode 100644
 -index 0000000..d95f3ad
 ---- /dev/null
 -+++ b/file
 -@@ -0,0 +1 @@
 -+content
 -EOF
 +      cat >expected <<-\EOF
 +      new file mode 100644
 +      index 0000000..d95f3ad
 +      --- /dev/null
 +      +++ b/file
 +      @@ -0,0 +1 @@
 +      +content
 +      EOF
  '
  
  test_expect_success 'diff works (initial)' '
        (echo d; echo 1) | git add -i >output &&
        sed -ne "/new file/,/content/p" <output >diff &&
 -      test_cmp expected diff
 +      diff_cmp expected diff
  '
  test_expect_success 'revert works (initial)' '
        git add file &&
@@@ -72,20 -59,20 +72,20 @@@ test_expect_success 'status works (comm
  '
  
  test_expect_success 'setup expected' '
 -cat >expected <<EOF
 -index 180b47c..b6f2c08 100644
 ---- a/file
 -+++ b/file
 -@@ -1 +1,2 @@
 - baseline
 -+content
 -EOF
 +      cat >expected <<-\EOF
 +      index 180b47c..b6f2c08 100644
 +      --- a/file
 +      +++ b/file
 +      @@ -1 +1,2 @@
 +       baseline
 +      +content
 +      EOF
  '
  
  test_expect_success 'diff works (commit)' '
        (echo d; echo 1) | git add -i >output &&
        sed -ne "/^index/,/content/p" <output >diff &&
 -      test_cmp expected diff
 +      diff_cmp expected diff
  '
  test_expect_success 'revert works (commit)' '
        git add file &&
  
  
  test_expect_success 'setup expected' '
 -cat >expected <<EOF
 -EOF
 -'
 -
 -test_expect_success 'setup fake editor' '
 -      >fake_editor.sh &&
 -      chmod a+x fake_editor.sh &&
 -      test_set_editor "$(pwd)/fake_editor.sh"
 +      cat >expected <<-\EOF
 +      EOF
  '
  
  test_expect_success 'dummy edit works' '
 +      test_set_editor : &&
        (echo e; echo a) | git add -p &&
        git diff > diff &&
 -      test_cmp expected diff
 +      diff_cmp expected diff
  '
  
  test_expect_success 'setup patch' '
 -cat >patch <<EOF
 -@@ -1,1 +1,4 @@
 - this
 -+patch
 --does not
 - apply
 -EOF
 +      cat >patch <<-\EOF
 +      @@ -1,1 +1,4 @@
 +       this
 +      +patch
 +      -does not
 +       apply
 +      EOF
  '
  
  test_expect_success 'setup fake editor' '
 -      echo "#!$SHELL_PATH" >fake_editor.sh &&
 -      cat >>fake_editor.sh <<\EOF &&
 -mv -f "$1" oldpatch &&
 -mv -f patch "$1"
 -EOF
 -      chmod a+x fake_editor.sh &&
 +      write_script "fake_editor.sh" <<-\EOF &&
 +      mv -f "$1" oldpatch &&
 +      mv -f patch "$1"
 +      EOF
        test_set_editor "$(pwd)/fake_editor.sh"
  '
  
@@@ -132,10 -126,10 +132,10 @@@ test_expect_success 'bad edit rejected
  '
  
  test_expect_success 'setup patch' '
 -cat >patch <<EOF
 -this patch
 -is garbage
 -EOF
 +      cat >patch <<-\EOF
 +      this patch
 +      is garbage
 +      EOF
  '
  
  test_expect_success 'garbage edit rejected' '
  '
  
  test_expect_success 'setup patch' '
 -cat >patch <<EOF
 -@@ -1,0 +1,0 @@
 - baseline
 -+content
 -+newcontent
 -+lines
 -EOF
 +      cat >patch <<-\EOF
 +      @@ -1,0 +1,0 @@
 +       baseline
 +      +content
 +      +newcontent
 +      +lines
 +      EOF
  '
  
  test_expect_success 'setup expected' '
 -cat >expected <<EOF
 -diff --git a/file b/file
 -index b5dd6c9..f910ae9 100644
 ---- a/file
 -+++ b/file
 -@@ -1,4 +1,4 @@
 - baseline
 - content
 --newcontent
 -+more
 - lines
 -EOF
 +      cat >expected <<-\EOF
 +      diff --git a/file b/file
 +      index b5dd6c9..f910ae9 100644
 +      --- a/file
 +      +++ b/file
 +      @@ -1,4 +1,4 @@
 +       baseline
 +       content
 +      -newcontent
 +      +more
 +       lines
 +      EOF
  '
  
  test_expect_success 'real edit works' '
        (echo e; echo n; echo d) | git add -p &&
        git diff >output &&
 -      test_cmp expected output
 +      diff_cmp expected output
  '
  
  test_expect_success 'skip files similarly as commit -a' '
        git reset &&
        git commit -am commit &&
        git diff >expected &&
 -      test_cmp expected output &&
 +      diff_cmp expected output &&
        git reset --hard HEAD^
  '
  rm -f .gitignore
@@@ -228,67 -222,52 +228,67 @@@ test_expect_success 'setup again' 
  
  # Write the patch file with a new line at the top and bottom
  test_expect_success 'setup patch' '
 -cat >patch <<EOF
 -index 180b47c..b6f2c08 100644
 ---- a/file
 -+++ b/file
 -@@ -1,2 +1,4 @@
 -+firstline
 - baseline
 - content
 -+lastline
 -EOF
 -'
 -
 -# Expected output, similar to the patch but w/ diff at the top
 +      cat >patch <<-\EOF
 +      index 180b47c..b6f2c08 100644
 +      --- a/file
 +      +++ b/file
 +      @@ -1,2 +1,4 @@
 +      +firstline
 +       baseline
 +       content
 +      +lastline
 +      \ No newline at end of file
 +      EOF
 +'
 +
 +# Expected output, diff is similar to the patch but w/ diff at the top
  test_expect_success 'setup expected' '
 -cat >expected <<EOF
 -diff --git a/file b/file
 -index b6f2c08..61b9053 100755
 ---- a/file
 -+++ b/file
 -@@ -1,2 +1,4 @@
 -+firstline
 - baseline
 - content
 -+lastline
 -EOF
 +      echo diff --git a/file b/file >expected &&
 +      cat patch |sed "/^index/s/ 100644/ 100755/" >>expected &&
 +      cat >expected-output <<-\EOF
 +      --- a/file
 +      +++ b/file
 +      @@ -1,2 +1,4 @@
 +      +firstline
 +       baseline
 +       content
 +      +lastline
 +      \ No newline at end of file
 +      @@ -1,2 +1,3 @@
 +      +firstline
 +       baseline
 +       content
 +      @@ -1,2 +2,3 @@
 +       baseline
 +       content
 +      +lastline
 +      \ No newline at end of file
 +      EOF
  '
  
  # Test splitting the first patch, then adding both
 -test_expect_success 'add first line works' '
 +test_expect_success C_LOCALE_OUTPUT 'add first line works' '
        git commit -am "clear local changes" &&
        git apply patch &&
 -      (echo s; echo y; echo y) | git add -p file &&
 -      git diff --cached > diff &&
 -      test_cmp expected diff
 +      printf "%s\n" s y y | git add -p file 2>error |
 +              sed -n -e "s/^Stage this hunk[^@]*\(@@ .*\)/\1/" \
 +                     -e "/^[-+@ \\\\]"/p  >output &&
 +      test_must_be_empty error &&
 +      git diff --cached >diff &&
 +      diff_cmp expected diff &&
 +      test_cmp expected-output output
  '
  
  test_expect_success 'setup expected' '
 -cat >expected <<EOF
 -diff --git a/non-empty b/non-empty
 -deleted file mode 100644
 -index d95f3ad..0000000
 ---- a/non-empty
 -+++ /dev/null
 -@@ -1 +0,0 @@
 --content
 -EOF
 +      cat >expected <<-\EOF
 +      diff --git a/non-empty b/non-empty
 +      deleted file mode 100644
 +      index d95f3ad..0000000
 +      --- a/non-empty
 +      +++ /dev/null
 +      @@ -1 +0,0 @@
 +      -content
 +      EOF
  '
  
  test_expect_success 'deleting a non-empty file' '
        rm non-empty &&
        echo y | git add -p non-empty &&
        git diff --cached >diff &&
 -      test_cmp expected diff
 +      diff_cmp expected diff
  '
  
  test_expect_success 'setup expected' '
 -cat >expected <<EOF
 -diff --git a/empty b/empty
 -deleted file mode 100644
 -index e69de29..0000000
 -EOF
 +      cat >expected <<-\EOF
 +      diff --git a/empty b/empty
 +      deleted file mode 100644
 +      index e69de29..0000000
 +      EOF
  '
  
  test_expect_success 'deleting an empty file' '
        rm empty &&
        echo y | git add -p empty &&
        git diff --cached >diff &&
 -      test_cmp expected diff
 +      diff_cmp expected diff
  '
  
  test_expect_success 'split hunk setup' '
        git reset --hard &&
 -      for i in 10 20 30 40 50 60
 -      do
 -              echo $i
 -      done >test &&
 +      test_write_lines 10 20 30 40 50 60 >test &&
        git add test &&
        test_tick &&
        git commit -m test &&
  
 -      for i in 10 15 20 21 22 23 24 30 40 50 60
 -      do
 -              echo $i
 -      done >test
 +      test_write_lines 10 15 20 21 22 23 24 30 40 50 60 >test
  '
  
  test_expect_success 'split hunk "add -p (edit)"' '
  '
  
  test_expect_failure 'split hunk "add -p (no, yes, edit)"' '
 -      cat >test <<-\EOF &&
 -      5
 -      10
 -      20
 -      21
 -      30
 -      31
 -      40
 -      50
 -      60
 -      EOF
 +      test_write_lines 5 10 20 21 30 31 40 50 60 >test &&
        git reset &&
        # test sequence is s(plit), n(o), y(es), e(dit)
        # q n q q is there to make sure we exit at the end.
@@@ -383,7 -378,7 +383,7 @@@ test_expect_success 'patch mode ignore
        +changed
        EOF
        git diff --cached >diff &&
 -      test_cmp expected diff
 +      diff_cmp expected diff
  '
  
  test_expect_success TTY 'diffs can be colorized' '
        grep "$(printf "\\033")" output
  '
  
+ test_expect_success TTY 'diffFilter filters diff' '
+       git reset --hard &&
+       echo content >test &&
+       test_config interactive.diffFilter "sed s/^/foo:/" &&
+       printf y | test_terminal git add -p >output 2>&1 &&
+       # avoid depending on the exact coloring or content of the prompts,
+       # and just make sure we saw our diff prefixed
+       grep foo:.*content output
+ '
+ test_expect_success TTY 'detect bogus diffFilter output' '
+       git reset --hard &&
+       echo content >test &&
+       test_config interactive.diffFilter "echo too-short" &&
+       printf y | test_must_fail test_terminal git add -p
+ '
  test_expect_success 'patch-mode via -i prompts for files' '
        git reset --hard &&
  
  
        echo test >expect &&
        git diff --cached --name-only >actual &&
 -      test_cmp expect actual
 +      diff_cmp expect actual
  '
  
  test_expect_success 'add -p handles globs' '
@@@ -546,34 -561,4 +566,34 @@@ test_expect_success 'status ignores dir
        ! grep dirty-otherwise output
  '
  
 +test_expect_success 'set up pathological context' '
 +      git reset --hard &&
 +      test_write_lines a a a a a a a a a a a >a &&
 +      git add a &&
 +      git commit -m a &&
 +      test_write_lines c b a a a a a a a b a a a a >a &&
 +      test_write_lines     a a a a a a a b a a a a >expected-1 &&
 +      test_write_lines   b a a a a a a a b a a a a >expected-2 &&
 +      # check editing can cope with missing header and deleted context lines
 +      # as well as changes to other lines
 +      test_write_lines +b " a" >patch
 +'
 +
 +test_expect_success 'add -p works with pathological context lines' '
 +      git reset &&
 +      printf "%s\n" n y |
 +      git add -p &&
 +      git cat-file blob :a >actual &&
 +      test_cmp expected-1 actual
 +'
 +
 +test_expect_success 'add -p patch editing works with pathological context lines' '
 +      git reset &&
 +      # n q q below is in case edit fails
 +      printf "%s\n" e y    n q q |
 +      git add -p &&
 +      git cat-file blob :a >actual &&
 +      test_cmp expected-2 actual
 +'
 +
  test_done