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 committedMay 27, 2020 at 21:09 UTC2c8bd8471a6abc68064dafc743362547fc730f77
3 files changed+61-9
add-patch.c
+23-7
index d8dafa8168..eaace1b824 100644--- a/add-patch.c+++ b/add-patch.c@@ -9,7 +9,7 @@ #include "compat/terminal.h" enum prompt_mode_type {- PROMPT_MODE_CHANGE = 0, PROMPT_DELETION, PROMPT_HUNK,+ PROMPT_MODE_CHANGE = 0, PROMPT_DELETION, PROMPT_ADDITION, PROMPT_HUNK, PROMPT_MODE_MAX, /* must be last */ };@@ -32,6 +32,7 @@ static struct patch_mode patch_mode_add = { .prompt_mode = { N_("Stage mode change [y,n,q,a,d%s,?]? "), N_("Stage deletion [y,n,q,a,d%s,?]? "),+ N_("Stage addition [y,n,q,a,d%s,?]? "), N_("Stage this hunk [y,n,q,a,d%s,?]? ") }, .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "@@ -53,6 +54,7 @@ static struct patch_mode patch_mode_stash = { .prompt_mode = { N_("Stash mode change [y,n,q,a,d%s,?]? "), N_("Stash deletion [y,n,q,a,d%s,?]? "),+ N_("Stash addition [y,n,q,a,d%s,?]? "), N_("Stash this hunk [y,n,q,a,d%s,?]? "), }, .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "@@ -76,6 +78,7 @@ static struct patch_mode patch_mode_reset_head = { .prompt_mode = { N_("Unstage mode change [y,n,q,a,d%s,?]? "), N_("Unstage deletion [y,n,q,a,d%s,?]? "),+ N_("Unstage addition [y,n,q,a,d%s,?]? "), N_("Unstage this hunk [y,n,q,a,d%s,?]? "), }, .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "@@ -98,6 +101,7 @@ static struct patch_mode patch_mode_reset_nothead = { .prompt_mode = { N_("Apply mode change to index [y,n,q,a,d%s,?]? "), N_("Apply deletion to index [y,n,q,a,d%s,?]? "),+ N_("Apply addition to index [y,n,q,a,d%s,?]? "), N_("Apply this hunk to index [y,n,q,a,d%s,?]? "), }, .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "@@ -120,6 +124,7 @@ static struct patch_mode patch_mode_checkout_index = { .prompt_mode = { N_("Discard mode change from worktree [y,n,q,a,d%s,?]? "), N_("Discard deletion from worktree [y,n,q,a,d%s,?]? "),+ N_("Discard addition from worktree [y,n,q,a,d%s,?]? "), N_("Discard this hunk from worktree [y,n,q,a,d%s,?]? "), }, .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "@@ -142,6 +147,7 @@ static struct patch_mode patch_mode_checkout_head = { .prompt_mode = { N_("Discard mode change from index and worktree [y,n,q,a,d%s,?]? "), N_("Discard deletion from index and worktree [y,n,q,a,d%s,?]? "),+ N_("Discard addition from index and worktree [y,n,q,a,d%s,?]? "), N_("Discard this hunk from index and worktree [y,n,q,a,d%s,?]? "), }, .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "@@ -163,6 +169,7 @@ static struct patch_mode patch_mode_checkout_nothead = { .prompt_mode = { N_("Apply mode change to index and worktree [y,n,q,a,d%s,?]? "), N_("Apply deletion to index and worktree [y,n,q,a,d%s,?]? "),+ N_("Apply addition to index and worktree [y,n,q,a,d%s,?]? "), N_("Apply this hunk to index and worktree [y,n,q,a,d%s,?]? "), }, .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "@@ -185,6 +192,7 @@ static struct patch_mode patch_mode_worktree_head = { .prompt_mode = { N_("Discard mode change from index and worktree [y,n,q,a,d%s,?]? "), N_("Discard deletion from index and worktree [y,n,q,a,d%s,?]? "),+ N_("Discard addition from index and worktree [y,n,q,a,d%s,?]? "), N_("Discard this hunk from index and worktree [y,n,q,a,d%s,?]? "), }, .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "@@ -206,6 +214,7 @@ static struct patch_mode patch_mode_worktree_nothead = { .prompt_mode = { N_("Apply mode change to index and worktree [y,n,q,a,d%s,?]? "), N_("Apply deletion to index and worktree [y,n,q,a,d%s,?]? "),+ N_("Apply addition to index and worktree [y,n,q,a,d%s,?]? "), N_("Apply this hunk to index and worktree [y,n,q,a,d%s,?]? "), }, .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "@@ -247,7 +256,7 @@ struct add_p_state { struct hunk head; struct hunk *hunk; size_t hunk_nr, hunk_alloc;- unsigned deleted:1, mode_change:1,binary:1;+ unsigned deleted:1, added:1, mode_change:1,binary:1; } *file_diff; size_t file_diff_nr;@@ -441,7 +450,7 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps) pend = p + plain->len; while (p != pend) { char *eol = memchr(p, '\n', pend - p);- const char *deleted = NULL, *mode_change = NULL;+ const char *deleted = NULL, *added = NULL, *mode_change = NULL; if (!eol) eol = pend;@@ -460,11 +469,12 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps) } else if (p == plain->buf) BUG("diff starts with unexpected line:\n" "%.*s\n", (int)(eol - p), p);- else if (file_diff->deleted)+ else if (file_diff->deleted || file_diff->added) ; /* keep the rest of the file in a single "hunk" */ else if (starts_with(p, "@@ ") || (hunk == &file_diff->head &&- skip_prefix(p, "deleted file", &deleted))) {+ (skip_prefix(p, "deleted file", &deleted) ||+ skip_prefix(p, "new file", &added)))) { if (marker == '-' || marker == '+') /* * 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) if (deleted) file_diff->deleted = 1;+ else if (added)+ file_diff->added = 1; else if (parse_hunk_header(s, hunk) < 0) return -1;@@ -536,8 +548,10 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps) starts_with(p, "Binary files ")) file_diff->binary = 1;- if (file_diff->deleted && file_diff->mode_change)- BUG("diff contains delete *and* a mode change?!?\n%.*s",+ if (!!file_diff->deleted + !!file_diff->added ++ !!file_diff->mode_change > 1)+ BUG("diff can only contain delete *or* add *or* a "+ "mode change?!?\n%.*s", (int)(eol - (plain->buf + file_diff->head.start)), plain->buf + file_diff->head.start);@@ -1397,6 +1411,8 @@ static int patch_update_file(struct add_p_state *s, if (file_diff->deleted) prompt_mode_type = PROMPT_DELETION;+ else if (file_diff->added)+ prompt_mode_type = PROMPT_ADDITION; else if (file_diff->mode_change && !hunk_index) prompt_mode_type = PROMPT_MODE_CHANGE; else
git-add--interactive.perl
+19-2
index 10fd30ae16..f36c0078ac 100755--- a/git-add--interactive.perl+++ b/git-add--interactive.perl@@ -754,16 +754,18 @@ sub parse_diff_header { my $head = { TEXT => [], DISPLAY => [], TYPE => 'header' }; my $mode = { TEXT => [], DISPLAY => [], TYPE => 'mode' }; my $deletion = { TEXT => [], DISPLAY => [], TYPE => 'deletion' };+ my $addition = { TEXT => [], DISPLAY => [], TYPE => 'addition' }; for (my $i = 0; $i < @{$src->{TEXT}}; $i++) { my $dest = $src->{TEXT}->[$i] =~ /^(old|new) mode (\d+)$/ ? $mode : $src->{TEXT}->[$i] =~ /^deleted file/ ? $deletion :+ $src->{TEXT}->[$i] =~ /^new file/ ? $addition : $head; push @{$dest->{TEXT}}, $src->{TEXT}->[$i]; push @{$dest->{DISPLAY}}, $src->{DISPLAY}->[$i]; }- return ($head, $mode, $deletion);+ return ($head, $mode, $deletion, $addition); } sub hunk_splittable {@@ -1427,46 +1429,55 @@ 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,?]? "),+ addition => N__("Stage addition [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,?]? "),+ addition => N__("Stash addition [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,?]? "),+ addition => N__("Unstage addition [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,?]? "),+ addition => N__("Apply addition 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,?]? "),+ addition => N__("Discard addition 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,?]? "),+ addition => N__("Discard addition 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,?]? "),+ addition => N__("Apply addition 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,?]? "), }, worktree_head => { 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,?]? "),+ addition => N__("Discard addition from worktree [y,n,q,a,d%s,?]? "), hunk => N__("Discard this hunk from worktree [y,n,q,a,d%s,?]? "), }, worktree_nothead => { mode => N__("Apply mode change to worktree [y,n,q,a,d%s,?]? "), deletion => N__("Apply deletion to worktree [y,n,q,a,d%s,?]? "),+ addition => N__("Apply addition to worktree [y,n,q,a,d%s,?]? "), hunk => N__("Apply this hunk to worktree [y,n,q,a,d%s,?]? "), }, );@@ -1476,7 +1487,7 @@ sub patch_update_file { my ($ix, $num); my $path = shift; my ($head, @hunk) = parse_diff($path);- ($head, my $mode, my $deletion) = parse_diff_header($head);+ ($head, my $mode, my $deletion, my $addition) = parse_diff_header($head); for (@{$head->{DISPLAY}}) { print; }@@ -1490,6 +1501,12 @@ sub patch_update_file { push @{$deletion->{DISPLAY}}, @{$hunk->{DISPLAY}}; } @hunk = ($deletion);+ } elsif (@{$addition->{TEXT}}) {+ foreach my $hunk (@hunk) {+ push @{$addition->{TEXT}}, @{$hunk->{TEXT}};+ push @{$addition->{DISPLAY}}, @{$hunk->{DISPLAY}};+ }+ @hunk = ($addition); } $num = scalar @hunk;