@matheusbsbs / matheus-bagliano / commits / 2c8bd8471a
Fork 0

checkout -p: handle new files correctly

The original patch selection code was written for `git add -p`, and the fundamental unit on which it works is a hunk. We hacked around that to handle deletions back in 24ab81ae4d (add-interactive: handle deletion of empty files, 2009-10-27). But `git add -p` would never see a new file, since we only consider the set of tracked files in the index. However, since the same machinery was used for `git checkout -p` & friends, we can see new files. Handle this case specifically, adding a new prompt for it that is modeled after the `deleted file` case. This also fixes the problem where added _empty_ files could not be staged via `git checkout -p`. Reported-by: Merlin Büge <toni@bluenox07.de> Helped-by: Jeff King <peff@peff.net> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Johannes Schindelin committed May 27, 2020 at 21:09 UTC 2c8bd8471a6abc68064dafc743362547fc730f77
3 files changed +61 -9
add-patch.c
+23 -7
@@ -9,7 +9,7 @@
9 #include "compat/terminal.h"
10
11 enum prompt_mode_type {
12 - PROMPT_MODE_CHANGE = 0, PROMPT_DELETION, PROMPT_HUNK,
12 + PROMPT_MODE_CHANGE = 0, PROMPT_DELETION, PROMPT_ADDITION, PROMPT_HUNK,
13 PROMPT_MODE_MAX, /* must be last */
14 };
15
@@ -32,6 +32,7 @@ static struct patch_mode patch_mode_add = {
32 .prompt_mode = {
33 N_("Stage mode change [y,n,q,a,d%s,?]? "),
34 N_("Stage deletion [y,n,q,a,d%s,?]? "),
35 + N_("Stage addition [y,n,q,a,d%s,?]? "),
36 N_("Stage this hunk [y,n,q,a,d%s,?]? ")
37 },
38 .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
@@ -53,6 +54,7 @@ static struct patch_mode patch_mode_stash = {
54 .prompt_mode = {
55 N_("Stash mode change [y,n,q,a,d%s,?]? "),
56 N_("Stash deletion [y,n,q,a,d%s,?]? "),
57 + N_("Stash addition [y,n,q,a,d%s,?]? "),
58 N_("Stash this hunk [y,n,q,a,d%s,?]? "),
59 },
60 .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
@@ -76,6 +78,7 @@ static struct patch_mode patch_mode_reset_head = {
78 .prompt_mode = {
79 N_("Unstage mode change [y,n,q,a,d%s,?]? "),
80 N_("Unstage deletion [y,n,q,a,d%s,?]? "),
81 + N_("Unstage addition [y,n,q,a,d%s,?]? "),
82 N_("Unstage this hunk [y,n,q,a,d%s,?]? "),
83 },
84 .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
@@ -98,6 +101,7 @@ static struct patch_mode patch_mode_reset_nothead = {
101 .prompt_mode = {
102 N_("Apply mode change to index [y,n,q,a,d%s,?]? "),
103 N_("Apply deletion to index [y,n,q,a,d%s,?]? "),
104 + N_("Apply addition to index [y,n,q,a,d%s,?]? "),
105 N_("Apply this hunk to index [y,n,q,a,d%s,?]? "),
106 },
107 .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
@@ -120,6 +124,7 @@ static struct patch_mode patch_mode_checkout_index = {
124 .prompt_mode = {
125 N_("Discard mode change from worktree [y,n,q,a,d%s,?]? "),
126 N_("Discard deletion from worktree [y,n,q,a,d%s,?]? "),
127 + N_("Discard addition from worktree [y,n,q,a,d%s,?]? "),
128 N_("Discard this hunk from worktree [y,n,q,a,d%s,?]? "),
129 },
130 .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
@@ -142,6 +147,7 @@ static struct patch_mode patch_mode_checkout_head = {
147 .prompt_mode = {
148 N_("Discard mode change from index and worktree [y,n,q,a,d%s,?]? "),
149 N_("Discard deletion from index and worktree [y,n,q,a,d%s,?]? "),
150 + N_("Discard addition from index and worktree [y,n,q,a,d%s,?]? "),
151 N_("Discard this hunk from index and worktree [y,n,q,a,d%s,?]? "),
152 },
153 .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
@@ -163,6 +169,7 @@ static struct patch_mode patch_mode_checkout_nothead = {
169 .prompt_mode = {
170 N_("Apply mode change to index and worktree [y,n,q,a,d%s,?]? "),
171 N_("Apply deletion to index and worktree [y,n,q,a,d%s,?]? "),
172 + N_("Apply addition to index and worktree [y,n,q,a,d%s,?]? "),
173 N_("Apply this hunk to index and worktree [y,n,q,a,d%s,?]? "),
174 },
175 .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
@@ -185,6 +192,7 @@ static struct patch_mode patch_mode_worktree_head = {
192 .prompt_mode = {
193 N_("Discard mode change from index and worktree [y,n,q,a,d%s,?]? "),
194 N_("Discard deletion from index and worktree [y,n,q,a,d%s,?]? "),
195 + N_("Discard addition from index and worktree [y,n,q,a,d%s,?]? "),
196 N_("Discard this hunk from index and worktree [y,n,q,a,d%s,?]? "),
197 },
198 .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
@@ -206,6 +214,7 @@ static struct patch_mode patch_mode_worktree_nothead = {
214 .prompt_mode = {
215 N_("Apply mode change to index and worktree [y,n,q,a,d%s,?]? "),
216 N_("Apply deletion to index and worktree [y,n,q,a,d%s,?]? "),
217 + N_("Apply addition to index and worktree [y,n,q,a,d%s,?]? "),
218 N_("Apply this hunk to index and worktree [y,n,q,a,d%s,?]? "),
219 },
220 .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
@@ -247,7 +256,7 @@ struct add_p_state {
256 struct hunk head;
257 struct hunk *hunk;
258 size_t hunk_nr, hunk_alloc;
250 - unsigned deleted:1, mode_change:1,binary:1;
259 + unsigned deleted:1, added:1, mode_change:1,binary:1;
260 } *file_diff;
261 size_t file_diff_nr;
262
@@ -441,7 +450,7 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
450 pend = p + plain->len;
451 while (p != pend) {
452 char *eol = memchr(p, '\n', pend - p);
444 - const char *deleted = NULL, *mode_change = NULL;
453 + const char *deleted = NULL, *added = NULL, *mode_change = NULL;
454
455 if (!eol)
456 eol = pend;
@@ -460,11 +469,12 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
469 } else if (p == plain->buf)
470 BUG("diff starts with unexpected line:\n"
471 "%.*s\n", (int)(eol - p), p);
463 - else if (file_diff->deleted)
472 + else if (file_diff->deleted || file_diff->added)
473 ; /* keep the rest of the file in a single "hunk" */
474 else if (starts_with(p, "@@ ") ||
475 (hunk == &file_diff->head &&
467 - skip_prefix(p, "deleted file", &deleted))) {
476 + (skip_prefix(p, "deleted file", &deleted) ||
477 + skip_prefix(p, "new file", &added)))) {
478 if (marker == '-' || marker == '+')
479 /*
480 * Should not happen; previous hunk did not end
@@ -484,6 +494,8 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
494
495 if (deleted)
496 file_diff->deleted = 1;
497 + else if (added)
498 + file_diff->added = 1;
499 else if (parse_hunk_header(s, hunk) < 0)
500 return -1;
501
@@ -536,8 +548,10 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
548 starts_with(p, "Binary files "))
549 file_diff->binary = 1;
550
539 - if (file_diff->deleted && file_diff->mode_change)
540 - BUG("diff contains delete *and* a mode change?!?\n%.*s",
551 + if (!!file_diff->deleted + !!file_diff->added +
552 + !!file_diff->mode_change > 1)
553 + BUG("diff can only contain delete *or* add *or* a "
554 + "mode change?!?\n%.*s",
555 (int)(eol - (plain->buf + file_diff->head.start)),
556 plain->buf + file_diff->head.start);
557
@@ -1397,6 +1411,8 @@ static int patch_update_file(struct add_p_state *s,
1411
1412 if (file_diff->deleted)
1413 prompt_mode_type = PROMPT_DELETION;
1414 + else if (file_diff->added)
1415 + prompt_mode_type = PROMPT_ADDITION;
1416 else if (file_diff->mode_change && !hunk_index)
1417 prompt_mode_type = PROMPT_MODE_CHANGE;
1418 else
git-add--interactive.perl
+19 -2
@@ -754,16 +754,18 @@ sub parse_diff_header {
754 my $head = { TEXT => [], DISPLAY => [], TYPE => 'header' };
755 my $mode = { TEXT => [], DISPLAY => [], TYPE => 'mode' };
756 my $deletion = { TEXT => [], DISPLAY => [], TYPE => 'deletion' };
757 + my $addition = { TEXT => [], DISPLAY => [], TYPE => 'addition' };
758
759 for (my $i = 0; $i < @{$src->{TEXT}}; $i++) {
760 my $dest =
761 $src->{TEXT}->[$i] =~ /^(old|new) mode (\d+)$/ ? $mode :
762 $src->{TEXT}->[$i] =~ /^deleted file/ ? $deletion :
763 + $src->{TEXT}->[$i] =~ /^new file/ ? $addition :
764 $head;
765 push @{$dest->{TEXT}}, $src->{TEXT}->[$i];
766 push @{$dest->{DISPLAY}}, $src->{DISPLAY}->[$i];
767 }
766 - return ($head, $mode, $deletion);
768 + return ($head, $mode, $deletion, $addition);
769 }
770
771 sub hunk_splittable {
@@ -1427,46 +1429,55 @@ my %patch_update_prompt_modes = (
1429 stage => {
1430 mode => N__("Stage mode change [y,n,q,a,d%s,?]? "),
1431 deletion => N__("Stage deletion [y,n,q,a,d%s,?]? "),
1432 + addition => N__("Stage addition [y,n,q,a,d%s,?]? "),
1433 hunk => N__("Stage this hunk [y,n,q,a,d%s,?]? "),
1434 },
1435 stash => {
1436 mode => N__("Stash mode change [y,n,q,a,d%s,?]? "),
1437 deletion => N__("Stash deletion [y,n,q,a,d%s,?]? "),
1438 + addition => N__("Stash addition [y,n,q,a,d%s,?]? "),
1439 hunk => N__("Stash this hunk [y,n,q,a,d%s,?]? "),
1440 },
1441 reset_head => {
1442 mode => N__("Unstage mode change [y,n,q,a,d%s,?]? "),
1443 deletion => N__("Unstage deletion [y,n,q,a,d%s,?]? "),
1444 + addition => N__("Unstage addition [y,n,q,a,d%s,?]? "),
1445 hunk => N__("Unstage this hunk [y,n,q,a,d%s,?]? "),
1446 },
1447 reset_nothead => {
1448 mode => N__("Apply mode change to index [y,n,q,a,d%s,?]? "),
1449 deletion => N__("Apply deletion to index [y,n,q,a,d%s,?]? "),
1450 + addition => N__("Apply addition to index [y,n,q,a,d%s,?]? "),
1451 hunk => N__("Apply this hunk to index [y,n,q,a,d%s,?]? "),
1452 },
1453 checkout_index => {
1454 mode => N__("Discard mode change from worktree [y,n,q,a,d%s,?]? "),
1455 deletion => N__("Discard deletion from worktree [y,n,q,a,d%s,?]? "),
1456 + addition => N__("Discard addition from worktree [y,n,q,a,d%s,?]? "),
1457 hunk => N__("Discard this hunk from worktree [y,n,q,a,d%s,?]? "),
1458 },
1459 checkout_head => {
1460 mode => N__("Discard mode change from index and worktree [y,n,q,a,d%s,?]? "),
1461 deletion => N__("Discard deletion from index and worktree [y,n,q,a,d%s,?]? "),
1462 + addition => N__("Discard addition from index and worktree [y,n,q,a,d%s,?]? "),
1463 hunk => N__("Discard this hunk from index and worktree [y,n,q,a,d%s,?]? "),
1464 },
1465 checkout_nothead => {
1466 mode => N__("Apply mode change to index and worktree [y,n,q,a,d%s,?]? "),
1467 deletion => N__("Apply deletion to index and worktree [y,n,q,a,d%s,?]? "),
1468 + addition => N__("Apply addition to index and worktree [y,n,q,a,d%s,?]? "),
1469 hunk => N__("Apply this hunk to index and worktree [y,n,q,a,d%s,?]? "),
1470 },
1471 worktree_head => {
1472 mode => N__("Discard mode change from worktree [y,n,q,a,d%s,?]? "),
1473 deletion => N__("Discard deletion from worktree [y,n,q,a,d%s,?]? "),
1474 + addition => N__("Discard addition from worktree [y,n,q,a,d%s,?]? "),
1475 hunk => N__("Discard this hunk from worktree [y,n,q,a,d%s,?]? "),
1476 },
1477 worktree_nothead => {
1478 mode => N__("Apply mode change to worktree [y,n,q,a,d%s,?]? "),
1479 deletion => N__("Apply deletion to worktree [y,n,q,a,d%s,?]? "),
1480 + addition => N__("Apply addition to worktree [y,n,q,a,d%s,?]? "),
1481 hunk => N__("Apply this hunk to worktree [y,n,q,a,d%s,?]? "),
1482 },
1483 );
@@ -1476,7 +1487,7 @@ sub patch_update_file {
1487 my ($ix, $num);
1488 my $path = shift;
1489 my ($head, @hunk) = parse_diff($path);
1479 - ($head, my $mode, my $deletion) = parse_diff_header($head);
1490 + ($head, my $mode, my $deletion, my $addition) = parse_diff_header($head);
1491 for (@{$head->{DISPLAY}}) {
1492 print;
1493 }
@@ -1490,6 +1501,12 @@ sub patch_update_file {
1501 push @{$deletion->{DISPLAY}}, @{$hunk->{DISPLAY}};
1502 }
1503 @hunk = ($deletion);
1504 + } elsif (@{$addition->{TEXT}}) {
1505 + foreach my $hunk (@hunk) {
1506 + push @{$addition->{TEXT}}, @{$hunk->{TEXT}};
1507 + push @{$addition->{DISPLAY}}, @{$hunk->{DISPLAY}};
1508 + }
1509 + @hunk = ($addition);
1510 }
1511
1512 $num = scalar @hunk;
t/t3701-add-interactive.sh
+19
@@ -403,6 +403,25 @@ test_expect_success 'deleting an empty file' '
403 diff_cmp expected diff
404 '
405
406 +test_expect_success 'adding an empty file' '
407 + git init added &&
408 + (
409 + cd added &&
410 + test_commit initial &&
411 + >empty &&
412 + git add empty &&
413 + test_tick &&
414 + git commit -m empty &&
415 + git tag added-file &&
416 + git reset --hard HEAD^ &&
417 + test_path_is_missing empty &&
418 +
419 + echo y | git checkout -p added-file -- >actual &&
420 + test_path_is_file empty &&
421 + test_i18ngrep "Apply addition to index and worktree" actual
422 + )
423 +'
424 +
425 test_expect_success 'split hunk setup' '
426 git reset --hard &&
427 test_write_lines 10 20 30 40 50 60 >test &&