Skip to content

Commit 2c8bd84

Browse files
dschogitster
authored andcommitted
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 24ab81a (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 <[email protected]> Helped-by: Jeff King <[email protected]> Signed-off-by: Johannes Schindelin <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent b2627cc commit 2c8bd84

File tree

3 files changed

+61
-9
lines changed

3 files changed

+61
-9
lines changed

add-patch.c

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
#include "compat/terminal.h"
1010

1111
enum prompt_mode_type {
12-
PROMPT_MODE_CHANGE = 0, PROMPT_DELETION, PROMPT_HUNK,
12+
PROMPT_MODE_CHANGE = 0, PROMPT_DELETION, PROMPT_ADDITION, PROMPT_HUNK,
1313
PROMPT_MODE_MAX, /* must be last */
1414
};
1515

@@ -32,6 +32,7 @@ static struct patch_mode patch_mode_add = {
3232
.prompt_mode = {
3333
N_("Stage mode change [y,n,q,a,d%s,?]? "),
3434
N_("Stage deletion [y,n,q,a,d%s,?]? "),
35+
N_("Stage addition [y,n,q,a,d%s,?]? "),
3536
N_("Stage this hunk [y,n,q,a,d%s,?]? ")
3637
},
3738
.edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
@@ -53,6 +54,7 @@ static struct patch_mode patch_mode_stash = {
5354
.prompt_mode = {
5455
N_("Stash mode change [y,n,q,a,d%s,?]? "),
5556
N_("Stash deletion [y,n,q,a,d%s,?]? "),
57+
N_("Stash addition [y,n,q,a,d%s,?]? "),
5658
N_("Stash this hunk [y,n,q,a,d%s,?]? "),
5759
},
5860
.edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
@@ -76,6 +78,7 @@ static struct patch_mode patch_mode_reset_head = {
7678
.prompt_mode = {
7779
N_("Unstage mode change [y,n,q,a,d%s,?]? "),
7880
N_("Unstage deletion [y,n,q,a,d%s,?]? "),
81+
N_("Unstage addition [y,n,q,a,d%s,?]? "),
7982
N_("Unstage this hunk [y,n,q,a,d%s,?]? "),
8083
},
8184
.edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
@@ -98,6 +101,7 @@ static struct patch_mode patch_mode_reset_nothead = {
98101
.prompt_mode = {
99102
N_("Apply mode change to index [y,n,q,a,d%s,?]? "),
100103
N_("Apply deletion to index [y,n,q,a,d%s,?]? "),
104+
N_("Apply addition to index [y,n,q,a,d%s,?]? "),
101105
N_("Apply this hunk to index [y,n,q,a,d%s,?]? "),
102106
},
103107
.edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
@@ -120,6 +124,7 @@ static struct patch_mode patch_mode_checkout_index = {
120124
.prompt_mode = {
121125
N_("Discard mode change from worktree [y,n,q,a,d%s,?]? "),
122126
N_("Discard deletion from worktree [y,n,q,a,d%s,?]? "),
127+
N_("Discard addition from worktree [y,n,q,a,d%s,?]? "),
123128
N_("Discard this hunk from worktree [y,n,q,a,d%s,?]? "),
124129
},
125130
.edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
@@ -142,6 +147,7 @@ static struct patch_mode patch_mode_checkout_head = {
142147
.prompt_mode = {
143148
N_("Discard mode change from index and worktree [y,n,q,a,d%s,?]? "),
144149
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,?]? "),
145151
N_("Discard this hunk from index and worktree [y,n,q,a,d%s,?]? "),
146152
},
147153
.edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
@@ -163,6 +169,7 @@ static struct patch_mode patch_mode_checkout_nothead = {
163169
.prompt_mode = {
164170
N_("Apply mode change to index and worktree [y,n,q,a,d%s,?]? "),
165171
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,?]? "),
166173
N_("Apply this hunk to index and worktree [y,n,q,a,d%s,?]? "),
167174
},
168175
.edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
@@ -185,6 +192,7 @@ static struct patch_mode patch_mode_worktree_head = {
185192
.prompt_mode = {
186193
N_("Discard mode change from index and worktree [y,n,q,a,d%s,?]? "),
187194
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,?]? "),
188196
N_("Discard this hunk from index and worktree [y,n,q,a,d%s,?]? "),
189197
},
190198
.edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
@@ -206,6 +214,7 @@ static struct patch_mode patch_mode_worktree_nothead = {
206214
.prompt_mode = {
207215
N_("Apply mode change to index and worktree [y,n,q,a,d%s,?]? "),
208216
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,?]? "),
209218
N_("Apply this hunk to index and worktree [y,n,q,a,d%s,?]? "),
210219
},
211220
.edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
@@ -247,7 +256,7 @@ struct add_p_state {
247256
struct hunk head;
248257
struct hunk *hunk;
249258
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;
251260
} *file_diff;
252261
size_t file_diff_nr;
253262

@@ -441,7 +450,7 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
441450
pend = p + plain->len;
442451
while (p != pend) {
443452
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;
445454

446455
if (!eol)
447456
eol = pend;
@@ -460,11 +469,12 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
460469
} else if (p == plain->buf)
461470
BUG("diff starts with unexpected line:\n"
462471
"%.*s\n", (int)(eol - p), p);
463-
else if (file_diff->deleted)
472+
else if (file_diff->deleted || file_diff->added)
464473
; /* keep the rest of the file in a single "hunk" */
465474
else if (starts_with(p, "@@ ") ||
466475
(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)))) {
468478
if (marker == '-' || marker == '+')
469479
/*
470480
* 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)
484494

485495
if (deleted)
486496
file_diff->deleted = 1;
497+
else if (added)
498+
file_diff->added = 1;
487499
else if (parse_hunk_header(s, hunk) < 0)
488500
return -1;
489501

@@ -536,8 +548,10 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
536548
starts_with(p, "Binary files "))
537549
file_diff->binary = 1;
538550

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",
541555
(int)(eol - (plain->buf + file_diff->head.start)),
542556
plain->buf + file_diff->head.start);
543557

@@ -1397,6 +1411,8 @@ static int patch_update_file(struct add_p_state *s,
13971411

13981412
if (file_diff->deleted)
13991413
prompt_mode_type = PROMPT_DELETION;
1414+
else if (file_diff->added)
1415+
prompt_mode_type = PROMPT_ADDITION;
14001416
else if (file_diff->mode_change && !hunk_index)
14011417
prompt_mode_type = PROMPT_MODE_CHANGE;
14021418
else

git-add--interactive.perl

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -754,16 +754,18 @@ sub parse_diff_header {
754754
my $head = { TEXT => [], DISPLAY => [], TYPE => 'header' };
755755
my $mode = { TEXT => [], DISPLAY => [], TYPE => 'mode' };
756756
my $deletion = { TEXT => [], DISPLAY => [], TYPE => 'deletion' };
757+
my $addition = { TEXT => [], DISPLAY => [], TYPE => 'addition' };
757758

758759
for (my $i = 0; $i < @{$src->{TEXT}}; $i++) {
759760
my $dest =
760761
$src->{TEXT}->[$i] =~ /^(old|new) mode (\d+)$/ ? $mode :
761762
$src->{TEXT}->[$i] =~ /^deleted file/ ? $deletion :
763+
$src->{TEXT}->[$i] =~ /^new file/ ? $addition :
762764
$head;
763765
push @{$dest->{TEXT}}, $src->{TEXT}->[$i];
764766
push @{$dest->{DISPLAY}}, $src->{DISPLAY}->[$i];
765767
}
766-
return ($head, $mode, $deletion);
768+
return ($head, $mode, $deletion, $addition);
767769
}
768770

769771
sub hunk_splittable {
@@ -1427,46 +1429,55 @@ sub display_hunks {
14271429
stage => {
14281430
mode => N__("Stage mode change [y,n,q,a,d%s,?]? "),
14291431
deletion => N__("Stage deletion [y,n,q,a,d%s,?]? "),
1432+
addition => N__("Stage addition [y,n,q,a,d%s,?]? "),
14301433
hunk => N__("Stage this hunk [y,n,q,a,d%s,?]? "),
14311434
},
14321435
stash => {
14331436
mode => N__("Stash mode change [y,n,q,a,d%s,?]? "),
14341437
deletion => N__("Stash deletion [y,n,q,a,d%s,?]? "),
1438+
addition => N__("Stash addition [y,n,q,a,d%s,?]? "),
14351439
hunk => N__("Stash this hunk [y,n,q,a,d%s,?]? "),
14361440
},
14371441
reset_head => {
14381442
mode => N__("Unstage mode change [y,n,q,a,d%s,?]? "),
14391443
deletion => N__("Unstage deletion [y,n,q,a,d%s,?]? "),
1444+
addition => N__("Unstage addition [y,n,q,a,d%s,?]? "),
14401445
hunk => N__("Unstage this hunk [y,n,q,a,d%s,?]? "),
14411446
},
14421447
reset_nothead => {
14431448
mode => N__("Apply mode change to index [y,n,q,a,d%s,?]? "),
14441449
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,?]? "),
14451451
hunk => N__("Apply this hunk to index [y,n,q,a,d%s,?]? "),
14461452
},
14471453
checkout_index => {
14481454
mode => N__("Discard mode change from worktree [y,n,q,a,d%s,?]? "),
14491455
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,?]? "),
14501457
hunk => N__("Discard this hunk from worktree [y,n,q,a,d%s,?]? "),
14511458
},
14521459
checkout_head => {
14531460
mode => N__("Discard mode change from index and worktree [y,n,q,a,d%s,?]? "),
14541461
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,?]? "),
14551463
hunk => N__("Discard this hunk from index and worktree [y,n,q,a,d%s,?]? "),
14561464
},
14571465
checkout_nothead => {
14581466
mode => N__("Apply mode change to index and worktree [y,n,q,a,d%s,?]? "),
14591467
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,?]? "),
14601469
hunk => N__("Apply this hunk to index and worktree [y,n,q,a,d%s,?]? "),
14611470
},
14621471
worktree_head => {
14631472
mode => N__("Discard mode change from worktree [y,n,q,a,d%s,?]? "),
14641473
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,?]? "),
14651475
hunk => N__("Discard this hunk from worktree [y,n,q,a,d%s,?]? "),
14661476
},
14671477
worktree_nothead => {
14681478
mode => N__("Apply mode change to worktree [y,n,q,a,d%s,?]? "),
14691479
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,?]? "),
14701481
hunk => N__("Apply this hunk to worktree [y,n,q,a,d%s,?]? "),
14711482
},
14721483
);
@@ -1476,7 +1487,7 @@ sub patch_update_file {
14761487
my ($ix, $num);
14771488
my $path = shift;
14781489
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);
14801491
for (@{$head->{DISPLAY}}) {
14811492
print;
14821493
}
@@ -1490,6 +1501,12 @@ sub patch_update_file {
14901501
push @{$deletion->{DISPLAY}}, @{$hunk->{DISPLAY}};
14911502
}
14921503
@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);
14931510
}
14941511

14951512
$num = scalar @hunk;

t/t3701-add-interactive.sh

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,25 @@ test_expect_success 'deleting an empty file' '
403403
diff_cmp expected diff
404404
'
405405

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+
406425
test_expect_success 'split hunk setup' '
407426
git reset --hard &&
408427
test_write_lines 10 20 30 40 50 60 >test &&

0 commit comments

Comments
 (0)