From 06c81a1118a6c9a77505b0ae3092576fa4c01763 Mon Sep 17 00:00:00 2001 From: Abraham Samuel Adekunle Date: Sat, 14 Feb 2026 12:03:54 +0100 Subject: [PATCH 1/4] interactive -p: add new `--auto-advance` flag When using the interactive add, reset, stash or checkout machinery, we do not have the option of reworking with a file when selecting hunks, because the session automatically advances to the next file or ends if we have just one file. Introduce the flag `--auto-advance` which auto advances by default, when interactively selecting patches with the '--patch' option. However, the `--no-auto-advance` option does not auto advance, thereby allowing users the option to rework with files. Signed-off-by: Abraham Samuel Adekunle Signed-off-by: Junio C Hamano --- add-interactive.c | 2 ++ add-interactive.h | 4 +++- builtin/add.c | 4 ++++ builtin/checkout.c | 7 +++++++ builtin/reset.c | 4 ++++ builtin/stash.c | 8 ++++++++ t/t9902-completion.sh | 1 + 7 files changed, 29 insertions(+), 1 deletion(-) diff --git a/add-interactive.c b/add-interactive.c index 68fc09547d..ce9e737e0f 100644 --- a/add-interactive.c +++ b/add-interactive.c @@ -64,6 +64,7 @@ void init_add_i_state(struct add_i_state *s, struct repository *r, s->r = r; s->context = -1; s->interhunkcontext = -1; + s->auto_advance = add_p_opt->auto_advance; s->use_color_interactive = check_color_config(r, "color.interactive"); @@ -1017,6 +1018,7 @@ static int run_patch(struct add_i_state *s, const struct pathspec *ps, struct add_p_opt add_p_opt = { .context = s->context, .interhunkcontext = s->interhunkcontext, + .auto_advance = s->auto_advance }; struct strvec args = STRVEC_INIT; struct pathspec ps_selected = { 0 }; diff --git a/add-interactive.h b/add-interactive.h index da49502b76..7843397775 100644 --- a/add-interactive.h +++ b/add-interactive.h @@ -6,9 +6,10 @@ struct add_p_opt { int context; int interhunkcontext; + int auto_advance; }; -#define ADD_P_OPT_INIT { .context = -1, .interhunkcontext = -1 } +#define ADD_P_OPT_INIT { .context = -1, .interhunkcontext = -1, .auto_advance = 1 } struct add_i_state { struct repository *r; @@ -29,6 +30,7 @@ struct add_i_state { int use_single_key; char *interactive_diff_filter, *interactive_diff_algorithm; int context, interhunkcontext; + int auto_advance; }; void init_add_i_state(struct add_i_state *s, struct repository *r, diff --git a/builtin/add.c b/builtin/add.c index 32709794b3..4357f87b7f 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -256,6 +256,8 @@ static struct option builtin_add_options[] = { OPT_GROUP(""), OPT_BOOL('i', "interactive", &add_interactive, N_("interactive picking")), OPT_BOOL('p', "patch", &patch_interactive, N_("select hunks interactively")), + OPT_BOOL(0, "auto-advance", &add_p_opt.auto_advance, + N_("auto advance to the next file when selecting hunks interactively")), OPT_DIFF_UNIFIED(&add_p_opt.context), OPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext), OPT_BOOL('e', "edit", &edit_interactive, N_("edit current diff and apply")), @@ -418,6 +420,8 @@ int cmd_add(int argc, die(_("the option '%s' requires '%s'"), "--unified", "--interactive/--patch"); if (add_p_opt.interhunkcontext != -1) die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--interactive/--patch"); + if (!add_p_opt.auto_advance) + die(_("the option '%s' requires '%s'"), "--no-auto-advance", "--interactive/--patch"); } if (edit_interactive) { diff --git a/builtin/checkout.c b/builtin/checkout.c index 66b69df6e6..6710b53fd0 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -63,6 +63,7 @@ struct checkout_opts { int patch_mode; int patch_context; int patch_interhunk_context; + int auto_advance; int quiet; int merge; int force; @@ -111,6 +112,7 @@ struct checkout_opts { .merge = -1, \ .patch_context = -1, \ .patch_interhunk_context = -1, \ + .auto_advance = 1, \ } struct branch_info { @@ -549,6 +551,7 @@ static int checkout_paths(const struct checkout_opts *opts, struct add_p_opt add_p_opt = { .context = opts->patch_context, .interhunkcontext = opts->patch_interhunk_context, + .auto_advance = opts->auto_advance }; const char *rev = new_branch_info->name; char rev_oid[GIT_MAX_HEXSZ + 1]; @@ -1801,6 +1804,8 @@ static int checkout_main(int argc, const char **argv, const char *prefix, die(_("the option '%s' requires '%s'"), "--unified", "--patch"); if (opts->patch_interhunk_context != -1) die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--patch"); + if (!opts->auto_advance) + die(_("the option '%s' requires '%s'"), "--no-auto-advance", "--patch"); } if (opts->show_progress < 0) { @@ -1999,6 +2004,8 @@ int cmd_checkout(int argc, OPT_BOOL(0, "guess", &opts.dwim_new_local_branch, N_("second guess 'git checkout ' (default)")), OPT_BOOL(0, "overlay", &opts.overlay_mode, N_("use overlay mode (default)")), + OPT_BOOL(0, "auto-advance", &opts.auto_advance, + N_("auto advance to the next file when selecting hunks interactively")), OPT_END() }; diff --git a/builtin/reset.c b/builtin/reset.c index ed35802af1..7535591530 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -371,6 +371,8 @@ int cmd_reset(int argc, PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater), OPT_BOOL('p', "patch", &patch_mode, N_("select hunks interactively")), + OPT_BOOL(0, "auto-advance", &add_p_opt.auto_advance, + N_("auto advance to the next file when selecting hunks interactively")), OPT_DIFF_UNIFIED(&add_p_opt.context), OPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext), OPT_BOOL('N', "intent-to-add", &intent_to_add, @@ -443,6 +445,8 @@ int cmd_reset(int argc, die(_("the option '%s' requires '%s'"), "--unified", "--patch"); if (add_p_opt.interhunkcontext != -1) die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--patch"); + if (!add_p_opt.auto_advance) + die(_("the option '%s' requires '%s'"), "--no-auto-advance", "--patch"); } /* git reset tree [--] paths... can be used to diff --git a/builtin/stash.c b/builtin/stash.c index 948eba06fb..1a98358fa6 100644 --- a/builtin/stash.c +++ b/builtin/stash.c @@ -1849,6 +1849,8 @@ static int push_stash(int argc, const char **argv, const char *prefix, N_("stash staged changes only")), OPT_BOOL('p', "patch", &patch_mode, N_("stash in patch mode")), + OPT_BOOL(0, "auto-advance", &add_p_opt.auto_advance, + N_("auto advance to the next file when selecting hunks interactively")), OPT_DIFF_UNIFIED(&add_p_opt.context), OPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext), OPT__QUIET(&quiet, N_("quiet mode")), @@ -1911,6 +1913,8 @@ static int push_stash(int argc, const char **argv, const char *prefix, die(_("the option '%s' requires '%s'"), "--unified", "--patch"); if (add_p_opt.interhunkcontext != -1) die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--patch"); + if (!add_p_opt.auto_advance) + die(_("the option '%s' requires '%s'"), "--no-auto-advance", "--patch"); } if (add_p_opt.context < -1) @@ -1952,6 +1956,8 @@ static int save_stash(int argc, const char **argv, const char *prefix, N_("stash staged changes only")), OPT_BOOL('p', "patch", &patch_mode, N_("stash in patch mode")), + OPT_BOOL(0, "auto-advance", &add_p_opt.auto_advance, + N_("auto advance to the next file when selecting hunks interactively")), OPT_DIFF_UNIFIED(&add_p_opt.context), OPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext), OPT__QUIET(&quiet, N_("quiet mode")), @@ -1983,6 +1989,8 @@ static int save_stash(int argc, const char **argv, const char *prefix, die(_("the option '%s' requires '%s'"), "--unified", "--patch"); if (add_p_opt.interhunkcontext != -1) die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--patch"); + if (!add_p_opt.auto_advance) + die(_("the option '%s' requires '%s'"), "--no-auto-advance", "--patch"); } ret = do_push_stash(&ps, stash_msg, quiet, keep_index, diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh index 964e1f1569..3da5527ae5 100755 --- a/t/t9902-completion.sh +++ b/t/t9902-completion.sh @@ -2601,6 +2601,7 @@ test_expect_success 'double dash "git checkout"' ' --ignore-skip-worktree-bits Z --ignore-other-worktrees Z --recurse-submodules Z + --auto-advance Z --progress Z --guess Z --no-guess Z From 57088095e90a940d232e36594c6fa761f283d35d Mon Sep 17 00:00:00 2001 From: Abraham Samuel Adekunle Date: Sat, 14 Feb 2026 12:04:44 +0100 Subject: [PATCH 2/4] add-patch: modify patch_update_file() signature The function `patch_update_file()` takes the add_p_state struct pointer and the current `struct file_diff` pointer and returns an int. When using the `--no-auto-advance` flag, we want to be able to request the next or previous file from the caller. Modify the function signature to instead take the index of the current `file_diff` and the `add_p_state` struct pointer so that we can compute the `file_diff` from the index while also having access to the file index. This will help us request the next or previous file from the caller. Signed-off-by: Abraham Samuel Adekunle Signed-off-by: Junio C Hamano --- add-patch.c | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/add-patch.c b/add-patch.c index 173a53241e..49eb895745 100644 --- a/add-patch.c +++ b/add-patch.c @@ -1441,20 +1441,21 @@ static bool get_first_undecided(const struct file_diff *file_diff, size_t *idx) return false; } -static int patch_update_file(struct add_p_state *s, - struct file_diff *file_diff) +static size_t patch_update_file(struct add_p_state *s, size_t idx) { size_t hunk_index = 0; ssize_t i, undecided_previous, undecided_next, rendered_hunk_index = -1; struct hunk *hunk; char ch; struct child_process cp = CHILD_PROCESS_INIT; - int colored = !!s->colored.len, quit = 0, use_pager = 0; + int colored = !!s->colored.len, use_pager = 0; enum prompt_mode_type prompt_mode_type; + struct file_diff *file_diff = s->file_diff + idx; + size_t patch_update_resp = idx; /* Empty added files have no hunks */ if (!file_diff->hunk_nr && !file_diff->added) - return 0; + return patch_update_resp + 1; strbuf_reset(&s->buf); render_diff_header(s, file_diff, colored, &s->buf); @@ -1498,9 +1499,10 @@ static int patch_update_file(struct add_p_state *s, /* Everything decided? */ if (undecided_previous < 0 && undecided_next < 0 && - hunk->use != UNDECIDED_HUNK) - break; - + hunk->use != UNDECIDED_HUNK) { + patch_update_resp++; + break; + } strbuf_reset(&s->buf); if (file_diff->hunk_nr) { if (rendered_hunk_index != hunk_index) { @@ -1570,7 +1572,7 @@ static int patch_update_file(struct add_p_state *s, fputs(s->s.reset_color_interactive, stdout); fflush(stdout); if (read_single_character(s) == EOF) { - quit = 1; + patch_update_resp = s->file_diff_nr; break; } @@ -1616,7 +1618,7 @@ soft_increment: hunk->use = SKIP_HUNK; } } else if (ch == 'q') { - quit = 1; + patch_update_resp = s->file_diff_nr; break; } else if (s->answer.buf[0] == 'K') { if (permitted & ALLOW_GOTO_PREVIOUS_HUNK) @@ -1803,7 +1805,7 @@ soft_increment: } putchar('\n'); - return quit; + return patch_update_resp; } int run_add_p(struct repository *r, enum add_p_mode mode, @@ -1852,11 +1854,15 @@ int run_add_p(struct repository *r, enum add_p_mode mode, return -1; } - for (i = 0; i < s.file_diff_nr; i++) - if (s.file_diff[i].binary && !s.file_diff[i].hunk_nr) + for (i = 0; i < s.file_diff_nr;) { + if (s.file_diff[i].binary && !s.file_diff[i].hunk_nr) { binary_count++; - else if (patch_update_file(&s, s.file_diff + i)) + i++; + continue; + } + if ((i = patch_update_file(&s, i)) == s.file_diff_nr) break; + } if (s.file_diff_nr == 0) err(&s, _("No changes.")); From d3cce5e76fcbce00d33013424bece954f11fa2f3 Mon Sep 17 00:00:00 2001 From: Abraham Samuel Adekunle Date: Sat, 14 Feb 2026 12:06:06 +0100 Subject: [PATCH 3/4] add-patch: allow all-or-none application of patches When the flag `--no-auto-advance` is used with `--patch`, if the user has decided `USE` on a hunk in a file, goes to another file, and then returns to this file and changes the previous decision on the hunk to `SKIP`, because the patch has already been applied, the last decision is not registered and the now SKIPPED hunk is still applied. Move the logic for applying patches into a function so that we can reuse this logic to implement the all or non application of the patches after the user is done with the hunk selection. Signed-off-by: Abraham Samuel Adekunle Signed-off-by: Junio C Hamano --- add-patch.c | 62 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 35 insertions(+), 27 deletions(-) diff --git a/add-patch.c b/add-patch.c index 49eb895745..d6528d4818 100644 --- a/add-patch.c +++ b/add-patch.c @@ -1420,6 +1420,40 @@ N_("j - go to the next undecided hunk, roll over at the bottom\n" "P - print the current hunk using the pager\n" "? - print help\n"); +static void apply_patch(struct add_p_state *s, struct file_diff *file_diff) +{ + struct child_process cp = CHILD_PROCESS_INIT; + size_t j; + + /* Any hunk to be used? */ + for (j = 0; j < file_diff->hunk_nr; j++) + if (file_diff->hunk[j].use == USE_HUNK) + break; + + if (j < file_diff->hunk_nr || + (!file_diff->hunk_nr && file_diff->head.use == USE_HUNK)) { + /* At least one hunk selected: apply */ + strbuf_reset(&s->buf); + reassemble_patch(s, file_diff, 0, &s->buf); + + discard_index(s->s.r->index); + if (s->mode->apply_for_checkout) + apply_for_checkout(s, &s->buf, + s->mode->is_reverse); + else { + setup_child_process(s, &cp, "apply", NULL); + strvec_pushv(&cp.args, s->mode->apply_args); + if (pipe_command(&cp, s->buf.buf, s->buf.len, + NULL, 0, NULL, 0)) + error(_("'git apply' failed")); + } + if (repo_read_index(s->s.r) >= 0) + repo_refresh_and_write_index(s->s.r, REFRESH_QUIET, 0, + 1, NULL, NULL, NULL); + } + +} + static size_t dec_mod(size_t a, size_t m) { return a > 0 ? a - 1 : m - 1; @@ -1447,7 +1481,6 @@ static size_t patch_update_file(struct add_p_state *s, size_t idx) ssize_t i, undecided_previous, undecided_next, rendered_hunk_index = -1; struct hunk *hunk; char ch; - struct child_process cp = CHILD_PROCESS_INIT; int colored = !!s->colored.len, use_pager = 0; enum prompt_mode_type prompt_mode_type; struct file_diff *file_diff = s->file_diff + idx; @@ -1777,32 +1810,7 @@ soft_increment: } } - /* Any hunk to be used? */ - for (i = 0; i < file_diff->hunk_nr; i++) - if (file_diff->hunk[i].use == USE_HUNK) - break; - - if (i < file_diff->hunk_nr || - (!file_diff->hunk_nr && file_diff->head.use == USE_HUNK)) { - /* At least one hunk selected: apply */ - strbuf_reset(&s->buf); - reassemble_patch(s, file_diff, 0, &s->buf); - - discard_index(s->s.r->index); - if (s->mode->apply_for_checkout) - apply_for_checkout(s, &s->buf, - s->mode->is_reverse); - else { - setup_child_process(s, &cp, "apply", NULL); - strvec_pushv(&cp.args, s->mode->apply_args); - if (pipe_command(&cp, s->buf.buf, s->buf.len, - NULL, 0, NULL, 0)) - error(_("'git apply' failed")); - } - if (repo_read_index(s->s.r) >= 0) - repo_refresh_and_write_index(s->s.r, REFRESH_QUIET, 0, - 1, NULL, NULL, NULL); - } + apply_patch(s, file_diff); putchar('\n'); return patch_update_resp; From 417b181f99ce53f50dea6541430cfe1f1f359a6a Mon Sep 17 00:00:00 2001 From: Abraham Samuel Adekunle Date: Sat, 14 Feb 2026 12:06:55 +0100 Subject: [PATCH 4/4] add-patch: allow interfile navigation when selecting hunks After deciding on all hunks in a file, the interactive session advances automatically to the next file if there is another, or the process ends. Now using the `--no-auto-advance` flag with `--patch`, the process does not advance automatically. A user can choose to go to the next file by pressing '>' or the previous file by pressing '<', before or after deciding on all hunks in the current file. After all hunks have been decided in a file, the user can still rework with the file by applying the options available in the permit set for that hunk, and after all the decisions, the user presses 'q' to submit. After all hunks have been decided, the user can press '?' which will show the hunk selection summary in the help patch remainder text including the total hunks, number of hunks marked for use and number of hunks marked for skip. This feature is enabled by passing the `--no-auto-advance` flag to `--patch` option of the subcommands add, stash, reset, and checkout. Signed-off-by: Abraham Samuel Adekunle Signed-off-by: Junio C Hamano --- add-patch.c | 66 ++++++++++++++++++++++-- t/t3701-add-interactive.sh | 100 +++++++++++++++++++++++++++++++++++++ 2 files changed, 161 insertions(+), 5 deletions(-) diff --git a/add-patch.c b/add-patch.c index d6528d4818..d9900d58d5 100644 --- a/add-patch.c +++ b/add-patch.c @@ -1418,7 +1418,10 @@ N_("j - go to the next undecided hunk, roll over at the bottom\n" "e - manually edit the current hunk\n" "p - print the current hunk\n" "P - print the current hunk using the pager\n" - "? - print help\n"); + "> - go to the next file, roll over at the bottom\n" + "< - go to the previous file, roll over at the top\n" + "? - print help\n" + "HUNKS SUMMARY - Hunks: %d, USE: %d, SKIP: %d\n"); static void apply_patch(struct add_p_state *s, struct file_diff *file_diff) { @@ -1483,6 +1486,7 @@ static size_t patch_update_file(struct add_p_state *s, size_t idx) char ch; int colored = !!s->colored.len, use_pager = 0; enum prompt_mode_type prompt_mode_type; + int all_decided = 0; struct file_diff *file_diff = s->file_diff + idx; size_t patch_update_resp = idx; @@ -1501,7 +1505,9 @@ static size_t patch_update_file(struct add_p_state *s, size_t idx) ALLOW_GOTO_NEXT_UNDECIDED_HUNK = 1 << 3, ALLOW_SEARCH_AND_GOTO = 1 << 4, ALLOW_SPLIT = 1 << 5, - ALLOW_EDIT = 1 << 6 + ALLOW_EDIT = 1 << 6, + ALLOW_GOTO_PREVIOUS_FILE = 1 << 7, + ALLOW_GOTO_NEXT_FILE = 1 << 8 } permitted = 0; if (hunk_index >= file_diff->hunk_nr) @@ -1533,8 +1539,12 @@ static size_t patch_update_file(struct add_p_state *s, size_t idx) /* Everything decided? */ if (undecided_previous < 0 && undecided_next < 0 && hunk->use != UNDECIDED_HUNK) { - patch_update_resp++; - break; + if (!s->s.auto_advance) + all_decided = 1; + else { + patch_update_resp++; + break; + } } strbuf_reset(&s->buf); if (file_diff->hunk_nr) { @@ -1583,6 +1593,14 @@ static size_t patch_update_file(struct add_p_state *s, size_t idx) permitted |= ALLOW_EDIT; strbuf_addstr(&s->buf, ",e"); } + if (!s->s.auto_advance && s->file_diff_nr > 1) { + permitted |= ALLOW_GOTO_NEXT_FILE; + strbuf_addstr(&s->buf, ",>"); + } + if (!s->s.auto_advance && s->file_diff_nr > 1) { + permitted |= ALLOW_GOTO_PREVIOUS_FILE; + strbuf_addstr(&s->buf, ",<"); + } strbuf_addstr(&s->buf, ",p,P"); } if (file_diff->deleted) @@ -1653,6 +1671,28 @@ soft_increment: } else if (ch == 'q') { patch_update_resp = s->file_diff_nr; break; + } else if (!s->s.auto_advance && s->answer.buf[0] == '>') { + if (permitted & ALLOW_GOTO_NEXT_FILE) { + if (patch_update_resp == s->file_diff_nr - 1) + patch_update_resp = 0; + else + patch_update_resp++; + break; + } else { + err(s, _("No next file")); + continue; + } + } else if (!s->s.auto_advance && s->answer.buf[0] == '<') { + if (permitted & ALLOW_GOTO_PREVIOUS_FILE) { + if (patch_update_resp == 0) + patch_update_resp = s->file_diff_nr - 1; + else + patch_update_resp--; + break; + } else { + err(s, _("No previous file")); + continue; + } } else if (s->answer.buf[0] == 'K') { if (permitted & ALLOW_GOTO_PREVIOUS_HUNK) hunk_index = dec_mod(hunk_index, @@ -1798,6 +1838,18 @@ soft_increment: * commands shown in the prompt that are not * always available. */ + if (all_decided && !strncmp(p, "HUNKS SUMMARY", 13)) { + int total = file_diff->hunk_nr, used = 0, skipped = 0; + + for (i = 0; i < file_diff->hunk_nr; i++) { + if (file_diff->hunk[i].use == USE_HUNK) + used += 1; + if (file_diff->hunk[i].use == SKIP_HUNK) + skipped += 1; + } + color_fprintf_ln(stdout, s->s.help_color, _(p), + total, used, skipped); + } if (*p != '?' && !strchr(s->buf.buf, *p)) continue; @@ -1810,7 +1862,8 @@ soft_increment: } } - apply_patch(s, file_diff); + if (s->s.auto_advance) + apply_patch(s, file_diff); putchar('\n'); return patch_update_resp; @@ -1871,6 +1924,9 @@ int run_add_p(struct repository *r, enum add_p_mode mode, if ((i = patch_update_file(&s, i)) == s.file_diff_nr) break; } + if (!s.s.auto_advance) + for (i = 0; i < s.file_diff_nr; i++) + apply_patch(&s, s.file_diff + i); if (s.file_diff_nr == 0) err(&s, _("No changes.")); diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh index 4285314f35..7924c9a28b 100755 --- a/t/t3701-add-interactive.sh +++ b/t/t3701-add-interactive.sh @@ -1441,5 +1441,105 @@ test_expect_success 'EOF quits' ' test_grep file out && test_grep ! file2 out ' +for cmd in add checkout reset "stash save" "stash push" +do + test_expect_success "$cmd rejects invalid --no-auto-advance options" ' + test_must_fail git $cmd --no-auto-advance 2>actual && + test_grep -E "requires .*--(interactive|patch)" actual + ' +done + +test_expect_success 'manual advance (">") moves to next file with --no-auto-advance' ' + git reset --hard && + echo line1 >first-file && + echo line2 >second-file && + git add -A && + git commit -m initial >/dev/null 2>&1 && + echo change_first >>first-file && + echo change_second >>second-file && + + printf ">\nq\n" | git add -p --no-auto-advance >output.test 2>&1 && + test_grep -E "(a|b)/second-file" output.test +' + +test_expect_success 'select n on a hunk, go to another file, come back and change to y stages' ' + git reset --hard && + echo one >f1 && + echo one >f2 && + git add -A && + git commit -m initial >/dev/null 2>&1 && + echo change1 >>f1 && + echo change2 >>f2 && + + printf "n\n>\n<\ny\nq\n" | git add -p --no-auto-advance >output.staged 2>&1 && + git diff --cached --name-only >staged && + test_grep -E "(a/f1)" output.staged +' + +test_expect_success 'select y on a hunk, go to another file, come back and change to n does not stage' ' + git reset --hard && + echo one >f1 && + echo one >f2 && + git add -A && + git commit -m initial >/dev/null 2>&1 && + echo change1 >>f1 && + echo change2 >>f2 && + + printf "y\n>\n<\nn\nq\n" | git add -p --no-auto-advance >output.unstaged 2>&1 && + git diff --cached --name-only >staged && + test_must_be_empty staged +' + +test_expect_success 'deciding all hunks in a file does not auto advance' ' + git reset --hard && + echo line >stay && + echo line >other && + git add -A && + git commit -m initial >/dev/null 2>&1 && + echo change >>stay && + echo change >>other && + test_write_lines y | git add -p --no-auto-advance >raw-output 2>&1 && + test_grep "(1/1) Stage this hunk (was: y)" raw-output && + test_grep ! "diff --git a/stay b/stay" raw-output +' +test_expect_success 'HUNKS SUMMARY does not show in help text when there are undecided hunks' ' + git reset --hard && + test_write_lines 1 2 3 4 5 6 7 8 9 >f && + git add f && + git commit -m initial >/dev/null 2>&1 && + test_write_lines 1 X 3 4 Y 6 7 Z 9 >f && + test_write_lines s y n | git add -p --no-auto-advance >raw-nostat 2>&1 && + test_grep ! "HUNKS SUMMARY - Hunks: " raw-nostat +' + +test_expect_success 'help text shows HUNK SUMMARY when all hunks have been decided' ' + git reset --hard && + test_write_lines 1 2 3 4 5 6 7 8 9 >f2 && + git add f2 && + git commit -m initial >/dev/null 2>&1 && + test_write_lines 1 X 3 4 Y 6 7 Z 9 >f2 && + printf "s\ny\nn\ny\n?\n" | git add -p --no-auto-advance >raw-stat 2>&1 && + test_grep "HUNKS SUMMARY - Hunks: 3, USE: 2, SKIP: 1" raw-stat +' + +test_expect_success 'selective staging across multiple files with --no-advance' ' + git reset --hard && + test_write_lines 1 2 3 4 5 6 7 8 9 >a.file && + test_write_lines 1 2 3 4 5 6 7 8 9 >b.file && + test_write_lines 1 2 3 4 5 6 7 8 9 >c.file && + git add -A && + git commit -m initial >/dev/null 2>&1 && + test_write_lines 1 A2 3 4 A5 6 7 8 9 >a.file && + test_write_lines 1 2 B3 4 5 6 7 B8 9 >b.file && + test_write_lines C1 2 3 4 5 C6 7 8 9 >c.file && + printf "s\ny\nn\n>\ns\nn\ny\n>\ns\ny\ny\nq\n" | git add -p --no-auto-advance >output.index 2>&1 && + git diff --cached >staged.diff && + test_grep "+A2" staged.diff && + test_grep ! "+A5" staged.diff && + test_grep "+B8" staged.diff && + test_grep ! "+B3" staged.diff && + test_grep "+C1" staged.diff && + test_grep "+C6" staged.diff +' test_done