add-interactive: fix bogus diff header line ordering
authorJeff King <peff@peff.net>
Tue, 23 Feb 2010 01:05:44 +0000 (20:05 -0500)
committerJunio C Hamano <gitster@pobox.com>
Tue, 23 Feb 2010 03:23:49 +0000 (19:23 -0800)
When we look at a patch for adding hunks interactively, we
first split it into a header and a list of hunks. Some of
the header lines, such as mode changes and deletion, however,
become their own selectable hunks. Later when we reassemble
the patch, we simply concatenate the header and the selected
hunks. This leads to patches like this:

  diff --git a/file b/file
  index d95f3ad..0000000
  --- a/file
  +++ /dev/null
  deleted file mode 100644
  @@ -1 +0,0 @@
  -content

Notice how the deletion comes _after_ the ---/+++ lines,
when it should come before.

In many cases, we can get away with this as git-apply
accepts the slightly bogus input. However, in the specific
case of a deletion line that is being applied via "apply
-R", this malformed patch triggers an assert in git-apply.
This comes up when discarding a deletion via "git checkout
-p".

Rather than try to make git-apply accept our odd input,
let's just reassemble the patch in the correct order.

Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
git-add--interactive.perl
t/t2016-checkout-patch.sh

index 75b71967a7c2f861c8e75e17c17e5cdce824e2e3..7493e68b8430c2dc3e3c707a5db59179e69a11d8 100755 (executable)
@@ -957,6 +957,28 @@ sub coalesce_overlapping_hunks {
        return @out;
 }
 
+sub reassemble_patch {
+       my $head = shift;
+       my @patch;
+
+       # Include everything in the header except the beginning of the diff.
+       push @patch, (grep { !/^[-+]{3}/ } @$head);
+
+       # Then include any headers from the hunk lines, which must
+       # come before any actual hunk.
+       while (@_ && $_[0] !~ /^@/) {
+               push @patch, shift;
+       }
+
+       # Then begin the diff.
+       push @patch, grep { /^[-+]{3}/ } @$head;
+
+       # And then the actual hunks.
+       push @patch, @_;
+
+       return @patch;
+}
+
 sub color_diff {
        return map {
                colored((/^@/  ? $fraginfo_color :
@@ -1454,7 +1476,7 @@ sub patch_update_file {
 
        if (@result) {
                my $fh;
-               my @patch = (@{$head->{TEXT}}, @result);
+               my @patch = reassemble_patch($head->{TEXT}, @result);
                my $apply_routine = $patch_mode_flavour{APPLY};
                &$apply_routine(@patch);
                refresh();
index 4d1c2e9e099918b5ff4e2e9458e702546f4c9605..2144184d790055ab12dfbb53ec10f519de598f72 100755 (executable)
@@ -66,6 +66,14 @@ test_expect_success 'git checkout -p HEAD^' '
        verify_state dir/foo parent parent
 '
 
+test_expect_success 'git checkout -p handles deletion' '
+       set_state dir/foo work index &&
+       rm dir/foo &&
+       (echo n; echo y) | git checkout -p &&
+       verify_saved_state bar &&
+       verify_state dir/foo index index
+'
+
 # The idea in the rest is that bar sorts first, so we always say 'y'
 # first and if the path limiter fails it'll apply to bar instead of
 # dir/foo.  There's always an extra 'n' to reject edits to dir/foo in