{"thread":{"id":"54217","subject":"[PATCH] add -p: fix editing of intent-to-add paths","startedAt":"2020-09-09T16:48:54Z","lastAt":"2020-09-09T16:48:54Z","messageCount":1,"participants":["Phillip Wood via GitGitGadget"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"405252","messageId":"pull.731.git.1599659932391.gitgitgadget@gmail.com","threadId":"54217","inReplyTo":null,"subject":"[PATCH] add -p: fix editing of intent-to-add paths","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-09-09T13:58:52Z","receivedAt":"2020-09-09T16:48:54Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nA popular way of partially staging a new file is to run `git add -N\n<path>` and then use the hunk editing of `git add -p` to select the\npart of the file that the user wishes to stage. Since\n85953a3187 (\"diff-files --raw: show correct post-image of\nintent-to-add files\", 2020-07-01) this has stopped working as\nintent-to-add paths are now show as new files rather than changes to\nan empty blob and `git apply` refused to apply a creation patch for a\npath that was marked as intent-to-add. 7cfde3fa0f (\"apply: allow \"new\nfile\" patches on i-t-a entries\", 2020-08-06) fixed the problem with\napply but it still wasn't possible to edit the added hunk properly.\n\n2c8bd8471a (\"checkout -p: handle new files correctly\", 2020-05-27)\nhad previously changed `add -p` to handle new files but it did not\nimplement patch editing correctly. The perl version simply forbade\nediting and the C version opened the editor with the full diff rather\nthat just the hunk which meant that the user had to edit the hunk\nheader manually to get it to work.\n\nThe root cause of the problem is that added files store the diff header\nwith the hunk data rather than separating the two as we do for other\nchanges. Changing added files to store the diff header separately\nfixes the editing problem at the expense of having to special case\nempty additions as they no longer have any hunks associated with them,\nonly the diff header.\n\nThe changes move some existing code into a conditional changing the\nindentation, they are best viewed with\n--color-moved-ws=allow-indentation-change (or --ignore-space-change\nworks well to get an overview of the changes)\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\nReported-by: Thomas Sullivan <tom@msbit.com.au>\nReported-by: Yuchen Ying <ych@google.com>\n---\n    add -p: fix editing of intent-to-add paths\n    \n    While working on this I realized that we should probably disallow 'a'\n    and 'd' when there is no next undecided hunk as they just act like 'y'\n    and 'n' in that case and give the misleading impression that there are\n    more hunks to come. Doing so would make these changes slightly simpler\n    but as we've had two bug reports about this regression already I decided\n    to focus on just fixing this for now.\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-731%2Fphillipwood%2Fwip%2Fadd-p-fix-editing-added-files-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-731/phillipwood/wip/add-p-fix-editing-added-files-v1\nPull-Request: https://github.com/gitgitgadget/git/pull/731\n\n add-patch.c                | 140 +++++++++++++++++++++----------------\n git-add--interactive.perl  |  89 ++++++++++++++---------\n t/t3701-add-interactive.sh |  38 ++++++++++\n 3 files changed, 173 insertions(+), 94 deletions(-)\n\ndiff --git a/add-patch.c b/add-patch.c\nindex 457b8c550e..867720a7c6 100644\n--- a/add-patch.c\n+++ b/add-patch.c\n@@ -451,7 +451,7 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)\n \tpend = p + plain->len;\n \twhile (p != pend) {\n \t\tchar *eol = memchr(p, '\\n', pend - p);\n-\t\tconst char *deleted = NULL, *added = NULL, *mode_change = NULL;\n+\t\tconst char *deleted = NULL, *mode_change = NULL;\n \n \t\tif (!eol)\n \t\t\teol = pend;\n@@ -468,12 +468,11 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)\n \t\t} else if (p == plain->buf)\n \t\t\tBUG(\"diff starts with unexpected line:\\n\"\n \t\t\t    \"%.*s\\n\", (int)(eol - p), p);\n-\t\telse if (file_diff->deleted || file_diff->added)\n+\t\telse if (file_diff->deleted)\n \t\t\t; /* keep the rest of the file in a single \"hunk\" */\n \t\telse if (starts_with(p, \"@@ \") ||\n \t\t\t (hunk == &file_diff->head &&\n-\t\t\t  (skip_prefix(p, \"deleted file\", &deleted) ||\n-\t\t\t   skip_prefix(p, \"new file\", &added)))) {\n+\t\t\t  (skip_prefix(p, \"deleted file\", &deleted)))) {\n \t\t\tif (marker == '-' || marker == '+')\n \t\t\t\t/*\n \t\t\t\t * Should not happen; previous hunk did not end\n@@ -491,8 +490,6 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)\n \n \t\t\tif (deleted)\n \t\t\t\tfile_diff->deleted = 1;\n-\t\t\telse if (added)\n-\t\t\t\tfile_diff->added = 1;\n \t\t\telse if (parse_hunk_header(s, hunk) < 0)\n \t\t\t\treturn -1;\n \n@@ -501,6 +498,9 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)\n \t\t\t * split\n \t\t\t */\n \t\t\tmarker = *p;\n+\t\t} else if (hunk == &file_diff->head &&\n+\t\t\t   starts_with(p, \"new file\")) {\n+\t\t\tfile_diff->added = 1;\n \t\t} else if (hunk == &file_diff->head &&\n \t\t\t   skip_prefix(p, \"old mode \", &mode_change) &&\n \t\t\t   is_octal(mode_change, eol - mode_change)) {\n@@ -1362,7 +1362,8 @@ static int patch_update_file(struct add_p_state *s,\n \t\tALLOW_EDIT = 1 << 6\n \t} permitted = 0;\n \n-\tif (!file_diff->hunk_nr)\n+\t/* Empty added files have no hunks */\n+\tif (!file_diff->hunk_nr && !file_diff->added)\n \t\treturn 0;\n \n \tstrbuf_reset(&s->buf);\n@@ -1371,21 +1372,25 @@ static int patch_update_file(struct add_p_state *s,\n \tfor (;;) {\n \t\tif (hunk_index >= file_diff->hunk_nr)\n \t\t\thunk_index = 0;\n-\t\thunk = file_diff->hunk + hunk_index;\n-\n+\t\thunk = file_diff->hunk_nr\n+\t\t\t\t? file_diff->hunk + hunk_index\n+\t\t\t\t: &file_diff->head;\n \t\tundecided_previous = -1;\n-\t\tfor (i = hunk_index - 1; i >= 0; i--)\n-\t\t\tif (file_diff->hunk[i].use == UNDECIDED_HUNK) {\n-\t\t\t\tundecided_previous = i;\n-\t\t\t\tbreak;\n-\t\t\t}\n-\n \t\tundecided_next = -1;\n-\t\tfor (i = hunk_index + 1; i < file_diff->hunk_nr; i++)\n-\t\t\tif (file_diff->hunk[i].use == UNDECIDED_HUNK) {\n-\t\t\t\tundecided_next = i;\n-\t\t\t\tbreak;\n-\t\t\t}\n+\n+\t\tif (file_diff->hunk_nr) {\n+\t\t\tfor (i = hunk_index - 1; i >= 0; i--)\n+\t\t\t\tif (file_diff->hunk[i].use == UNDECIDED_HUNK) {\n+\t\t\t\t\tundecided_previous = i;\n+\t\t\t\t\tbreak;\n+\t\t\t\t}\n+\n+\t\t\tfor (i = hunk_index + 1; i < file_diff->hunk_nr; i++)\n+\t\t\t\tif (file_diff->hunk[i].use == UNDECIDED_HUNK) {\n+\t\t\t\t\tundecided_next = i;\n+\t\t\t\t\tbreak;\n+\t\t\t\t}\n+\t\t}\n \n \t\t/* Everything decided? */\n \t\tif (undecided_previous < 0 && undecided_next < 0 &&\n@@ -1393,38 +1398,40 @@ static int patch_update_file(struct add_p_state *s,\n \t\t\tbreak;\n \n \t\tstrbuf_reset(&s->buf);\n-\t\trender_hunk(s, hunk, 0, colored, &s->buf);\n-\t\tfputs(s->buf.buf, stdout);\n+\t\tif (file_diff->hunk_nr) {\n+\t\t\trender_hunk(s, hunk, 0, colored, &s->buf);\n+\t\t\tfputs(s->buf.buf, stdout);\n \n-\t\tstrbuf_reset(&s->buf);\n-\t\tif (undecided_previous >= 0) {\n-\t\t\tpermitted |= ALLOW_GOTO_PREVIOUS_UNDECIDED_HUNK;\n-\t\t\tstrbuf_addstr(&s->buf, \",k\");\n-\t\t}\n-\t\tif (hunk_index) {\n-\t\t\tpermitted |= ALLOW_GOTO_PREVIOUS_HUNK;\n-\t\t\tstrbuf_addstr(&s->buf, \",K\");\n-\t\t}\n-\t\tif (undecided_next >= 0) {\n-\t\t\tpermitted |= ALLOW_GOTO_NEXT_UNDECIDED_HUNK;\n-\t\t\tstrbuf_addstr(&s->buf, \",j\");\n-\t\t}\n-\t\tif (hunk_index + 1 < file_diff->hunk_nr) {\n-\t\t\tpermitted |= ALLOW_GOTO_NEXT_HUNK;\n-\t\t\tstrbuf_addstr(&s->buf, \",J\");\n-\t\t}\n-\t\tif (file_diff->hunk_nr > 1) {\n-\t\t\tpermitted |= ALLOW_SEARCH_AND_GOTO;\n-\t\t\tstrbuf_addstr(&s->buf, \",g,/\");\n-\t\t}\n-\t\tif (hunk->splittable_into > 1) {\n-\t\t\tpermitted |= ALLOW_SPLIT;\n-\t\t\tstrbuf_addstr(&s->buf, \",s\");\n-\t\t}\n-\t\tif (hunk_index + 1 > file_diff->mode_change &&\n-\t\t    !file_diff->deleted) {\n-\t\t\tpermitted |= ALLOW_EDIT;\n-\t\t\tstrbuf_addstr(&s->buf, \",e\");\n+\t\t\tstrbuf_reset(&s->buf);\n+\t\t\tif (undecided_previous >= 0) {\n+\t\t\t\tpermitted |= ALLOW_GOTO_PREVIOUS_UNDECIDED_HUNK;\n+\t\t\t\tstrbuf_addstr(&s->buf, \",k\");\n+\t\t\t}\n+\t\t\tif (hunk_index) {\n+\t\t\t\tpermitted |= ALLOW_GOTO_PREVIOUS_HUNK;\n+\t\t\t\tstrbuf_addstr(&s->buf, \",K\");\n+\t\t\t}\n+\t\t\tif (undecided_next >= 0) {\n+\t\t\t\tpermitted |= ALLOW_GOTO_NEXT_UNDECIDED_HUNK;\n+\t\t\t\tstrbuf_addstr(&s->buf, \",j\");\n+\t\t\t}\n+\t\t\tif (hunk_index + 1 < file_diff->hunk_nr) {\n+\t\t\t\tpermitted |= ALLOW_GOTO_NEXT_HUNK;\n+\t\t\t\tstrbuf_addstr(&s->buf, \",J\");\n+\t\t\t}\n+\t\t\tif (file_diff->hunk_nr > 1) {\n+\t\t\t\tpermitted |= ALLOW_SEARCH_AND_GOTO;\n+\t\t\t\tstrbuf_addstr(&s->buf, \",g,/\");\n+\t\t\t}\n+\t\t\tif (hunk->splittable_into > 1) {\n+\t\t\t\tpermitted |= ALLOW_SPLIT;\n+\t\t\t\tstrbuf_addstr(&s->buf, \",s\");\n+\t\t\t}\n+\t\t\tif (hunk_index + 1 > file_diff->mode_change &&\n+\t\t\t    !file_diff->deleted) {\n+\t\t\t\tpermitted |= ALLOW_EDIT;\n+\t\t\t\tstrbuf_addstr(&s->buf, \",e\");\n+\t\t\t}\n \t\t}\n \t\tif (file_diff->deleted)\n \t\t\tprompt_mode_type = PROMPT_DELETION;\n@@ -1438,7 +1445,9 @@ static int patch_update_file(struct add_p_state *s,\n \t\tcolor_fprintf(stdout, s->s.prompt_color,\n \t\t\t      \"(%\"PRIuMAX\"/%\"PRIuMAX\") \",\n \t\t\t      (uintmax_t)hunk_index + 1,\n-\t\t\t      (uintmax_t)file_diff->hunk_nr);\n+\t\t\t      (uintmax_t)(file_diff->hunk_nr\n+\t\t\t\t\t\t? file_diff->hunk_nr\n+\t\t\t\t\t\t: 1));\n \t\tcolor_fprintf(stdout, s->s.prompt_color,\n \t\t\t      _(s->mode->prompt_mode[prompt_mode_type]),\n \t\t\t      s->buf.buf);\n@@ -1458,16 +1467,24 @@ static int patch_update_file(struct add_p_state *s,\n \t\t\thunk->use = SKIP_HUNK;\n \t\t\tgoto soft_increment;\n \t\t} else if (ch == 'a') {\n-\t\t\tfor (; hunk_index < file_diff->hunk_nr; hunk_index++) {\n-\t\t\t\thunk = file_diff->hunk + hunk_index;\n-\t\t\t\tif (hunk->use == UNDECIDED_HUNK)\n-\t\t\t\t\thunk->use = USE_HUNK;\n+\t\t\tif (file_diff->hunk_nr) {\n+\t\t\t\tfor (; hunk_index < file_diff->hunk_nr; hunk_index++) {\n+\t\t\t\t\thunk = file_diff->hunk + hunk_index;\n+\t\t\t\t\tif (hunk->use == UNDECIDED_HUNK)\n+\t\t\t\t\t\thunk->use = USE_HUNK;\n+\t\t\t\t}\n+\t\t\t} else if (hunk->use == UNDECIDED_HUNK) {\n+\t\t\t\thunk->use = USE_HUNK;\n \t\t\t}\n \t\t} else if (ch == 'd' || ch == 'q') {\n-\t\t\tfor (; hunk_index < file_diff->hunk_nr; hunk_index++) {\n-\t\t\t\thunk = file_diff->hunk + hunk_index;\n-\t\t\t\tif (hunk->use == UNDECIDED_HUNK)\n-\t\t\t\t\thunk->use = SKIP_HUNK;\n+\t\t\tif (file_diff->hunk_nr) {\n+\t\t\t\tfor (; hunk_index < file_diff->hunk_nr; hunk_index++) {\n+\t\t\t\t\thunk = file_diff->hunk + hunk_index;\n+\t\t\t\t\tif (hunk->use == UNDECIDED_HUNK)\n+\t\t\t\t\t\thunk->use = SKIP_HUNK;\n+\t\t\t\t}\n+\t\t\t} else if (hunk->use == UNDECIDED_HUNK) {\n+\t\t\t\thunk->use = SKIP_HUNK;\n \t\t\t}\n \t\t\tif (ch == 'q') {\n \t\t\t\tquit = 1;\n@@ -1625,7 +1642,8 @@ static int patch_update_file(struct add_p_state *s,\n \t\tif (file_diff->hunk[i].use == USE_HUNK)\n \t\t\tbreak;\n \n-\tif (i < file_diff->hunk_nr) {\n+\tif (i < file_diff->hunk_nr ||\n+\t    (!file_diff->hunk_nr && file_diff->head.use == USE_HUNK)) {\n \t\t/* At least one hunk selected: apply */\n \t\tstrbuf_reset(&s->buf);\n \t\treassemble_patch(s, file_diff, 0, &s->buf);\ndiff --git a/git-add--interactive.perl b/git-add--interactive.perl\nindex f36c0078ac..6771c0a026 100755\n--- a/git-add--interactive.perl\n+++ b/git-add--interactive.perl\n@@ -754,13 +754,16 @@ sub parse_diff_header {\n \tmy $head = { TEXT => [], DISPLAY => [], TYPE => 'header' };\n \tmy $mode = { TEXT => [], DISPLAY => [], TYPE => 'mode' };\n \tmy $deletion = { TEXT => [], DISPLAY => [], TYPE => 'deletion' };\n-\tmy $addition = { TEXT => [], DISPLAY => [], TYPE => 'addition' };\n+\tmy $addition;\n \n \tfor (my $i = 0; $i < @{$src->{TEXT}}; $i++) {\n+\t\tif ($src->{TEXT}->[$i] =~ /^new file/) {\n+\t\t\t$addition = 1;\n+\t\t\t$head->{TYPE} = 'addition';\n+\t\t}\n \t\tmy $dest =\n \t\t   $src->{TEXT}->[$i] =~ /^(old|new) mode (\\d+)$/ ? $mode :\n \t\t   $src->{TEXT}->[$i] =~ /^deleted file/ ? $deletion :\n-\t\t   $src->{TEXT}->[$i] =~ /^new file/ ? $addition :\n \t\t   $head;\n \t\tpush @{$dest->{TEXT}}, $src->{TEXT}->[$i];\n \t\tpush @{$dest->{DISPLAY}}, $src->{DISPLAY}->[$i];\n@@ -1501,12 +1504,6 @@ sub patch_update_file {\n \t\t\tpush @{$deletion->{DISPLAY}}, @{$hunk->{DISPLAY}};\n \t\t}\n \t\t@hunk = ($deletion);\n-\t} elsif (@{$addition->{TEXT}}) {\n-\t\tforeach my $hunk (@hunk) {\n-\t\t\tpush @{$addition->{TEXT}}, @{$hunk->{TEXT}};\n-\t\t\tpush @{$addition->{DISPLAY}}, @{$hunk->{DISPLAY}};\n-\t\t}\n-\t\t@hunk = ($addition);\n \t}\n \n \t$num = scalar @hunk;\n@@ -1516,6 +1513,7 @@ sub patch_update_file {\n \t\tmy ($prev, $next, $other, $undecided, $i);\n \t\t$other = '';\n \n+\t\tlast if ($ix and !$num);\n \t\tif ($num <= $ix) {\n \t\t\t$ix = 0;\n \t\t}\n@@ -1548,35 +1546,51 @@ sub patch_update_file {\n \t\t\t\tlast;\n \t\t\t}\n \t\t}\n-\t\tlast if (!$undecided);\n+\t\tlast if (!$undecided && ($num || !$addition));\n \n-\t\tif ($hunk[$ix]{TYPE} eq 'hunk' &&\n-\t\t    hunk_splittable($hunk[$ix]{TEXT})) {\n-\t\t\t$other .= ',s';\n-\t\t}\n-\t\tif ($hunk[$ix]{TYPE} eq 'hunk') {\n-\t\t\t$other .= ',e';\n-\t\t}\n-\t\tfor (@{$hunk[$ix]{DISPLAY}}) {\n-\t\t\tprint;\n+\t\tif ($num) {\n+\t\t\tif ($hunk[$ix]{TYPE} eq 'hunk' &&\n+\t\t\t    hunk_splittable($hunk[$ix]{TEXT})) {\n+\t\t\t\t$other .= ',s';\n+\t\t\t}\n+\t\t\tif ($hunk[$ix]{TYPE} eq 'hunk') {\n+\t\t\t\t$other .= ',e';\n+\t\t\t}\n+\t\t\tfor (@{$hunk[$ix]{DISPLAY}}) {\n+\t\t\t\tprint;\n+\t\t\t}\n \t\t}\n-\t\tprint colored $prompt_color, \"(\", ($ix+1), \"/$num) \",\n-\t\t\tsprintf(__($patch_update_prompt_modes{$patch_mode}{$hunk[$ix]{TYPE}}), $other);\n+\t\tmy $type = $num ? $hunk[$ix]{TYPE} : $head->{TYPE};\n+\t\tprint colored $prompt_color, \"(\", ($ix+1), \"/\", ($num ? $num : 1), \") \",\n+\t\t\tsprintf(__($patch_update_prompt_modes{$patch_mode}{$type}), $other);\n \n \t\tmy $line = prompt_single_character;\n \t\tlast unless defined $line;\n \t\tif ($line) {\n \t\t\tif ($line =~ /^y/i) {\n-\t\t\t\t$hunk[$ix]{USE} = 1;\n+\t\t\t\tif ($num) {\n+\t\t\t\t\t$hunk[$ix]{USE} = 1;\n+\t\t\t\t} else {\n+\t\t\t\t\t$head->{USE} = 1;\n+\t\t\t\t}\n \t\t\t}\n \t\t\telsif ($line =~ /^n/i) {\n-\t\t\t\t$hunk[$ix]{USE} = 0;\n+\t\t\t\tif ($num) {\n+\t\t\t\t\t$hunk[$ix]{USE} = 0;\n+\t\t\t\t} else {\n+\t\t\t\t\t$head->{USE} = 0;\n+\t\t\t\t}\n \t\t\t}\n \t\t\telsif ($line =~ /^a/i) {\n-\t\t\t\twhile ($ix < $num) {\n-\t\t\t\t\tif (!defined $hunk[$ix]{USE}) {\n-\t\t\t\t\t\t$hunk[$ix]{USE} = 1;\n+\t\t\t\tif ($num) {\n+\t\t\t\t\twhile ($ix < $num) {\n+\t\t\t\t\t\tif (!defined $hunk[$ix]{USE}) {\n+\t\t\t\t\t\t\t$hunk[$ix]{USE} = 1;\n+\t\t\t\t\t\t}\n+\t\t\t\t\t\t$ix++;\n \t\t\t\t\t}\n+\t\t\t\t} else {\n+\t\t\t\t\t$head->{USE} = 1;\n \t\t\t\t\t$ix++;\n \t\t\t\t}\n \t\t\t\tnext;\n@@ -1613,19 +1627,28 @@ sub patch_update_file {\n \t\t\t\tnext;\n \t\t\t}\n \t\t\telsif ($line =~ /^d/i) {\n-\t\t\t\twhile ($ix < $num) {\n-\t\t\t\t\tif (!defined $hunk[$ix]{USE}) {\n-\t\t\t\t\t\t$hunk[$ix]{USE} = 0;\n+\t\t\t\tif ($num) {\n+\t\t\t\t\twhile ($ix < $num) {\n+\t\t\t\t\t\tif (!defined $hunk[$ix]{USE}) {\n+\t\t\t\t\t\t\t$hunk[$ix]{USE} = 0;\n+\t\t\t\t\t\t}\n+\t\t\t\t\t\t$ix++;\n \t\t\t\t\t}\n+\t\t\t\t} else {\n+\t\t\t\t\t$head->{USE} = 0;\n \t\t\t\t\t$ix++;\n \t\t\t\t}\n \t\t\t\tnext;\n \t\t\t}\n \t\t\telsif ($line =~ /^q/i) {\n-\t\t\t\tfor ($i = 0; $i < $num; $i++) {\n-\t\t\t\t\tif (!defined $hunk[$i]{USE}) {\n-\t\t\t\t\t\t$hunk[$i]{USE} = 0;\n+\t\t\t\tif ($num) {\n+\t\t\t\t\tfor ($i = 0; $i < $num; $i++) {\n+\t\t\t\t\t\tif (!defined $hunk[$i]{USE}) {\n+\t\t\t\t\t\t\t$hunk[$i]{USE} = 0;\n+\t\t\t\t\t\t}\n \t\t\t\t\t}\n+\t\t\t\t} elsif (!defined $head->{USE}) {\n+\t\t\t\t\t$head->{USE} = 0;\n \t\t\t\t}\n \t\t\t\t$quit = 1;\n \t\t\t\tlast;\n@@ -1743,7 +1766,7 @@ sub patch_update_file {\n \t\t}\n \t}\n \n-\t@hunk = coalesce_overlapping_hunks(@hunk);\n+\t@hunk = coalesce_overlapping_hunks(@hunk) if ($num);\n \n \tmy $n_lofs = 0;\n \tmy @result = ();\n@@ -1753,7 +1776,7 @@ sub patch_update_file {\n \t\t}\n \t}\n \n-\tif (@result) {\n+\tif (@result or $head->{USE}) {\n \t\tmy @patch = reassemble_patch($head->{TEXT}, @result);\n \t\tmy $apply_routine = $patch_mode_flavour{APPLY};\n \t\t&$apply_routine(@patch);\ndiff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh\nindex fb73a847cb..ebe84a6353 100755\n--- a/t/t3701-add-interactive.sh\n+++ b/t/t3701-add-interactive.sh\n@@ -814,6 +814,44 @@ test_expect_success 'checkout -p works with pathological context lines' '\n \ttest_cmp expect a\n '\n \n+# This should be called from a subshell as it sets a temporary editor\n+setup_new_file() {\n+\twrite_script new-file-editor.sh <<-\\EOF &&\n+\tsed /^#/d \"$1\" >patch &&\n+\tsed /^+c/d patch >\"$1\"\n+\tEOF\n+\ttest_set_editor \"$(pwd)/new-file-editor.sh\" &&\n+\ttest_write_lines a b c d e f >new-file &&\n+\ttest_write_lines a b d e f >new-file-expect &&\n+\ttest_write_lines \"@@ -0,0 +1,6 @@\" +a +b +c +d +e +f >patch-expect\n+}\n+\n+test_expect_success 'add -N followed by add -p patch editing' '\n+\tgit reset --hard &&\n+\t(\n+\t\tsetup_new_file &&\n+\t\tgit add -N new-file &&\n+\t\ttest_write_lines e n q | git add -p &&\n+\t\tgit cat-file blob :new-file >actual &&\n+\t\ttest_cmp new-file-expect actual &&\n+\t\ttest_cmp patch-expect patch\n+\t)\n+'\n+\n+test_expect_success 'checkout -p patch editing of added file' '\n+\tgit reset --hard &&\n+\t(\n+\t\tsetup_new_file &&\n+\t\tgit add new-file &&\n+\t\tgit commit -m \"add new file\" &&\n+\t\tgit rm new-file &&\n+\t\tgit commit -m \"remove new file\" &&\n+\t\ttest_write_lines e n q | git checkout -p HEAD^ &&\n+\t\ttest_cmp new-file-expect new-file &&\n+\t\ttest_cmp patch-expect patch\n+\t)\n+'\n+\n test_expect_success 'show help from add--helper' '\n \tgit reset --hard &&\n \tcat >expect <<-EOF &&\n\nbase-commit: 3a238e539bcdfe3f9eb5010fd218640c1b499f7a\n-- \ngitgitgadget\n"}]}