Merge branch 'pw/add-p-recount' into maint
authorJunio C Hamano <gitster@pobox.com>
Mon, 29 Jul 2019 19:38:22 +0000 (12:38 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 29 Jul 2019 19:38:22 +0000 (12:38 -0700)
"git checkout -p" needs to selectively apply a patch in reverse,
which did not work well.

* pw/add-p-recount:
add -p: fix checkout -p with pathological context

1  2 
git-add--interactive.perl
t/t3701-add-interactive.sh
index 20eb81cc92f947d872b31a179d43d97772ff25e4,85d0dc11960c08f3af30015d99228429d9dd7e64..da5b4ec4bc50e4e3adaba0f25820262b6a7436a0
@@@ -205,15 -205,8 +205,15 @@@ my $status_head = sprintf($status_fmt, 
        }
  }
  
 -sub get_empty_tree {
 -      return '4b825dc642cb6eb9a060e54bf8d69288fbee4904';
 +{
 +      my $empty_tree;
 +      sub get_empty_tree {
 +              return $empty_tree if defined $empty_tree;
 +
 +              $empty_tree = run_cmd_pipe(qw(git hash-object -t tree /dev/null));
 +              chomp $empty_tree;
 +              return $empty_tree;
 +      }
  }
  
  sub get_diff_reference {
@@@ -269,7 -262,7 +269,7 @@@ sub list_modified 
                }
        }
  
 -      for (run_cmd_pipe(qw(git diff-files --numstat --summary --raw --), @ARGV)) {
 +      for (run_cmd_pipe(qw(git diff-files --ignore-submodules=dirty --numstat --summary --raw --), @ARGV)) {
                if (($add, $del, $file) =
                    /^([-\d]+)  ([-\d]+)        (.*)/) {
                        $file = unquote_path($file);
@@@ -712,14 -705,6 +712,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 => [],
@@@ -972,7 -957,11 +972,11 @@@ sub coalesce_overlapping_hunks 
                        next;
                }
                if ($ofs_delta) {
-                       $n_ofs += $ofs_delta;
+                       if ($patch_mode_flavour{IS_REVERSE}) {
+                               $o_ofs -= $ofs_delta;
+                       } else {
+                               $n_ofs += $ofs_delta;
+                       }
                        $_->{TEXT}->[0] = format_hunk_header($o_ofs, $o_cnt,
                                                             $n_ofs, $n_cnt);
                }
@@@ -1263,13 -1252,7 +1267,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
@@@ -1387,39 -1370,39 +1391,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,?]? "),
        },
  );
  
@@@ -1475,7 -1458,7 +1479,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;
 -                              if ($1 eq "") {
 +                              unless ($other =~ m|/|) {
 +                                      error_msg __("No other hunks to search\n");
 +                                      next;
 +                              }
 +                              if ($regex eq "") {
                                        print colored $prompt_color, __("search for regex? ");
                                        $regex = <STDIN>;
                                        if (defined $regex) {
                                        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 65dfbc033a027df1a590cbfaf75ae47f4ed9c547,2c54f84a8dda705aaa91f359734894117b893c03..69991a3168f354b8bfefcd396a968e653bb352bd
@@@ -46,13 -46,13 +46,13 @@@ test_expect_success 'setup expected' 
  '
  
  test_expect_success 'diff works (initial)' '
 -      (echo d; echo 1) | git add -i >output &&
 +      test_write_lines d 1 | git add -i >output &&
        sed -ne "/new file/,/content/p" <output >diff &&
        diff_cmp expected diff
  '
  test_expect_success 'revert works (initial)' '
        git add file &&
 -      (echo r; echo 1) | git add -i &&
 +      test_write_lines r 1 | git add -i &&
        git ls-files >output &&
        ! grep . output
  '
@@@ -83,13 -83,13 +83,13 @@@ test_expect_success 'setup expected' 
  '
  
  test_expect_success 'diff works (commit)' '
 -      (echo d; echo 1) | git add -i >output &&
 +      test_write_lines d 1 | git add -i >output &&
        sed -ne "/^index/,/content/p" <output >diff &&
        diff_cmp expected diff
  '
  test_expect_success 'revert works (commit)' '
        git add file &&
 -      (echo r; echo 1) | git add -i &&
 +      test_write_lines r 1 | git add -i &&
        git add -i </dev/null >output &&
        grep "unchanged *+3/-0 file" output
  '
@@@ -102,7 -102,7 +102,7 @@@ test_expect_success 'setup expected' 
  
  test_expect_success 'dummy edit works' '
        test_set_editor : &&
 -      (echo e; echo a) | git add -p &&
 +      test_write_lines e a | git add -p &&
        git diff > diff &&
        diff_cmp expected diff
  '
@@@ -127,7 -127,7 +127,7 @@@ test_expect_success 'setup fake editor
  
  test_expect_success 'bad edit rejected' '
        git reset &&
 -      (echo e; echo n; echo d) | git add -p >output &&
 +      test_write_lines e n d | git add -p >output &&
        grep "hunk does not apply" output
  '
  
@@@ -140,7 -140,7 +140,7 @@@ test_expect_success 'setup patch' 
  
  test_expect_success 'garbage edit rejected' '
        git reset &&
 -      (echo e; echo n; echo d) | git add -p >output &&
 +      test_write_lines e n d | git add -p >output &&
        grep "hunk does not apply" output
  '
  
@@@ -170,7 -170,7 +170,7 @@@ test_expect_success 'setup expected' 
  '
  
  test_expect_success 'real edit works' '
 -      (echo e; echo n; echo d) | git add -p &&
 +      test_write_lines e n d | git add -p &&
        git diff >output &&
        diff_cmp expected output
  '
@@@ -440,26 -440,6 +440,26 @@@ test_expect_success TTY 'diffs can be c
        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 &&
  
@@@ -540,7 -520,7 +540,7 @@@ test_expect_success 'add -p does not ex
        # update it, but we want to be sure that our "." pathspec
        # was not expanded into the argument list of any command.
        # So look only for "not-changed".
 -      ! grep not-changed trace.out
 +      ! grep -E "^trace: (built-in|exec|run_command): .*not-changed" trace.out
  '
  
  test_expect_success 'hunk-editing handles custom comment char' '
@@@ -561,54 -541,6 +561,54 @@@ test_expect_success 'add -p works even 
        test_cmp expect actual
  '
  
 +test_expect_success 'setup different kinds of dirty submodules' '
 +      test_create_repo for-submodules &&
 +      (
 +              cd for-submodules &&
 +              test_commit initial &&
 +              test_create_repo dirty-head &&
 +              (
 +                      cd dirty-head &&
 +                      test_commit initial
 +              ) &&
 +              cp -R dirty-head dirty-otherwise &&
 +              cp -R dirty-head dirty-both-ways &&
 +              git add dirty-head &&
 +              git add dirty-otherwise dirty-both-ways &&
 +              git commit -m initial &&
 +
 +              cd dirty-head &&
 +              test_commit updated &&
 +              cd ../dirty-both-ways &&
 +              test_commit updated &&
 +              echo dirty >>initial &&
 +              : >untracked &&
 +              cd ../dirty-otherwise &&
 +              echo dirty >>initial &&
 +              : >untracked
 +      ) &&
 +      git -C for-submodules diff-files --name-only >actual &&
 +      cat >expected <<-\EOF &&
 +      dirty-both-ways
 +      dirty-head
 +      dirty-otherwise
 +      EOF
 +      test_cmp expected actual &&
 +      git -C for-submodules diff-files --name-only --ignore-submodules=dirty >actual &&
 +      cat >expected <<-\EOF &&
 +      dirty-both-ways
 +      dirty-head
 +      EOF
 +      test_cmp expected actual
 +'
 +
 +test_expect_success 'status ignores dirty submodules (except HEAD)' '
 +      git -C for-submodules add -i </dev/null >output &&
 +      grep dirty-head output &&
 +      grep dirty-both-ways output &&
 +      ! 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 &&
@@@ -639,4 -571,12 +639,12 @@@ test_expect_success 'add -p patch editi
        test_cmp expected-2 actual
  '
  
+ test_expect_success 'checkout -p works with pathological context lines' '
+       test_write_lines a a a a a a >a &&
+       git add a &&
+       test_write_lines a b a b a b a b a b a > a&&
+       test_write_lines s n n y q | git checkout -p &&
+       test_write_lines a b a b a a b a b a >expect &&
+       test_cmp expect a
+ '
  test_done