* [RFC PATCH 0/1] add-patch: Allow reworking with a file after deciding on its hunks
@ 2026-01-23 11:56 Abraham Samuel Adekunle
2026-01-23 11:58 ` [RFC PATCH 1/1] add-patch: Allow reworking with a file after deciding on all " Abraham Samuel Adekunle
2026-01-27 15:43 ` [PATCH v2 0/1] Allow reworking with a file when making hunk decisions Abraham Samuel Adekunle
0 siblings, 2 replies; 54+ messages in thread
From: Abraham Samuel Adekunle @ 2026-01-23 11:56 UTC (permalink / raw)
To: git
Cc: Patrick Steinhardt, Phillip Wood, SZEDER Gábor,
Christian Couder, Kristoffer Haugsbakk, Ben Knoble,
Junio C Hamano
Hello,
In the discussion between Phillip Wood and Junio C Hamano in [1], Junio suggested
some enhancements to the UI of the add interactive.
They include;
i. Add a way to see the previous hunk decision of the current hunk after the
user navigates back with K/J.
ii. Allow reworking with a file after deciding on all its hunks without
auto advancing.
iii. When having multiple modified files, allow switching from the current file to
another file whose hunks have already been decided.
I have been able to work on 'i' above in [2] and this RFC seeks to find suggestions
on 'ii' and maybe 'iii' as this enters design realms
The patch is in no way a final version. I just want to present something that
members giving suggestions can work with.
While trying to follow the suggestions in [1] in the patch, when all hunks
have been decided;
* A what_now prompts appears, allowing navigation with J/K, q to quit
and '>' to go to the next file if there is a next file
* If K/J is used to return to a hunk from the what_now mode, after any new decision,
on the hunk, the user is brought back to the what_now prompt since all hunks
had previously been decided on.
I would appreciate your thoughts on this.
Thanks
1. https://lore.kernel.org/git/xmqqseg9azdc.fsf@gitster.g/
2. https://lore.kernel.org/git/aV_IGCld5T_dBxTs@Adekunles-MacBook-Air.local/
Abraham Samuel Adekunle (1):
add-patch: Allow reworking with a file after deciding on all its hunks
add-patch.c | 71 ++++++++++++++++++++++++++++++++++++++++++++++++-----
1 file changed, 65 insertions(+), 6 deletions(-)
--
2.39.5 (Apple Git-154)
^ permalink raw reply [flat|nested] 54+ messages in thread* [RFC PATCH 1/1] add-patch: Allow reworking with a file after deciding on all its hunks 2026-01-23 11:56 [RFC PATCH 0/1] add-patch: Allow reworking with a file after deciding on its hunks Abraham Samuel Adekunle @ 2026-01-23 11:58 ` Abraham Samuel Adekunle 2026-01-23 16:38 ` Junio C Hamano 2026-01-27 15:43 ` [PATCH v2 0/1] Allow reworking with a file when making hunk decisions Abraham Samuel Adekunle 1 sibling, 1 reply; 54+ messages in thread From: Abraham Samuel Adekunle @ 2026-01-23 11:58 UTC (permalink / raw) To: git Cc: Patrick Steinhardt, Phillip Wood, SZEDER Gábor, Christian Couder, Kristoffer Haugsbakk, Ben Knoble, Junio C Hamano 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. Allow for reworking with a file by introducing a what_now prompt which allows for navigating with J/K or advancing to the next file if there is one. Signed-off-by: Abraham Samuel Adekunle <abrahamadekunle50@gmail.com> --- add-patch.c | 71 ++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 65 insertions(+), 6 deletions(-) diff --git a/add-patch.c b/add-patch.c index 173a53241e..1ac565b0ab 100644 --- a/add-patch.c +++ b/add-patch.c @@ -1449,7 +1449,7 @@ static int patch_update_file(struct add_p_state *s, 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, quit = 0, use_pager = 0, skip_what_now = 0; enum prompt_mode_type prompt_mode_type; /* Empty added files have no hunks */ @@ -1498,12 +1498,61 @@ 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 && !skip_what_now ) { + const char *prompt_whatnow; + /* Allow navigation between hunks or go to next file */ + if (s->file_diff_nr > 1) + prompt_whatnow = _("What now? [J,K,q,>]? "); + else + prompt_whatnow = _("What now? [J,K,q]? "); + printf("%s %s", + s->s.prompt_color, + prompt_whatnow); + if (*s->s.reset_color_interactive) + fputs(s->s.reset_color_interactive, stdout); + fflush(stdout); + if (read_single_character(s) == EOF) { + quit = 1; + break; + } + if (!s->answer.len) + continue; + if (s->answer.buf[0] == '>' && s->file_diff_nr > 1) { + skip_what_now = 0; + break; + } + else if (s->answer.buf[0] == 'K') { + if (file_diff->hunk_nr > 1) { + hunk_index = dec_mod(hunk_index, file_diff->hunk_nr); + skip_what_now = 1; + } + else + err(s, _("No other hunk")); + continue; + } + else if (s->answer.buf[0] == 'J') { + if (file_diff->hunk_nr > 1) { + hunk_index = inc_mod(hunk_index, file_diff->hunk_nr); + skip_what_now = 1; + } + else + err(s, _("No other hunk")); + continue; + } + else if (s->answer.buf[0] == 'q') { + skip_what_now = 0; + quit = 1; + break; + } + else { + err(s, _("All hunks decided (use '?' for help)")); + continue; + } + } strbuf_reset(&s->buf); if (file_diff->hunk_nr) { - if (rendered_hunk_index != hunk_index) { + if (rendered_hunk_index != hunk_index || skip_what_now == 1) { if (use_pager) { setup_pager(the_repository); sigchain_push(SIGPIPE, SIG_IGN); @@ -1586,12 +1635,18 @@ static int patch_update_file(struct add_p_state *s, if (ch == 'y') { hunk->use = USE_HUNK; soft_increment: - hunk_index = undecided_next < 0 ? - file_diff->hunk_nr : undecided_next; + if (skip_what_now) { + hunk_index = inc_mod(hunk_index, file_diff->hunk_nr); + skip_what_now = 0; + } else + hunk_index = undecided_next < 0 ? + file_diff->hunk_nr : undecided_next; } else if (ch == 'n') { hunk->use = SKIP_HUNK; goto soft_increment; } else if (ch == 'a') { + if (skip_what_now) + skip_what_now = 0; if (file_diff->hunk_nr) { for (; hunk_index < file_diff->hunk_nr; hunk_index++) { hunk = file_diff->hunk + hunk_index; @@ -1604,6 +1659,8 @@ static int patch_update_file(struct add_p_state *s, hunk->use = USE_HUNK; } } else if (ch == 'd') { + if (skip_what_now) + skip_what_now = 0; if (file_diff->hunk_nr) { for (; hunk_index < file_diff->hunk_nr; hunk_index++) { hunk = file_diff->hunk + hunk_index; @@ -1616,6 +1673,8 @@ static int patch_update_file(struct add_p_state *s, hunk->use = SKIP_HUNK; } } else if (ch == 'q') { + if (skip_what_now) + skip_what_now = 0; quit = 1; break; } else if (s->answer.buf[0] == 'K') { -- 2.39.5 (Apple Git-154) ^ permalink raw reply related [flat|nested] 54+ messages in thread
* Re: [RFC PATCH 1/1] add-patch: Allow reworking with a file after deciding on all its hunks 2026-01-23 11:58 ` [RFC PATCH 1/1] add-patch: Allow reworking with a file after deciding on all " Abraham Samuel Adekunle @ 2026-01-23 16:38 ` Junio C Hamano 2026-01-23 21:43 ` Samuel Abraham 0 siblings, 1 reply; 54+ messages in thread From: Junio C Hamano @ 2026-01-23 16:38 UTC (permalink / raw) To: Abraham Samuel Adekunle Cc: git, Patrick Steinhardt, Phillip Wood, SZEDER Gábor, Christian Couder, Kristoffer Haugsbakk, Ben Knoble Abraham Samuel Adekunle <abrahamadekunle50@gmail.com> writes: > 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. > > Allow for reworking with a file by introducing a what_now prompt which > allows for navigating with J/K or advancing to the next file if there is one. Describe "how" you are allowing these new things that users used not to be able to do (no, not in the "by adding this variable and switching on its value" sense, but in the "now deciding on all the hunks in a file does not automatically advance to the next file, and the user has to do X to move forward" sense). > - int colored = !!s->colored.len, quit = 0, use_pager = 0; > + int colored = !!s->colored.len, quit = 0, use_pager = 0, skip_what_now = 0; This is getting overly long. Wouldn't it be easier to follow if a preliminary patch split these existing variables into three independent definitions, and the main patch adds the fourth one? > + if (s->file_diff_nr > 1) > + prompt_whatnow = _("What now? [J,K,q,>]? "); > + else > + prompt_whatnow = _("What now? [J,K,q]? "); I wonder if ">" has to be made so special. Wouldn't it be easier to reason about the logic if ">" (and probably "<" to go back by one file) are added to the prompt in the same logic that decides 'g', 'k', 's', etc. should be shown using the "permitted" variable? And when the inter-file navigation is in the permitted set (i.e., there are multiple files involved), you'd show ">" (or "<", or both if you are dealing with the second file among three files) and ask, instead of silently moving to the next one, or something like that. Organizing the logic that way will also allow you to move to the next file _without_ first having to decide on all hunks in the current file. Just say ">" to deal with the next file first, and after you are done, either come back with "<", or the system notices that there are undecided hunks in the earlier file and takes you back automatically. I also have a hunch that with such a code structure you may not even need skip_what_now flag, but I haven't even written the code in my head, so if somebody tries to do so, they may discover the reason why such a flag is still needed. > strbuf_reset(&s->buf); > if (file_diff->hunk_nr) { > - if (rendered_hunk_index != hunk_index) { > + if (rendered_hunk_index != hunk_index || skip_what_now == 1) { Style (which may become irrelevant, as I just said the variable may not be needed after all, but anyway). Elsewhere skip_what_now is used only for "is it zero, or is it not zero?". Comparing explicitly with 1 only here makes readers suspect if assigning 2 or 70 to the variable has special meanings and wastes their brain cycles. ^ permalink raw reply [flat|nested] 54+ messages in thread
* Re: [RFC PATCH 1/1] add-patch: Allow reworking with a file after deciding on all its hunks 2026-01-23 16:38 ` Junio C Hamano @ 2026-01-23 21:43 ` Samuel Abraham 0 siblings, 0 replies; 54+ messages in thread From: Samuel Abraham @ 2026-01-23 21:43 UTC (permalink / raw) To: Junio C Hamano Cc: git, Patrick Steinhardt, Phillip Wood, SZEDER Gábor, Christian Couder, Kristoffer Haugsbakk, Ben Knoble On Fri, Jan 23, 2026 at 5:38 PM Junio C Hamano <gitster@pobox.com> wrote: > > Abraham Samuel Adekunle <abrahamadekunle50@gmail.com> writes: > > > 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. > > > > Allow for reworking with a file by introducing a what_now prompt which > > allows for navigating with J/K or advancing to the next file if there is one. > > Describe "how" you are allowing these new things that users used not > to be able to do (no, not in the "by adding this variable and > switching on its value" sense, but in the "now deciding on all the > hunks in a file does not automatically advance to the next file, and > the user has to do X to move forward" sense). Thank you for your feedback Junio. Okay, this is noted. > > > - int colored = !!s->colored.len, quit = 0, use_pager = 0; > > + int colored = !!s->colored.len, quit = 0, use_pager = 0, skip_what_now = 0; > > This is getting overly long. Wouldn't it be easier to follow if a > preliminary patch split these existing variables into three > independent definitions, and the main patch adds the fourth one? Yes it will be easier to follow. I will do that. > > > + if (s->file_diff_nr > 1) > > + prompt_whatnow = _("What now? [J,K,q,>]? "); > > + else > > + prompt_whatnow = _("What now? [J,K,q]? "); > > I wonder if ">" has to be made so special. Wouldn't it be easier to > reason about the logic if ">" (and probably "<" to go back by one > file) are added to the prompt in the same logic that decides 'g', > 'k', 's', etc. should be shown using the "permitted" variable? Yes this makes a lot of sense. Thank you for the guidance. > > And when the inter-file navigation is in the permitted set (i.e., > there are multiple files involved), you'd show ">" (or "<", or both > if you are dealing with the second file among three files) and ask, > instead of silently moving to the next one, or something like that. Yes I understand. > > Organizing the logic that way will also allow you to move to the > next file _without_ first having to decide on all hunks in the > current file. Just say ">" to deal with the next file first, and > after you are done, either come back with "<", or the system notices > that there are undecided hunks in the earlier file and takes you > back automatically. This is very insightful. I will work with this design in mind Thank you > > I also have a hunch that with such a code structure you may not even > need skip_what_now flag, but I haven't even written the code in my > head, so if somebody tries to do so, they may discover the reason > why such a flag is still needed. Yes > > > strbuf_reset(&s->buf); > > if (file_diff->hunk_nr) { > > - if (rendered_hunk_index != hunk_index) { > > + if (rendered_hunk_index != hunk_index || skip_what_now == 1) { > > Style (which may become irrelevant, as I just said the variable may > not be needed after all, but anyway). Elsewhere skip_what_now is > used only for "is it zero, or is it not zero?". Comparing > explicitly with 1 only here makes readers suspect if assigning 2 or > 70 to the variable has special meanings and wastes their brain > cycles. I will give more thoughtful efforts into the next versions I will send after your recommendations. Thank you very much Abraham. ^ permalink raw reply [flat|nested] 54+ messages in thread
* [PATCH v2 0/1] Allow reworking with a file when making hunk decisions 2026-01-23 11:56 [RFC PATCH 0/1] add-patch: Allow reworking with a file after deciding on its hunks Abraham Samuel Adekunle 2026-01-23 11:58 ` [RFC PATCH 1/1] add-patch: Allow reworking with a file after deciding on all " Abraham Samuel Adekunle @ 2026-01-27 15:43 ` Abraham Samuel Adekunle 2026-01-27 15:45 ` [PATCH v2 1/1] Allow reworking with a file after deciding on all its hunks Abraham Samuel Adekunle ` (2 more replies) 1 sibling, 3 replies; 54+ messages in thread From: Abraham Samuel Adekunle @ 2026-01-27 15:43 UTC (permalink / raw) To: git Cc: Patrick Steinhardt, Phillip Wood, SZEDER Gábor, Christian Couder, Kristoffer Haugsbakk, Ben Knoble, Junio C Hamano Hello, After review and suggestions from Junio, I have been able to add the '<' and '>' options for going to the previous file and next file respectively. If there is only one file, neither of the options will be available, if we are in the second of three or more file, both '<' and '>' will be available and if we are at the last file, only '<' will be available. This will enable simultaneous hunk decisions between between files. After all decisions have been made in a file, a prompt shows which asks "All hunks decided. What now?" that allows reworking with the file, moving to the next or previous file as the case may be. Since all hunks in the file have been decided, if the user navigates to a particular hunk with 'K' or 'J' and redecides on an already decided hunk with options such as, 'y' or 'n', the user is taken back to the first hunk with the "what now?" prompt shown. The decision to use 'q' as a submit is because after some or all the decisions have been made in a file, 'q' submits them as is even though in the `help_patch_text` it say `q` will not stage the current hunk and all hunks after it. This is not true if hunks decisions have been made and the user navigates with 'K' and 'J' or uses 'a' to select all hunks and 'q' after wards I have not attempted to work on the t/3701-interactive.sh yet but this will be done after concensus on the UI when all hunks have been decided. Abraham Samuel Adekunle (1): Allow reworking with a file after deciding on all its hunks add-patch.c | 139 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 102 insertions(+), 37 deletions(-) -- 2.39.5 (Apple Git-154) ^ permalink raw reply [flat|nested] 54+ messages in thread
* [PATCH v2 1/1] Allow reworking with a file after deciding on all its hunks 2026-01-27 15:43 ` [PATCH v2 0/1] Allow reworking with a file when making hunk decisions Abraham Samuel Adekunle @ 2026-01-27 15:45 ` Abraham Samuel Adekunle 2026-01-27 20:48 ` Junio C Hamano 2026-01-27 17:04 ` [PATCH v2 0/1] Allow reworking with a file when making hunk decisions Junio C Hamano 2026-02-06 15:52 ` [PATCH v3 0/3] introduce new option `rework-with-file` Abraham Samuel Adekunle 2 siblings, 1 reply; 54+ messages in thread From: Abraham Samuel Adekunle @ 2026-01-27 15:45 UTC (permalink / raw) To: git Cc: Patrick Steinhardt, Phillip Wood, SZEDER Gábor, Christian Couder, Kristoffer Haugsbakk, Ben Knoble, Junio C Hamano 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 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, a prompt appears, which allow the user to 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. Signed-off-by: Abraham Samuel Adekunle <abrahamadekunle50@gmail.com> --- Changes in v2: ============= - Added '<' and '>' to the permit set - All patches are now applied after all decisions in all files have been made by submitting with 'q'. add-patch.c | 139 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 102 insertions(+), 37 deletions(-) diff --git a/add-patch.c b/add-patch.c index 173a53241e..edb2fab3fd 100644 --- a/add-patch.c +++ b/add-patch.c @@ -1418,6 +1418,8 @@ 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" + "> - go to the next file\n" + "< - go to the previous file\n" "? - print help\n"); static size_t dec_mod(size_t a, size_t m) @@ -1441,6 +1443,17 @@ static bool get_first_undecided(const struct file_diff *file_diff, size_t *idx) return false; } +static size_t get_file_diff_index(struct add_p_state *s, struct file_diff *file_diff) { + size_t idx = 0; + for (size_t i = 0; i < s->file_diff_nr; i++) { + if (s->file_diff + i == file_diff) { + idx = i; + break; + } + } + return idx; +} + static int patch_update_file(struct add_p_state *s, struct file_diff *file_diff) { @@ -1448,9 +1461,10 @@ static int patch_update_file(struct add_p_state *s, 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; enum prompt_mode_type prompt_mode_type; + size_t file_diff_index = get_file_diff_index(s, file_diff); + int all_decided = 0; /* Empty added files have no hunks */ if (!file_diff->hunk_nr && !file_diff->added) @@ -1467,7 +1481,9 @@ static int patch_update_file(struct add_p_state *s, 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) @@ -1499,8 +1515,7 @@ static int patch_update_file(struct add_p_state *s, /* Everything decided? */ if (undecided_previous < 0 && undecided_next < 0 && hunk->use != UNDECIDED_HUNK) - break; - + all_decided = 1; strbuf_reset(&s->buf); if (file_diff->hunk_nr) { if (rendered_hunk_index != hunk_index) { @@ -1548,6 +1563,16 @@ static int patch_update_file(struct add_p_state *s, permitted |= ALLOW_EDIT; strbuf_addstr(&s->buf, ",e"); } + if (file_diff_index >= 0 && + file_diff_index < s->file_diff_nr - 1) { + permitted |= ALLOW_GOTO_NEXT_FILE; + strbuf_addstr(&s->buf, ",>"); + } + if (file_diff_index > 0 && + file_diff_index <= s->file_diff_nr - 1) { + permitted |= ALLOW_GOTO_PREVIOUS_FILE; + strbuf_addstr(&s->buf, ",<"); + } strbuf_addstr(&s->buf, ",p,P"); } if (file_diff->deleted) @@ -1566,6 +1591,9 @@ static int patch_update_file(struct add_p_state *s, : 1)); printf(_(s->mode->prompt_mode[prompt_mode_type]), s->buf.buf); + if (all_decided) + printf(_("\n%s All hunks decided. What now? "), + s->s.prompt_color); if (*s->s.reset_color_interactive) fputs(s->s.reset_color_interactive, stdout); fflush(stdout); @@ -1618,7 +1646,24 @@ static int patch_update_file(struct add_p_state *s, } else if (ch == 'q') { quit = 1; break; - } else if (s->answer.buf[0] == 'K') { + } else if (s->answer.buf[0] == '>') { + if (permitted & ALLOW_GOTO_NEXT_FILE) { + quit = 0; + break; + } else { + err(s, _("No next file")); + continue; + } + } else if (s->answer.buf[0] == '<') { + if (permitted & ALLOW_GOTO_PREVIOUS_FILE) { + quit = 2; + 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, file_diff->hunk_nr); @@ -1775,33 +1820,6 @@ static int patch_update_file(struct add_p_state *s, } } - /* 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); - } - putchar('\n'); return quit; } @@ -1813,7 +1831,9 @@ int run_add_p(struct repository *r, enum add_p_mode mode, struct add_p_state s = { { r }, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT }; - size_t i, binary_count = 0; + size_t i, j, binary_count = 0; + size_t patch_update_result = 0; + struct child_process cp = CHILD_PROCESS_INIT; init_add_i_state(&s.s, r, o); @@ -1852,11 +1872,56 @@ 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)) - break; + i++; + continue; + } + else { + patch_update_result = patch_update_file(&s, s.file_diff + i); + if (patch_update_result == 0) { + i++; + continue; + } + if (patch_update_result == 1) + break; + if (patch_update_result == 2) { + i--; + continue; + } + } + } + for (i = 0; i < s.file_diff_nr; i++) { + + /* Any hunk to be used? */ + for (j = 0; j < s.file_diff[i].hunk_nr; j++) + if (s.file_diff[i].hunk[j].use == USE_HUNK) + break; + + if (j < s.file_diff[i].hunk_nr || + (!s.file_diff[i].hunk_nr && s.file_diff[i].head.use == USE_HUNK)) { + /* At least one hunk selected: apply */ + strbuf_reset(&s.buf); + reassemble_patch(&s, s.file_diff + i, 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); + } + + } if (s.file_diff_nr == 0) err(&s, _("No changes.")); -- 2.39.5 (Apple Git-154) ^ permalink raw reply related [flat|nested] 54+ messages in thread
* Re: [PATCH v2 1/1] Allow reworking with a file after deciding on all its hunks 2026-01-27 15:45 ` [PATCH v2 1/1] Allow reworking with a file after deciding on all its hunks Abraham Samuel Adekunle @ 2026-01-27 20:48 ` Junio C Hamano 2026-01-28 11:26 ` Samuel Abraham 0 siblings, 1 reply; 54+ messages in thread From: Junio C Hamano @ 2026-01-27 20:48 UTC (permalink / raw) To: Abraham Samuel Adekunle Cc: git, Patrick Steinhardt, Phillip Wood, SZEDER Gábor, Christian Couder, Kristoffer Haugsbakk, Ben Knoble Abraham Samuel Adekunle <abrahamadekunle50@gmail.com> writes: > diff --git a/add-patch.c b/add-patch.c > index 173a53241e..edb2fab3fd 100644 > --- a/add-patch.c > +++ b/add-patch.c > @@ -1418,6 +1418,8 @@ 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" > + "> - go to the next file\n" > + "< - go to the previous file\n" > "? - print help\n"); As I said earlier, these may have to be optional. It may give existing users a jarring experience to be given a prompt after deciding on all the hunks in a file, when they expect to be on the next file already. > @@ -1441,6 +1443,17 @@ static bool get_first_undecided(const struct file_diff *file_diff, size_t *idx) > return false; > } > > +static size_t get_file_diff_index(struct add_p_state *s, struct file_diff *file_diff) { > + size_t idx = 0; > + for (size_t i = 0; i < s->file_diff_nr; i++) { > + if (s->file_diff + i == file_diff) { > + idx = i; > + break; > + } > + } > + return idx; > +} Yuck. Can't we lose the need for this function if we change the interface into patch_update_file so that it takes the index of the file (i.e., instead of "&s.file_diff[i]", pass "i")? There is only one caller to patch_update_file() which is run_add_p(), so such a clean-up should be trivial. > static int patch_update_file(struct add_p_state *s, > struct file_diff *file_diff) > { > @@ -1448,9 +1461,10 @@ static int patch_update_file(struct add_p_state *s, > ssize_t i, undecided_previous, undecided_next, rendered_hunk_index = -1; > struct hunk *hunk; > char ch; > - struct child_process cp = CHILD_PROCESS_INIT; This is related to the hoisting of the actual patch application to the caller, but it is not explained why such a change is needed, and it byitself, even without the "jump to the next file before deciding on all the hunks" feature. What problem is it solving??? If it is necessary to move the code to run "git apply" to the caller, would it make sense to split this patch into at least two patches, one to do such a move, possibly another patch to change the function signature of patch_update_file() so that it takes the file index instead of file_diff struct, and finally another patch to allow jumping around the files? > int colored = !!s->colored.len, quit = 0, use_pager = 0; > enum prompt_mode_type prompt_mode_type; > + size_t file_diff_index = get_file_diff_index(s, file_diff); > + int all_decided = 0; > > /* Empty added files have no hunks */ > if (!file_diff->hunk_nr && !file_diff->added) > @@ -1467,7 +1481,9 @@ static int patch_update_file(struct add_p_state *s, > 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) > @@ -1499,8 +1515,7 @@ static int patch_update_file(struct add_p_state *s, > /* Everything decided? */ > if (undecided_previous < 0 && undecided_next < 0 && > hunk->use != UNDECIDED_HUNK) > - break; > - > + all_decided = 1; > strbuf_reset(&s->buf); > if (file_diff->hunk_nr) { > if (rendered_hunk_index != hunk_index) { > @@ -1548,6 +1563,16 @@ static int patch_update_file(struct add_p_state *s, > permitted |= ALLOW_EDIT; > strbuf_addstr(&s->buf, ",e"); > } > + if (file_diff_index >= 0 && > + file_diff_index < s->file_diff_nr - 1) { > + permitted |= ALLOW_GOTO_NEXT_FILE; > + strbuf_addstr(&s->buf, ",>"); > + } > + if (file_diff_index > 0 && > + file_diff_index <= s->file_diff_nr - 1) { > + permitted |= ALLOW_GOTO_PREVIOUS_FILE; > + strbuf_addstr(&s->buf, ",<"); > + } As can be seen in what patch_update_file() does when the user says 'J' or 'K', hunks in a file are treated as a ring, and these commands are enabled as long as there are more than one hunks. Perhaps that is more familiar than "when we hit the floor, we cannot sink deeper, and when we hit the ceiling, we cannot float more", which seems to be what the above implements. > strbuf_addstr(&s->buf, ",p,P"); > } > if (file_diff->deleted) > @@ -1566,6 +1591,9 @@ static int patch_update_file(struct add_p_state *s, > : 1)); > printf(_(s->mode->prompt_mode[prompt_mode_type]), > s->buf.buf); > + if (all_decided) > + printf(_("\n%s All hunks decided. What now? "), > + s->s.prompt_color); > if (*s->s.reset_color_interactive) > fputs(s->s.reset_color_interactive, stdout); > fflush(stdout); > @@ -1618,7 +1646,24 @@ static int patch_update_file(struct add_p_state *s, > } else if (ch == 'q') { > quit = 1; > break; > - } else if (s->answer.buf[0] == 'K') { > + } else if (s->answer.buf[0] == '>') { > + if (permitted & ALLOW_GOTO_NEXT_FILE) { > + quit = 0; > + break; > + } else { > + err(s, _("No next file")); > + continue; > + } > + } else if (s->answer.buf[0] == '<') { > + if (permitted & ALLOW_GOTO_PREVIOUS_FILE) { > + quit = 2; > + break; What's the magic number "2"? Should "quit" become an enum with elements that are more meaningfully named? > + } 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, > file_diff->hunk_nr); > @@ -1775,33 +1820,6 @@ static int patch_update_file(struct add_p_state *s, > } > } > > - /* 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); > - } It is not obvious why the above code needs to be hoisted to the caller. > putchar('\n'); > return quit; > } > @@ -1813,7 +1831,9 @@ int run_add_p(struct repository *r, enum add_p_mode mode, > struct add_p_state s = { > { r }, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT > }; > - size_t i, binary_count = 0; > + size_t i, j, binary_count = 0; > + size_t patch_update_result = 0; Hmph, I think patch_update_file() returns "int quit". Why do we want overly wide type to store the result, which cannot even express negative number to potentially signal a failure? > + struct child_process cp = CHILD_PROCESS_INIT; > > init_add_i_state(&s.s, r, o); > > @@ -1852,11 +1872,56 @@ 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)) > - break; > + i++; > + continue; > + } > + else { > + patch_update_result = patch_update_file(&s, s.file_diff + i); > + if (patch_update_result == 0) { > + i++; > + continue; > + } > + if (patch_update_result == 1) > + break; > + if (patch_update_result == 2) { > + i--; > + continue; > + } > + } > + } > + for (i = 0; i < s.file_diff_nr; i++) { > + > + /* Any hunk to be used? */ > + for (j = 0; j < s.file_diff[i].hunk_nr; j++) > + if (s.file_diff[i].hunk[j].use == USE_HUNK) > + break; > + > + if (j < s.file_diff[i].hunk_nr || > + (!s.file_diff[i].hunk_nr && s.file_diff[i].head.use == USE_HUNK)) { > + /* At least one hunk selected: apply */ > + strbuf_reset(&s.buf); > + reassemble_patch(&s, s.file_diff + i, 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); > + } > + > + } One upside of having "git apply" at the end of patch_update_file() is that you can "^C" out of "git add -p" or your terminal connection can be cut off, after dealing with hunks in a few early files, and these early part of your work that you have already done are already reflected to the working tree files. By hoisting the logic to the caller, this is making the update all-or-none, which is good in transactional systems, but can make a horrible experience for an interactive use where you make progress while thinking. So I am not yet convinced if this change makes sense---it could be because of the lack of justification for this change. > > if (s.file_diff_nr == 0) > err(&s, _("No changes.")); ^ permalink raw reply [flat|nested] 54+ messages in thread
* Re: [PATCH v2 1/1] Allow reworking with a file after deciding on all its hunks 2026-01-27 20:48 ` Junio C Hamano @ 2026-01-28 11:26 ` Samuel Abraham 2026-01-30 9:22 ` Samuel Abraham 0 siblings, 1 reply; 54+ messages in thread From: Samuel Abraham @ 2026-01-28 11:26 UTC (permalink / raw) To: Junio C Hamano Cc: git, Patrick Steinhardt, Phillip Wood, SZEDER Gábor, Christian Couder, Kristoffer Haugsbakk, Ben Knoble On Tue, Jan 27, 2026 at 9:48 PM Junio C Hamano <gitster@pobox.com> wrote: > > Abraham Samuel Adekunle <abrahamadekunle50@gmail.com> writes: > > > diff --git a/add-patch.c b/add-patch.c > > index 173a53241e..edb2fab3fd 100644 > > --- a/add-patch.c > > +++ b/add-patch.c > > @@ -1418,6 +1418,8 @@ 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" > > + "> - go to the next file\n" > > + "< - go to the previous file\n" > > "? - print help\n"); > > As I said earlier, these may have to be optional. It may give > existing users a jarring experience to be given a prompt after > deciding on all the hunks in a file, when they expect to be on > the next file already. Yes I agree. I will work on making it an optional feature. > > > @@ -1441,6 +1443,17 @@ static bool get_first_undecided(const struct file_diff *file_diff, size_t *idx) > > return false; > > } > > > > +static size_t get_file_diff_index(struct add_p_state *s, struct file_diff *file_diff) { > > + size_t idx = 0; > > + for (size_t i = 0; i < s->file_diff_nr; i++) { > > + if (s->file_diff + i == file_diff) { > > + idx = i; > > + break; > > + } > > + } > > + return idx; > > +} > > Yuck. Can't we lose the need for this function if we change the > interface into patch_update_file so that it takes the index of the > file (i.e., instead of "&s.file_diff[i]", pass "i")? There is only > one caller to patch_update_file() which is run_add_p(), so such a > clean-up should be trivial. Ah yes this is definitely a sweet and better option. > > > static int patch_update_file(struct add_p_state *s, > > struct file_diff *file_diff) > > { > > @@ -1448,9 +1461,10 @@ static int patch_update_file(struct add_p_state *s, > > ssize_t i, undecided_previous, undecided_next, rendered_hunk_index = -1; > > struct hunk *hunk; > > char ch; > > - struct child_process cp = CHILD_PROCESS_INIT; > > This is related to the hoisting of the actual patch application to > the caller, but it is not explained why such a change is needed, and > it byitself, even without the "jump to the next file before deciding > on all the hunks" feature. What problem is it solving??? I explained this below > > If it is necessary to move the code to run "git apply" to the > caller, would it make sense to split this patch into at least two > patches, one to do such a move, possibly another patch to change the > function signature of patch_update_file() so that it takes the file > index instead of file_diff struct, and finally another patch to > allow jumping around the files? Okay yes it would make much sense. > > > int colored = !!s->colored.len, quit = 0, use_pager = 0; > > enum prompt_mode_type prompt_mode_type; > > + size_t file_diff_index = get_file_diff_index(s, file_diff); > > + int all_decided = 0; > > > > /* Empty added files have no hunks */ > > if (!file_diff->hunk_nr && !file_diff->added) > > @@ -1467,7 +1481,9 @@ static int patch_update_file(struct add_p_state *s, > > 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) > > @@ -1499,8 +1515,7 @@ static int patch_update_file(struct add_p_state *s, > > /* Everything decided? */ > > if (undecided_previous < 0 && undecided_next < 0 && > > hunk->use != UNDECIDED_HUNK) > > - break; > > - > > + all_decided = 1; > > strbuf_reset(&s->buf); > > if (file_diff->hunk_nr) { > > if (rendered_hunk_index != hunk_index) { > > @@ -1548,6 +1563,16 @@ static int patch_update_file(struct add_p_state *s, > > permitted |= ALLOW_EDIT; > > strbuf_addstr(&s->buf, ",e"); > > } > > + if (file_diff_index >= 0 && > > + file_diff_index < s->file_diff_nr - 1) { > > + permitted |= ALLOW_GOTO_NEXT_FILE; > > + strbuf_addstr(&s->buf, ",>"); > > + } > > + if (file_diff_index > 0 && > > + file_diff_index <= s->file_diff_nr - 1) { > > + permitted |= ALLOW_GOTO_PREVIOUS_FILE; > > + strbuf_addstr(&s->buf, ",<"); > > + } > > As can be seen in what patch_update_file() does when the user says > 'J' or 'K', hunks in a file are treated as a ring, and these > commands are enabled as long as there are more than one hunks. > > Perhaps that is more familiar than "when we hit the floor, we cannot > sink deeper, and when we hit the ceiling, we cannot float more", > which seems to be what the above implements. Yes I understand this now. It does make sense this way. > > > strbuf_addstr(&s->buf, ",p,P"); > > } > > if (file_diff->deleted) > > @@ -1566,6 +1591,9 @@ static int patch_update_file(struct add_p_state *s, > > : 1)); > > printf(_(s->mode->prompt_mode[prompt_mode_type]), > > s->buf.buf); > > + if (all_decided) > > + printf(_("\n%s All hunks decided. What now? "), > > + s->s.prompt_color); > > if (*s->s.reset_color_interactive) > > fputs(s->s.reset_color_interactive, stdout); > > fflush(stdout); > > @@ -1618,7 +1646,24 @@ static int patch_update_file(struct add_p_state *s, > > } else if (ch == 'q') { > > quit = 1; > > break; > > - } else if (s->answer.buf[0] == 'K') { > > + } else if (s->answer.buf[0] == '>') { > > + if (permitted & ALLOW_GOTO_NEXT_FILE) { > > + quit = 0; > > + break; > > + } else { > > + err(s, _("No next file")); > > + continue; > > + } > > + } else if (s->answer.buf[0] == '<') { > > + if (permitted & ALLOW_GOTO_PREVIOUS_FILE) { > > + quit = 2; > > + break; > > What's the magic number "2"? Should "quit" become an enum with > elements that are more meaningfully named? Okay, yes an enum would be better. > > > + } 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, > > file_diff->hunk_nr); > > @@ -1775,33 +1820,6 @@ static int patch_update_file(struct add_p_state *s, > > } > > } > > > > - /* 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); > > - } > > It is not obvious why the above code needs to be hoisted to the > caller. I explained this below. > > > putchar('\n'); > > return quit; > > } > > @@ -1813,7 +1831,9 @@ int run_add_p(struct repository *r, enum add_p_mode mode, > > struct add_p_state s = { > > { r }, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT > > }; > > - size_t i, binary_count = 0; > > + size_t i, j, binary_count = 0; > > + size_t patch_update_result = 0; > > Hmph, I think patch_update_file() returns "int quit". Why do we > want overly wide type to store the result, which cannot even express > negative number to potentially signal a failure? Sorry, this is a mistake on my part > > > + struct child_process cp = CHILD_PROCESS_INIT; > > > > init_add_i_state(&s.s, r, o); > > > > @@ -1852,11 +1872,56 @@ 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)) > > - break; > > + i++; > > + continue; > > + } > > + else { > > + patch_update_result = patch_update_file(&s, s.file_diff + i); > > + if (patch_update_result == 0) { > > + i++; > > + continue; > > + } > > + if (patch_update_result == 1) > > + break; > > + if (patch_update_result == 2) { > > + i--; > > + continue; > > + } > > + } > > + } > > + for (i = 0; i < s.file_diff_nr; i++) { > > + > > + /* Any hunk to be used? */ > > + for (j = 0; j < s.file_diff[i].hunk_nr; j++) > > + if (s.file_diff[i].hunk[j].use == USE_HUNK) > > + break; > > + > > + if (j < s.file_diff[i].hunk_nr || > > + (!s.file_diff[i].hunk_nr && s.file_diff[i].head.use == USE_HUNK)) { > > + /* At least one hunk selected: apply */ > > + strbuf_reset(&s.buf); > > + reassemble_patch(&s, s.file_diff + i, 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); > > + } > > + > > + } > > One upside of having "git apply" at the end of patch_update_file() > is that you can "^C" out of "git add -p" or your terminal connection > can be cut off, after dealing with hunks in a few early files, and > these early part of your work that you have already done are already > reflected to the working tree files. By hoisting the logic to the > caller, this is making the update all-or-none, which is good in > transactional systems, but can make a horrible experience for an > interactive use where you make progress while thinking. > > So I am not yet convinced if this change makes sense---it could be > because of the lack of justification for this change. What I observed after adding the '>' and '<' options is that if a user chooses to use a hunk A in file 1, and then goes to file 2 with '>', comes back to file 1 with '<', and decides on hunk A to skip it instead, because patch_update_file() has applied the file with the hunk the user initially decided to use before proceeding to file 2 with '>', coming back to redecide and say skip does not apply the latest decision and when you check the index, the file with the hunks which the user initially decided to use but changed to skip is present in the index. But if the user initially decided to skip a hunk in a file, goes to the next file with '>' and back to the first file, changes the decision on the hunk to use, it applies the patch with the hunk because the hunk was not initially selected when the patch was applied. But if he now goes away and comes back to the file a third time and chooses to skip the hunk, then quits with 'q', because he had selected to use the hunk the second time, choosing skip again will not work. So basically, initially choosing to use a hunk in a file, going to another file and coming back to this file then choosing to skip it does not register the latest skip decision on that hunk. That was why I decided to do it this way. I will appreciate a better suggestion from you Thanks Abraham. ^ permalink raw reply [flat|nested] 54+ messages in thread
* Re: [PATCH v2 1/1] Allow reworking with a file after deciding on all its hunks 2026-01-28 11:26 ` Samuel Abraham @ 2026-01-30 9:22 ` Samuel Abraham 2026-01-30 16:29 ` Junio C Hamano 2026-01-31 19:25 ` Junio C Hamano 0 siblings, 2 replies; 54+ messages in thread From: Samuel Abraham @ 2026-01-30 9:22 UTC (permalink / raw) To: Junio C Hamano Cc: git, Patrick Steinhardt, Phillip Wood, SZEDER Gábor, Christian Couder, Kristoffer Haugsbakk, Ben Knoble On Wed, Jan 28, 2026 at 12:26 PM Samuel Abraham <abrahamadekunle50@gmail.com> wrote: > > On Tue, Jan 27, 2026 at 9:48 PM Junio C Hamano <gitster@pobox.com> wrote: > > > > Abraham Samuel Adekunle <abrahamadekunle50@gmail.com> writes: > > > > > diff --git a/add-patch.c b/add-patch.c > > > index 173a53241e..edb2fab3fd 100644 > > > --- a/add-patch.c > > > +++ b/add-patch.c > > > @@ -1418,6 +1418,8 @@ 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" > > > + "> - go to the next file\n" > > > + "< - go to the previous file\n" > > > "? - print help\n"); > > > > As I said earlier, these may have to be optional. It may give > > existing users a jarring experience to be given a prompt after > > deciding on all the hunks in a file, when they expect to be on > > the next file already. > > Yes I agree. > I will work on making it an optional feature. > > > > > > @@ -1441,6 +1443,17 @@ static bool get_first_undecided(const struct file_diff *file_diff, size_t *idx) > > > return false; > > > } > > > > > > +static size_t get_file_diff_index(struct add_p_state *s, struct file_diff *file_diff) { > > > + size_t idx = 0; > > > + for (size_t i = 0; i < s->file_diff_nr; i++) { > > > + if (s->file_diff + i == file_diff) { > > > + idx = i; > > > + break; > > > + } > > > + } > > > + return idx; > > > +} > > > > Yuck. Can't we lose the need for this function if we change the > > interface into patch_update_file so that it takes the index of the > > file (i.e., instead of "&s.file_diff[i]", pass "i")? There is only > > one caller to patch_update_file() which is run_add_p(), so such a > > clean-up should be trivial. > > Ah yes this is definitely a sweet and better option. > > > > > > static int patch_update_file(struct add_p_state *s, > > > struct file_diff *file_diff) > > > { > > > @@ -1448,9 +1461,10 @@ static int patch_update_file(struct add_p_state *s, > > > ssize_t i, undecided_previous, undecided_next, rendered_hunk_index = -1; > > > struct hunk *hunk; > > > char ch; > > > - struct child_process cp = CHILD_PROCESS_INIT; > > > > This is related to the hoisting of the actual patch application to > > the caller, but it is not explained why such a change is needed, and > > it byitself, even without the "jump to the next file before deciding > > on all the hunks" feature. What problem is it solving??? > > I explained this below > > > > > If it is necessary to move the code to run "git apply" to the > > caller, would it make sense to split this patch into at least two > > patches, one to do such a move, possibly another patch to change the > > function signature of patch_update_file() so that it takes the file > > index instead of file_diff struct, and finally another patch to > > allow jumping around the files? > > Okay yes it would make much sense. > > > > > > int colored = !!s->colored.len, quit = 0, use_pager = 0; > > > enum prompt_mode_type prompt_mode_type; > > > + size_t file_diff_index = get_file_diff_index(s, file_diff); > > > + int all_decided = 0; > > > > > > /* Empty added files have no hunks */ > > > if (!file_diff->hunk_nr && !file_diff->added) > > > @@ -1467,7 +1481,9 @@ static int patch_update_file(struct add_p_state *s, > > > 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) > > > @@ -1499,8 +1515,7 @@ static int patch_update_file(struct add_p_state *s, > > > /* Everything decided? */ > > > if (undecided_previous < 0 && undecided_next < 0 && > > > hunk->use != UNDECIDED_HUNK) > > > - break; > > > - > > > + all_decided = 1; > > > strbuf_reset(&s->buf); > > > if (file_diff->hunk_nr) { > > > if (rendered_hunk_index != hunk_index) { > > > @@ -1548,6 +1563,16 @@ static int patch_update_file(struct add_p_state *s, > > > permitted |= ALLOW_EDIT; > > > strbuf_addstr(&s->buf, ",e"); > > > } > > > + if (file_diff_index >= 0 && > > > + file_diff_index < s->file_diff_nr - 1) { > > > + permitted |= ALLOW_GOTO_NEXT_FILE; > > > + strbuf_addstr(&s->buf, ",>"); > > > + } > > > + if (file_diff_index > 0 && > > > + file_diff_index <= s->file_diff_nr - 1) { > > > + permitted |= ALLOW_GOTO_PREVIOUS_FILE; > > > + strbuf_addstr(&s->buf, ",<"); > > > + } > > > > As can be seen in what patch_update_file() does when the user says > > 'J' or 'K', hunks in a file are treated as a ring, and these > > commands are enabled as long as there are more than one hunks. > > > > Perhaps that is more familiar than "when we hit the floor, we cannot > > sink deeper, and when we hit the ceiling, we cannot float more", > > which seems to be what the above implements. > > Yes I understand this now. > It does make sense this way. > > > > > > strbuf_addstr(&s->buf, ",p,P"); > > > } > > > if (file_diff->deleted) > > > @@ -1566,6 +1591,9 @@ static int patch_update_file(struct add_p_state *s, > > > : 1)); > > > printf(_(s->mode->prompt_mode[prompt_mode_type]), > > > s->buf.buf); > > > + if (all_decided) > > > + printf(_("\n%s All hunks decided. What now? "), > > > + s->s.prompt_color); > > > if (*s->s.reset_color_interactive) > > > fputs(s->s.reset_color_interactive, stdout); > > > fflush(stdout); > > > @@ -1618,7 +1646,24 @@ static int patch_update_file(struct add_p_state *s, > > > } else if (ch == 'q') { > > > quit = 1; > > > break; > > > - } else if (s->answer.buf[0] == 'K') { > > > + } else if (s->answer.buf[0] == '>') { > > > + if (permitted & ALLOW_GOTO_NEXT_FILE) { > > > + quit = 0; > > > + break; > > > + } else { > > > + err(s, _("No next file")); > > > + continue; > > > + } > > > + } else if (s->answer.buf[0] == '<') { > > > + if (permitted & ALLOW_GOTO_PREVIOUS_FILE) { > > > + quit = 2; > > > + break; > > > > What's the magic number "2"? Should "quit" become an enum with > > elements that are more meaningfully named? > > Okay, yes an enum would be better. > > > > > > + } 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, > > > file_diff->hunk_nr); > > > @@ -1775,33 +1820,6 @@ static int patch_update_file(struct add_p_state *s, > > > } > > > } > > > > > > - /* 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); > > > - } > > > > It is not obvious why the above code needs to be hoisted to the > > caller. > > I explained this below. > > > > > > putchar('\n'); > > > return quit; > > > } > > > @@ -1813,7 +1831,9 @@ int run_add_p(struct repository *r, enum add_p_mode mode, > > > struct add_p_state s = { > > > { r }, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT > > > }; > > > - size_t i, binary_count = 0; > > > + size_t i, j, binary_count = 0; > > > + size_t patch_update_result = 0; > > > > Hmph, I think patch_update_file() returns "int quit". Why do we > > want overly wide type to store the result, which cannot even express > > negative number to potentially signal a failure? > > Sorry, this is a mistake on my part > > > > > > + struct child_process cp = CHILD_PROCESS_INIT; > > > > > > init_add_i_state(&s.s, r, o); > > > > > > @@ -1852,11 +1872,56 @@ 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)) > > > - break; > > > + i++; > > > + continue; > > > + } > > > + else { > > > + patch_update_result = patch_update_file(&s, s.file_diff + i); > > > + if (patch_update_result == 0) { > > > + i++; > > > + continue; > > > + } > > > + if (patch_update_result == 1) > > > + break; > > > + if (patch_update_result == 2) { > > > + i--; > > > + continue; > > > + } > > > + } > > > + } > > > + for (i = 0; i < s.file_diff_nr; i++) { > > > + > > > + /* Any hunk to be used? */ > > > + for (j = 0; j < s.file_diff[i].hunk_nr; j++) > > > + if (s.file_diff[i].hunk[j].use == USE_HUNK) > > > + break; > > > + > > > + if (j < s.file_diff[i].hunk_nr || > > > + (!s.file_diff[i].hunk_nr && s.file_diff[i].head.use == USE_HUNK)) { > > > + /* At least one hunk selected: apply */ > > > + strbuf_reset(&s.buf); > > > + reassemble_patch(&s, s.file_diff + i, 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); > > > + } > > > + > > > + } > > > > One upside of having "git apply" at the end of patch_update_file() > > is that you can "^C" out of "git add -p" or your terminal connection > > can be cut off, after dealing with hunks in a few early files, and > > these early part of your work that you have already done are already > > reflected to the working tree files. By hoisting the logic to the > > caller, this is making the update all-or-none, which is good in > > transactional systems, but can make a horrible experience for an > > interactive use where you make progress while thinking. > > > > So I am not yet convinced if this change makes sense---it could be > > because of the lack of justification for this change. > > What I observed after adding the '>' and '<' options is that if a user chooses > to use a hunk A in file 1, and then goes to file 2 with '>', comes back to > file 1 with '<', and decides on hunk A to skip it instead, because > patch_update_file() has > applied the file with the hunk the user initially decided to use > before proceeding to file > 2 with '>', coming back to redecide and say skip does not apply the > latest decision > and when you check the index, the file with the hunks which the user > initially decided to > use but changed to skip is present in the index. > > But if the user initially decided to skip a hunk in a file, goes to > the next file with '>' > and back to the first file, changes the decision on the hunk to use, > it applies the patch > with the hunk because the hunk was not initially selected when the > patch was applied. > But if he now goes away and comes back to the file a third time and > chooses to skip the > hunk, then quits with 'q', because he had selected to use the hunk the > second time, > choosing skip again will not work. > > So basically, initially choosing to use a hunk in a file, going to > another file and coming > back to this file then choosing to skip it does not register the > latest skip decision > on that hunk. > > That was why I decided to do it this way. > I will appreciate a better suggestion from you > > Thanks > Abraham. Hello Junio, thank you for your review. Here I explain my decision to move the "git apply" in patch_update_file() to the caller. Does it sound like a valid reason to make the move? Thanks Abraham ^ permalink raw reply [flat|nested] 54+ messages in thread
* Re: [PATCH v2 1/1] Allow reworking with a file after deciding on all its hunks 2026-01-30 9:22 ` Samuel Abraham @ 2026-01-30 16:29 ` Junio C Hamano 2026-01-30 17:36 ` Samuel Abraham 2026-01-31 19:25 ` Junio C Hamano 1 sibling, 1 reply; 54+ messages in thread From: Junio C Hamano @ 2026-01-30 16:29 UTC (permalink / raw) To: Samuel Abraham Cc: git, Patrick Steinhardt, Phillip Wood, SZEDER Gábor, Christian Couder, Kristoffer Haugsbakk, Ben Knoble Samuel Abraham <abrahamadekunle50@gmail.com> writes: > Hello Junio, thank you for your review. > Here I explain my decision to move the "git apply" in patch_update_file() > to the caller. > > Does it sound like a valid reason to make the move? I am not sure, but as long as this is an optional feature, users can choose not to opt in if they do not like the new "all or none" semantics, I guess. Thanks. ^ permalink raw reply [flat|nested] 54+ messages in thread
* Re: [PATCH v2 1/1] Allow reworking with a file after deciding on all its hunks 2026-01-30 16:29 ` Junio C Hamano @ 2026-01-30 17:36 ` Samuel Abraham 0 siblings, 0 replies; 54+ messages in thread From: Samuel Abraham @ 2026-01-30 17:36 UTC (permalink / raw) To: Junio C Hamano Cc: git, Patrick Steinhardt, Phillip Wood, SZEDER Gábor, Christian Couder, Kristoffer Haugsbakk, Ben Knoble On Fri, Jan 30, 2026 at 5:29 PM Junio C Hamano <gitster@pobox.com> wrote: > > Samuel Abraham <abrahamadekunle50@gmail.com> writes: > > > Hello Junio, thank you for your review. > > Here I explain my decision to move the "git apply" in patch_update_file() > > to the caller. > > > > Does it sound like a valid reason to make the move? > > I am not sure, but as long as this is an optional feature, users can > choose not to opt in if they do not like the new "all or none" > semantics, I guess. > > Thanks. Okay thank you. I will work on making it an optional feature Abraham ^ permalink raw reply [flat|nested] 54+ messages in thread
* Re: [PATCH v2 1/1] Allow reworking with a file after deciding on all its hunks 2026-01-30 9:22 ` Samuel Abraham 2026-01-30 16:29 ` Junio C Hamano @ 2026-01-31 19:25 ` Junio C Hamano 2026-02-02 11:14 ` Samuel Abraham 1 sibling, 1 reply; 54+ messages in thread From: Junio C Hamano @ 2026-01-31 19:25 UTC (permalink / raw) To: Samuel Abraham Cc: git, Patrick Steinhardt, Phillip Wood, SZEDER Gábor, Christian Couder, Kristoffer Haugsbakk, Ben Knoble Samuel Abraham <abrahamadekunle50@gmail.com> writes: >> What I observed after adding the '>' and '<' options is that if a user chooses >> to use a hunk A in file 1, and then goes to file 2 with '>', comes back to >> file 1 with '<', and decides on hunk A to skip it instead, because >> patch_update_file() has >> applied the file with the hunk the user initially decided to use >> before proceeding to file >> 2 with '>', coming back to redecide and say skip does not apply the >> latest decision >> and when you check the index, the file with the hunks which the user >> initially decided to >> use but changed to skip is present in the index. I am not sure if I would like the end result or rather prefer your "all-or-none", so please do not take this as "here is a better way to implement it" suggestion. But you should be able to keep the current semantics, if you wanted to, even if you apply the chosen hunks when you switch files, like the original code has been doing forever since it was written. You know which hunks you applied, so after applying before moving on to the next file, you can drop these hunks from the list of hunks to be decided for application. When the user comes back to the current file to decide on other hunks, you know that the already used hunks would get in the way, so why keep them? Having said that, I think the all-or-none mode may be handy if one makes the current working tree dirty with many little unrelated and insignificant changes and the only way to make sense is to see the "git diff --cached" output after adding some and leaving others, at least in the way some people work. I usually am very incremental when doing "git add -p", in that while using the command in one terminal, I run "git diff --cached" to see if I added unwanted things by mistake and "git diff" to see if I left out necessary things, so I would probably not be using the mode. But that is just my hunch without using the new interface long enough. Thanks. ^ permalink raw reply [flat|nested] 54+ messages in thread
* Re: [PATCH v2 1/1] Allow reworking with a file after deciding on all its hunks 2026-01-31 19:25 ` Junio C Hamano @ 2026-02-02 11:14 ` Samuel Abraham 2026-02-02 17:26 ` Junio C Hamano 0 siblings, 1 reply; 54+ messages in thread From: Samuel Abraham @ 2026-02-02 11:14 UTC (permalink / raw) To: Junio C Hamano Cc: git, Patrick Steinhardt, Phillip Wood, SZEDER Gábor, Christian Couder, Kristoffer Haugsbakk, Ben Knoble On Sat, Jan 31, 2026 at 8:25 PM Junio C Hamano <gitster@pobox.com> wrote: > > Samuel Abraham <abrahamadekunle50@gmail.com> writes: > > >> What I observed after adding the '>' and '<' options is that if a user chooses > >> to use a hunk A in file 1, and then goes to file 2 with '>', comes back to > >> file 1 with '<', and decides on hunk A to skip it instead, because > >> patch_update_file() has > >> applied the file with the hunk the user initially decided to use > >> before proceeding to file > >> 2 with '>', coming back to redecide and say skip does not apply the > >> latest decision > >> and when you check the index, the file with the hunks which the user > >> initially decided to > >> use but changed to skip is present in the index. > > I am not sure if I would like the end result or rather prefer your > "all-or-none", so please do not take this as "here is a better way > to implement it" suggestion. > > But you should be able to keep the current semantics, if you wanted > to, even if you apply the chosen hunks when you switch files, like > the original code has been doing forever since it was written. You > know which hunks you applied, so after applying before moving on to > the next file, you can drop these hunks from the list of hunks to be > decided for application. When the user comes back to the current > file to decide on other hunks, you know that the already used hunks > would get in the way, so why keep them? Yes thank you so much for suggesting this approach. > > Having said that, I think the all-or-none mode may be handy if one > makes the current working tree dirty with many little unrelated and > insignificant changes and the only way to make sense is to see the > "git diff --cached" output after adding some and leaving others, at > least in the way some people work. I usually am very incremental > when doing "git add -p", in that while using the command in one > terminal, I run "git diff --cached" to see if I added unwanted > things by mistake and "git diff" to see if I left out necessary > things, so I would probably not be using the mode. But that is just > my hunch without using the new interface long enough. Okay I think retaining "git apply" in patch_update_file() and dropping the hunks the user has already decided on when coming back to the file makes sense. By using this approach, we skip files that have been fully decided and applied, only showing files that; i. have been applied but also have undecided hunks. ii. not been applied and still have undecided hunks when the user navigates with ">" and "<". This will allow you to still run git diff--cached to see what has been added while also being able to see what has not been added, while navigating around files. Thank you. Abraham ^ permalink raw reply [flat|nested] 54+ messages in thread
* Re: [PATCH v2 1/1] Allow reworking with a file after deciding on all its hunks 2026-02-02 11:14 ` Samuel Abraham @ 2026-02-02 17:26 ` Junio C Hamano 2026-02-03 9:55 ` Samuel Abraham 0 siblings, 1 reply; 54+ messages in thread From: Junio C Hamano @ 2026-02-02 17:26 UTC (permalink / raw) To: Samuel Abraham Cc: git, Patrick Steinhardt, Phillip Wood, SZEDER Gábor, Christian Couder, Kristoffer Haugsbakk, Ben Knoble Samuel Abraham <abrahamadekunle50@gmail.com> writes: >> I am not sure if I would like the end result or rather prefer your >> "all-or-none", so please do not take this as "here is a better way >> to implement it" suggestion. >> >> But you should be able to keep the current semantics, if you wanted >> to, even if you apply the chosen hunks when you switch files, like >> the original code has been doing forever since it was written. You >> know which hunks you applied, so after applying before moving on to >> the next file, you can drop these hunks from the list of hunks to be >> decided for application. When the user comes back to the current >> file to decide on other hunks, you know that the already used hunks >> would get in the way, so why keep them? > > Yes thank you so much for suggesting this approach. Not so fast. I explicitly said I am *NOT* suggesting anything. And thinking about it more, I do not think it makes any sense to do anything other than "all-or-none" when the command is working in your new "you can move to different files before you decide on all hunks in the current file" mode (which I think we agreed to make it an optional mode). Why? After deciding yes, no, no among 5 hunks in the first file (leaving the hunks #4 and #5 undecided), you jump to the second file, do something there, and imagine that you come back. If we drop the alrady applied hunks like the suggestion, which I did not make ;-), we'd then give you four hunks (as hunk #1 has been already applied), and even though you have already decided not to use hunks #2 and #3, you *can* revisit them with "J" or "K", change your mind and use them if you wanted to. But it is too late for the hunk #1. It looks utterly inconsistent if you cannot change your mind on hunk #1 but can on hunks #2 and #3 and it reduces the usefulness of "you do not have to decide right now and visit other files before you do so" mode. Thanks. ^ permalink raw reply [flat|nested] 54+ messages in thread
* Re: [PATCH v2 1/1] Allow reworking with a file after deciding on all its hunks 2026-02-02 17:26 ` Junio C Hamano @ 2026-02-03 9:55 ` Samuel Abraham 0 siblings, 0 replies; 54+ messages in thread From: Samuel Abraham @ 2026-02-03 9:55 UTC (permalink / raw) To: Junio C Hamano Cc: git, Patrick Steinhardt, Phillip Wood, SZEDER Gábor, Christian Couder, Kristoffer Haugsbakk, Ben Knoble On Mon, Feb 2, 2026 at 6:26 PM Junio C Hamano <gitster@pobox.com> wrote: > > Samuel Abraham <abrahamadekunle50@gmail.com> writes: > > >> I am not sure if I would like the end result or rather prefer your > >> "all-or-none", so please do not take this as "here is a better way > >> to implement it" suggestion. > >> > >> But you should be able to keep the current semantics, if you wanted > >> to, even if you apply the chosen hunks when you switch files, like > >> the original code has been doing forever since it was written. You > >> know which hunks you applied, so after applying before moving on to > >> the next file, you can drop these hunks from the list of hunks to be > >> decided for application. When the user comes back to the current > >> file to decide on other hunks, you know that the already used hunks > >> would get in the way, so why keep them? > > > > Yes thank you so much for suggesting this approach. > > Not so fast. I explicitly said I am *NOT* suggesting anything. Yes you did. > > And thinking about it more, I do not think it makes any sense to do > anything other than "all-or-none" when the command is working in > your new "you can move to different files before you decide on all > hunks in the current file" mode (which I think we agreed to make it > an optional mode). Why? After deciding yes, no, no among 5 hunks > in the first file (leaving the hunks #4 and #5 undecided), you jump > to the second file, do something there, and imagine that you come > back. If we drop the alrady applied hunks like the suggestion, > which I did not make ;-), :D > we'd then give you four hunks (as hunk #1 > has been already applied), and even though you have already decided > not to use hunks #2 and #3, you *can* revisit them with "J" or "K", > change your mind and use them if you wanted to. But it is too late > for the hunk #1. It looks utterly inconsistent if you cannot change > your mind on hunk #1 but can on hunks #2 and #3 and it reduces the > usefulness of "you do not have to decide right now and visit other > files before you do so" mode. > > Thanks. Okay yes that would be very inconsistent. I briefly thought about this. If a user decides USE on some hunks and goes to the next file, and we apply the patch, the user comes and decides SKIP on those hunk(s), can't we "unapply" those hunks using "git apply -R"? I have not really thought about the complexities but it seems to be something that might be complex. I just thought to share to hear your thoughts Thanks Abraham ^ permalink raw reply [flat|nested] 54+ messages in thread
* Re: [PATCH v2 0/1] Allow reworking with a file when making hunk decisions 2026-01-27 15:43 ` [PATCH v2 0/1] Allow reworking with a file when making hunk decisions Abraham Samuel Adekunle 2026-01-27 15:45 ` [PATCH v2 1/1] Allow reworking with a file after deciding on all its hunks Abraham Samuel Adekunle @ 2026-01-27 17:04 ` Junio C Hamano 2026-01-28 9:49 ` Samuel Abraham 2026-02-06 15:52 ` [PATCH v3 0/3] introduce new option `rework-with-file` Abraham Samuel Adekunle 2 siblings, 1 reply; 54+ messages in thread From: Junio C Hamano @ 2026-01-27 17:04 UTC (permalink / raw) To: Abraham Samuel Adekunle Cc: git, Patrick Steinhardt, Phillip Wood, SZEDER Gábor, Christian Couder, Kristoffer Haugsbakk, Ben Knoble Abraham Samuel Adekunle <abrahamadekunle50@gmail.com> writes: > If there is only one file, neither of the options will be > available, if we are in the second of three or more file, both '<' > and '>' will be available and if we are at the last file, only '<' > will be available. An obvious alternative would be to treat the files as a ring, going next from the last one would take you to the first one, etc., but I think what you described is just as good. > This will enable simultaneous hunk decisions between between files. > After all decisions have been made in a file, a prompt shows which asks > "All hunks decided. What now?" that allows reworking with the file, > moving to the next or previous file as the case may be. I forgot to mention this in the previous review, but this would be a change that existing users may be surprised by. We _might_ need to introduce a flag to enable this as a new and optional feature. > The decision to use 'q' as a submit is because after some or all > the decisions have been made in a file, 'q' submits them as is > even though in the `help_patch_text` it say `q` will not stage the > current hunk and all hunks after it. The users do need to _knowingly_ leave some hunks undecided and apply what they already decided to use, and I think 'q' is an appropriate option to use. It is what the current system does, and I do not think it changes with this new feature. Thanks. ^ permalink raw reply [flat|nested] 54+ messages in thread
* Re: [PATCH v2 0/1] Allow reworking with a file when making hunk decisions 2026-01-27 17:04 ` [PATCH v2 0/1] Allow reworking with a file when making hunk decisions Junio C Hamano @ 2026-01-28 9:49 ` Samuel Abraham 0 siblings, 0 replies; 54+ messages in thread From: Samuel Abraham @ 2026-01-28 9:49 UTC (permalink / raw) To: Junio C Hamano Cc: git, Patrick Steinhardt, Phillip Wood, SZEDER Gábor, Christian Couder, Kristoffer Haugsbakk, Ben Knoble On Tue, Jan 27, 2026 at 6:04 PM Junio C Hamano <gitster@pobox.com> wrote: > > Abraham Samuel Adekunle <abrahamadekunle50@gmail.com> writes: > > > If there is only one file, neither of the options will be > > available, if we are in the second of three or more file, both '<' > > and '>' will be available and if we are at the last file, only '<' > > will be available. > > An obvious alternative would be to treat the files as a ring, going > next from the last one would take you to the first one, etc., but I > think what you described is just as good. Okay I will work that > > > This will enable simultaneous hunk decisions between between files. > > After all decisions have been made in a file, a prompt shows which asks > > "All hunks decided. What now?" that allows reworking with the file, > > moving to the next or previous file as the case may be. > > I forgot to mention this in the previous review, but this would be a > change that existing users may be surprised by. We _might_ need to > introduce a flag to enable this as a new and optional feature. Yes I thought about this too and it is a good idea. Thanks > > > The decision to use 'q' as a submit is because after some or all > > the decisions have been made in a file, 'q' submits them as is > > even though in the `help_patch_text` it say `q` will not stage the > > current hunk and all hunks after it. > > The users do need to _knowingly_ leave some hunks undecided and > apply what they already decided to use, and I think 'q' is an > appropriate option to use. It is what the current system does, > and I do not think it changes with this new feature. Okay thank you. ^ permalink raw reply [flat|nested] 54+ messages in thread
* [PATCH v3 0/3] introduce new option `rework-with-file` 2026-01-27 15:43 ` [PATCH v2 0/1] Allow reworking with a file when making hunk decisions Abraham Samuel Adekunle 2026-01-27 15:45 ` [PATCH v2 1/1] Allow reworking with a file after deciding on all its hunks Abraham Samuel Adekunle 2026-01-27 17:04 ` [PATCH v2 0/1] Allow reworking with a file when making hunk decisions Junio C Hamano @ 2026-02-06 15:52 ` Abraham Samuel Adekunle 2026-02-06 15:54 ` [PATCH v3 1/3] interactive -p: add new `--rework-with-file` flag to interactive machinery Abraham Samuel Adekunle ` (4 more replies) 2 siblings, 5 replies; 54+ messages in thread From: Abraham Samuel Adekunle @ 2026-02-06 15:52 UTC (permalink / raw) To: git Cc: Patrick Steinhardt, Phillip Wood, SZEDER Gábor, Christian Couder, Kristoffer Haugsbakk, Ben Knoble, Junio C Hamano, Karthik Nayak Hello, After further review from Junio, I have been able to make reworking with a file during hunk selection an optional feature by passing the `rework-with-file` flag to the --patch option in the interactive machinery. With the option, users can navigate in between files and while deciding on hunks as they wish with the '>' and '<' option for going to the next and previous file respectively if there are more than one file. The process shows a prompt which allows reworking with the file and changing previous decisions if need be, going to the next or previous file if possible or using 'q' to submit and end the process. Patch 1 implements the new 'rework-with-file' options, Patch 2 makes some changes to allow interfile navigation when the option is supplied and Patch 3 modifies the code to allow the patches to be applied only after all decisions have been made and session ends when this option is enabled. Abraham Samuel Adekunle (3): interactive -p: add new `--rework-with-file` flag to interactive machinery add-patch: Allow interfile navigation when selecting hunks add-patch: Allow proper 'git apply' when using the --rework-with-file flag add-interactive.c | 3 + add-interactive.h | 5 +- add-patch.c | 161 +++++++++++++++++++++++++++++++----------- builtin/add.c | 4 ++ builtin/checkout.c | 6 ++ builtin/reset.c | 4 ++ builtin/stash.c | 8 +++ t/t9902-completion.sh | 1 + 8 files changed, 148 insertions(+), 44 deletions(-) -- 2.39.5 (Apple Git-154) ^ permalink raw reply [flat|nested] 54+ messages in thread
* [PATCH v3 1/3] interactive -p: add new `--rework-with-file` flag to interactive machinery 2026-02-06 15:52 ` [PATCH v3 0/3] introduce new option `rework-with-file` Abraham Samuel Adekunle @ 2026-02-06 15:54 ` Abraham Samuel Adekunle 2026-02-06 18:25 ` Junio C Hamano 2026-02-06 15:56 ` [PATCH v3 2/3] add-patch: Allow interfile navigation when selecting hunks Abraham Samuel Adekunle ` (3 subsequent siblings) 4 siblings, 1 reply; 54+ messages in thread From: Abraham Samuel Adekunle @ 2026-02-06 15:54 UTC (permalink / raw) To: git Cc: Patrick Steinhardt, Phillip Wood, SZEDER Gábor, Christian Couder, Kristoffer Haugsbakk, Ben Knoble, Junio C Hamano, Karthik Nayak When using the interactive add, reset, stash or checkout machinery, we do not have the option of reworking with a file because the session automatically advances to the next file or ends if we have just one file, immediately all hunks in a file are decided on. Introduce the flag "--rework-with-file" when interactively selecting patches with the '--patch' option, which does not auto advance, thereby allowing users the option to rework with files. This ensures the current auto-advance method stays as the default method. Signed-off-by: Abraham Samuel Adekunle <abrahamadekunle50@gmail.com> --- add-interactive.c | 3 +++ add-interactive.h | 5 +++-- builtin/add.c | 4 ++++ builtin/checkout.c | 6 ++++++ builtin/reset.c | 4 ++++ builtin/stash.c | 8 ++++++++ t/t9902-completion.sh | 1 + 7 files changed, 29 insertions(+), 2 deletions(-) diff --git a/add-interactive.c b/add-interactive.c index 68fc09547d..4eda115da8 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->no_auto_advance = 0; s->use_color_interactive = check_color_config(r, "color.interactive"); @@ -124,6 +125,8 @@ void init_add_i_state(struct add_i_state *s, struct repository *r, die(_("%s cannot be negative"), "--inter-hunk-context"); s->interhunkcontext = add_p_opt->interhunkcontext; } + if (add_p_opt->no_auto_advance) + s->no_auto_advance = 1; } void clear_add_i_state(struct add_i_state *s) diff --git a/add-interactive.h b/add-interactive.h index da49502b76..aef2feca56 100644 --- a/add-interactive.h +++ b/add-interactive.h @@ -6,9 +6,10 @@ struct add_p_opt { int context; int interhunkcontext; + int no_auto_advance; }; -#define ADD_P_OPT_INIT { .context = -1, .interhunkcontext = -1 } +#define ADD_P_OPT_INIT { .context = -1, .interhunkcontext = -1, .no_auto_advance = 0 } struct add_i_state { struct repository *r; @@ -28,7 +29,7 @@ struct add_i_state { int use_single_key; char *interactive_diff_filter, *interactive_diff_algorithm; - int context, interhunkcontext; + int context, interhunkcontext, no_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..408827cf54 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, "rework-with-file", &add_p_opt.no_auto_advance, + N_("rework with files 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.no_auto_advance) + die(_("the option '%s' requires '%s'"), "--rework-with-file", "--interactive/--patch"); } if (edit_interactive) { diff --git a/builtin/checkout.c b/builtin/checkout.c index 261699e2f5..3e98d06be1 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 no_auto_advance; int quiet; int merge; int force; @@ -549,6 +550,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, + .no_auto_advance = opts->no_auto_advance }; const char *rev = new_branch_info->name; char rev_oid[GIT_MAX_HEXSZ + 1]; @@ -1747,6 +1749,8 @@ static struct option *add_checkout_path_options(struct checkout_opts *opts, N_("checkout their version for unmerged files"), 3, PARSE_OPT_NONEG), OPT_BOOL('p', "patch", &opts->patch_mode, N_("select hunks interactively")), + OPT_BOOL(0, "rework-with-file", &opts->no_auto_advance, + N_("rework with files when selecting hunks interactively")), OPT_DIFF_UNIFIED(&opts->patch_context), OPT_DIFF_INTERHUNK_CONTEXT(&opts->patch_interhunk_context), OPT_BOOL(0, "ignore-skip-worktree-bits", &opts->ignore_skipworktree, @@ -1801,6 +1805,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->no_auto_advance) + die(_("the option '%s' requires '%s'"), "--rework-with-file", "--patch"); } if (opts->show_progress < 0) { diff --git a/builtin/reset.c b/builtin/reset.c index ed35802af1..1e7b93785d 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, "rework-with-file", &add_p_opt.no_auto_advance, + N_("rework with files 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.no_auto_advance) + die(_("the option '%s' requires '%s'"), "--rework-with-file", "--patch"); } /* git reset tree [--] paths... can be used to diff --git a/builtin/stash.c b/builtin/stash.c index 948eba06fb..1311707ea6 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, "rework-with-file", &add_p_opt.no_auto_advance, + N_("rework with files 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.no_auto_advance) + die(_("the option '%s' requires '%s'"), "--rework-with-file", "--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, "rework-with-file", &add_p_opt.no_auto_advance, + N_("rework with files 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.no_auto_advance) + die(_("the option '%s' requires '%s'"), "--rework-with-file", "--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..302534e92d 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 + --rework-with-file Z --progress Z --guess Z --no-guess Z -- 2.39.5 (Apple Git-154) ^ permalink raw reply related [flat|nested] 54+ messages in thread
* Re: [PATCH v3 1/3] interactive -p: add new `--rework-with-file` flag to interactive machinery 2026-02-06 15:54 ` [PATCH v3 1/3] interactive -p: add new `--rework-with-file` flag to interactive machinery Abraham Samuel Adekunle @ 2026-02-06 18:25 ` Junio C Hamano 2026-02-06 20:21 ` Samuel Abraham 0 siblings, 1 reply; 54+ messages in thread From: Junio C Hamano @ 2026-02-06 18:25 UTC (permalink / raw) To: Abraham Samuel Adekunle Cc: git, Patrick Steinhardt, Phillip Wood, SZEDER Gábor, Christian Couder, Kristoffer Haugsbakk, Ben Knoble, Karthik Nayak Abraham Samuel Adekunle <abrahamadekunle50@gmail.com> writes: > When using the interactive add, reset, stash or checkout machinery, we do > not have the option of reworking with a file because the session automatically > advances to the next file or ends if we have just one file, immediately all hunks > in a file are decided on. The last part of the sentence after the last comma does not read very well, at least to me. We recommend to fold lines in such a way that after a few e-mail exchange and quoting it will still stay within 80-column, so a practical fill-column value lies somewhere around ~70. Your lines a slightly longer. > Introduce the flag "--rework-with-file" when interactively selecting patches with the > '--patch' option, which does not auto advance, thereby allowing users the option > to rework with files. > This ensures the current auto-advance method stays as the default method. OK. There may be suggestions for better option names from others; I do not think of any right now. > diff --git a/add-interactive.h b/add-interactive.h > index da49502b76..aef2feca56 100644 > --- a/add-interactive.h > +++ b/add-interactive.h > @@ -6,9 +6,10 @@ > struct add_p_opt { > int context; > int interhunkcontext; > + int no_auto_advance; > }; We add a new risk of double-negation confusion, e.g., if (!opt->no_auto_advance) ... do the auto-advance thing ... where it may be easier to follow if it were written if (opt->auto_advance) ... do the auto-advance thing ... Would it make it harder to arrange the code if we made this member "auto_advance" that defaults to "true"? We have ADD_P_OPT_INIT that everybody is supposed to call already, like this > -#define ADD_P_OPT_INIT { .context = -1, .interhunkcontext = -1 } > +#define ADD_P_OPT_INIT { .context = -1, .interhunkcontext = -1, .no_auto_advance = 0 } so I do not imagine it would be too much hassle. > @@ -28,7 +29,7 @@ struct add_i_state { > > int use_single_key; > char *interactive_diff_filter, *interactive_diff_algorithm; > - int context, interhunkcontext; > + int context, interhunkcontext, no_auto_advance; > }; Likewise. > diff --git a/builtin/add.c b/builtin/add.c > index 32709794b3..408827cf54 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, "rework-with-file", &add_p_opt.no_auto_advance, > + N_("rework with files when selecting hunks interactively")), Likewise. ^ permalink raw reply [flat|nested] 54+ messages in thread
* Re: [PATCH v3 1/3] interactive -p: add new `--rework-with-file` flag to interactive machinery 2026-02-06 18:25 ` Junio C Hamano @ 2026-02-06 20:21 ` Samuel Abraham 0 siblings, 0 replies; 54+ messages in thread From: Samuel Abraham @ 2026-02-06 20:21 UTC (permalink / raw) To: Junio C Hamano Cc: git, Patrick Steinhardt, Phillip Wood, SZEDER Gábor, Christian Couder, Kristoffer Haugsbakk, Ben Knoble, Karthik Nayak On Fri, Feb 6, 2026 at 7:25 PM Junio C Hamano <gitster@pobox.com> wrote: > > Abraham Samuel Adekunle <abrahamadekunle50@gmail.com> writes: > > > When using the interactive add, reset, stash or checkout machinery, we do > > not have the option of reworking with a file because the session automatically > > advances to the next file or ends if we have just one file, immediately all hunks > > in a file are decided on. > > The last part of the sentence after the last comma does not read > very well, at least to me. Okay I will reword it. > > We recommend to fold lines in such a way that after a few e-mail > exchange and quoting it will still stay within 80-column, so a > practical fill-column value lies somewhere around ~70. Your lines a > slightly longer. Okay thank you. I will watch out for this. > > > Introduce the flag "--rework-with-file" when interactively selecting patches with the > > '--patch' option, which does not auto advance, thereby allowing users the option > > to rework with files. > > This ensures the current auto-advance method stays as the default method. > > OK. There may be suggestions for better option names from others; I > do not think of any right now. Okay > > > diff --git a/add-interactive.h b/add-interactive.h > > index da49502b76..aef2feca56 100644 > > --- a/add-interactive.h > > +++ b/add-interactive.h > > @@ -6,9 +6,10 @@ > > struct add_p_opt { > > int context; > > int interhunkcontext; > > + int no_auto_advance; > > }; > > We add a new risk of double-negation confusion, e.g., > > if (!opt->no_auto_advance) > ... do the auto-advance thing ... > > where it may be easier to follow if it were written > > if (opt->auto_advance) > ... do the auto-advance thing ... > > Would it make it harder to arrange the code if we made this member > "auto_advance" that defaults to "true"? We have ADD_P_OPT_INIT that > everybody is supposed to call already, like this > > > -#define ADD_P_OPT_INIT { .context = -1, .interhunkcontext = -1 } > > +#define ADD_P_OPT_INIT { .context = -1, .interhunkcontext = -1, .no_auto_advance = 0 } > > so I do not imagine it would be too much hassle. No it won't. > > > @@ -28,7 +29,7 @@ struct add_i_state { > > > > int use_single_key; > > char *interactive_diff_filter, *interactive_diff_algorithm; > > - int context, interhunkcontext; > > + int context, interhunkcontext, no_auto_advance; > > }; > > Likewise. Noted. > > > diff --git a/builtin/add.c b/builtin/add.c > > index 32709794b3..408827cf54 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, "rework-with-file", &add_p_opt.no_auto_advance, > > + N_("rework with files when selecting hunks interactively")), > > Likewise. Thanks. Abraham ^ permalink raw reply [flat|nested] 54+ messages in thread
* [PATCH v3 2/3] add-patch: Allow interfile navigation when selecting hunks 2026-02-06 15:52 ` [PATCH v3 0/3] introduce new option `rework-with-file` Abraham Samuel Adekunle 2026-02-06 15:54 ` [PATCH v3 1/3] interactive -p: add new `--rework-with-file` flag to interactive machinery Abraham Samuel Adekunle @ 2026-02-06 15:56 ` Abraham Samuel Adekunle 2026-02-06 18:35 ` Junio C Hamano ` (2 more replies) 2026-02-06 15:57 ` [PATCH v3 3/3] add-patch: Allow proper 'git apply' when using the --rework-with-file flag Abraham Samuel Adekunle ` (2 subsequent siblings) 4 siblings, 3 replies; 54+ messages in thread From: Abraham Samuel Adekunle @ 2026-02-06 15:56 UTC (permalink / raw) To: git Cc: Patrick Steinhardt, Phillip Wood, SZEDER Gábor, Christian Couder, Kristoffer Haugsbakk, Ben Knoble, Junio C Hamano, Karthik Nayak 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 `--rework-with-file` 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, a prompt appears, which allow the user to 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. This feature is enabled by passing the `--rework-with-file` flag to `--patch` option of the subcommands add, stash, reset, and checkout Signed-off-by: Abraham Samuel Adekunle <abrahamadekunle50@gmail.com> --- add-patch.c | 95 ++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 80 insertions(+), 15 deletions(-) diff --git a/add-patch.c b/add-patch.c index 173a53241e..2bd839f17e 100644 --- a/add-patch.c +++ b/add-patch.c @@ -1418,6 +1418,8 @@ 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" + "> - go to the next file\n" + "< - go to the previous file\n" "? - print help\n"); static size_t dec_mod(size_t a, size_t m) @@ -1430,6 +1432,12 @@ static size_t inc_mod(size_t a, size_t m) return a < m - 1 ? a + 1 : 0; } +enum patch_update_response { + NEXT_FILE = 0, + QUIT, + PREVIOUS_FILE, +}; + static bool get_first_undecided(const struct file_diff *file_diff, size_t *idx) { for (size_t i = 0; i < file_diff->hunk_nr; i++) { @@ -1441,7 +1449,7 @@ 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, +static enum patch_update_response patch_update_file(struct add_p_state *s, struct file_diff *file_diff) { size_t hunk_index = 0; @@ -1449,12 +1457,14 @@ static int patch_update_file(struct add_p_state *s, 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; + int all_decided = 0; + enum patch_update_response ret = NEXT_FILE; /* Empty added files have no hunks */ if (!file_diff->hunk_nr && !file_diff->added) - return 0; + return NEXT_FILE; strbuf_reset(&s->buf); render_diff_header(s, file_diff, colored, &s->buf); @@ -1467,7 +1477,9 @@ static int patch_update_file(struct add_p_state *s, 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) @@ -1498,9 +1510,12 @@ 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) { + if (s->s.no_auto_advance) + all_decided = 1; + else + break; + } strbuf_reset(&s->buf); if (file_diff->hunk_nr) { if (rendered_hunk_index != hunk_index) { @@ -1548,6 +1563,14 @@ static int patch_update_file(struct add_p_state *s, permitted |= ALLOW_EDIT; strbuf_addstr(&s->buf, ",e"); } + if (s->s.no_auto_advance && s->file_diff_nr > 1) { + permitted |= ALLOW_GOTO_NEXT_FILE; + strbuf_addstr(&s->buf, ",>"); + } + if (s->s.no_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) @@ -1566,11 +1589,14 @@ static int patch_update_file(struct add_p_state *s, : 1)); printf(_(s->mode->prompt_mode[prompt_mode_type]), s->buf.buf); + if (s->s.no_auto_advance && all_decided) + printf(_("\n%s All hunks decided. What now? "), + s->s.prompt_color); if (*s->s.reset_color_interactive) fputs(s->s.reset_color_interactive, stdout); fflush(stdout); if (read_single_character(s) == EOF) { - quit = 1; + ret = QUIT; break; } @@ -1616,9 +1642,26 @@ static int patch_update_file(struct add_p_state *s, hunk->use = SKIP_HUNK; } } else if (ch == 'q') { - quit = 1; + ret = QUIT; break; - } else if (s->answer.buf[0] == 'K') { + } else if (s->s.no_auto_advance && s->answer.buf[0] == '>') { + if (permitted & ALLOW_GOTO_NEXT_FILE) { + ret = NEXT_FILE; + break; + } else { + err(s, _("No next file")); + continue; + } + } else if (s->s.no_auto_advance && s->answer.buf[0] == '<') { + if (permitted & ALLOW_GOTO_PREVIOUS_FILE) { + ret = PREVIOUS_FILE; + 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, file_diff->hunk_nr); @@ -1803,7 +1846,7 @@ static int patch_update_file(struct add_p_state *s, } putchar('\n'); - return quit; + return ret; } int run_add_p(struct repository *r, enum add_p_mode mode, @@ -1814,6 +1857,7 @@ int run_add_p(struct repository *r, enum add_p_mode mode, { r }, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT }; size_t i, binary_count = 0; + enum patch_update_response ret; init_add_i_state(&s.s, r, o); @@ -1852,11 +1896,32 @@ 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)) - break; + i++; + continue; + } + else { + ret = patch_update_file(&s, s.file_diff + i); + if (ret == NEXT_FILE) { + if (s.s.no_auto_advance && i == s.file_diff_nr - 1) + i = 0; + else + i++; + continue; + } + if (ret == QUIT) + break; + if (s.s.no_auto_advance && ret == PREVIOUS_FILE) { + if (i == 0) + i = s.file_diff_nr - 1; + else + i--; + continue; + } + } + } if (s.file_diff_nr == 0) err(&s, _("No changes.")); -- 2.39.5 (Apple Git-154) ^ permalink raw reply related [flat|nested] 54+ messages in thread
* Re: [PATCH v3 2/3] add-patch: Allow interfile navigation when selecting hunks 2026-02-06 15:56 ` [PATCH v3 2/3] add-patch: Allow interfile navigation when selecting hunks Abraham Samuel Adekunle @ 2026-02-06 18:35 ` Junio C Hamano 2026-02-06 20:22 ` Samuel Abraham 2026-02-06 18:54 ` Junio C Hamano 2026-02-06 19:21 ` Junio C Hamano 2 siblings, 1 reply; 54+ messages in thread From: Junio C Hamano @ 2026-02-06 18:35 UTC (permalink / raw) To: Abraham Samuel Adekunle Cc: git, Patrick Steinhardt, Phillip Wood, SZEDER Gábor, Christian Couder, Kristoffer Haugsbakk, Ben Knoble, Karthik Nayak Abraham Samuel Adekunle <abrahamadekunle50@gmail.com> writes: > - } else if (s->answer.buf[0] == 'K') { > + } else if (s->s.no_auto_advance && s->answer.buf[0] == '>') { > + if (permitted & ALLOW_GOTO_NEXT_FILE) { > + ret = NEXT_FILE; > +... > + continue; > + } > + } > + else if (s->answer.buf[0] == 'K') { This funny-looking diff is a sign that the coding guideline was followed in the preimage but not in the postimage, by splitting the "} else if (condition) {" into two lines for 'K'. ^ permalink raw reply [flat|nested] 54+ messages in thread
* Re: [PATCH v3 2/3] add-patch: Allow interfile navigation when selecting hunks 2026-02-06 18:35 ` Junio C Hamano @ 2026-02-06 20:22 ` Samuel Abraham 0 siblings, 0 replies; 54+ messages in thread From: Samuel Abraham @ 2026-02-06 20:22 UTC (permalink / raw) To: Junio C Hamano Cc: git, Patrick Steinhardt, Phillip Wood, SZEDER Gábor, Christian Couder, Kristoffer Haugsbakk, Ben Knoble, Karthik Nayak On Fri, Feb 6, 2026 at 7:35 PM Junio C Hamano <gitster@pobox.com> wrote: > > Abraham Samuel Adekunle <abrahamadekunle50@gmail.com> writes: > > > - } else if (s->answer.buf[0] == 'K') { > > + } else if (s->s.no_auto_advance && s->answer.buf[0] == '>') { > > + if (permitted & ALLOW_GOTO_NEXT_FILE) { > > + ret = NEXT_FILE; > > +... > > + continue; > > + } > > + } > > + else if (s->answer.buf[0] == 'K') { > > This funny-looking diff is a sign that the coding guideline was > followed in the preimage but not in the postimage, by splitting > the "} else if (condition) {" into two lines for 'K'. Sorry, I will fix it ^ permalink raw reply [flat|nested] 54+ messages in thread
* Re: [PATCH v3 2/3] add-patch: Allow interfile navigation when selecting hunks 2026-02-06 15:56 ` [PATCH v3 2/3] add-patch: Allow interfile navigation when selecting hunks Abraham Samuel Adekunle 2026-02-06 18:35 ` Junio C Hamano @ 2026-02-06 18:54 ` Junio C Hamano 2026-02-06 20:32 ` Samuel Abraham 2026-02-06 19:21 ` Junio C Hamano 2 siblings, 1 reply; 54+ messages in thread From: Junio C Hamano @ 2026-02-06 18:54 UTC (permalink / raw) To: Abraham Samuel Adekunle Cc: git, Patrick Steinhardt, Phillip Wood, SZEDER Gábor, Christian Couder, Kristoffer Haugsbakk, Ben Knoble, Karthik Nayak Abraham Samuel Adekunle <abrahamadekunle50@gmail.com> writes: > + for (i = 0; i < s.file_diff_nr;) { > + if (s.file_diff[i].binary && !s.file_diff[i].hunk_nr) { > binary_count++; > + i++; > + continue; > + } This "continue" is a commonly seen good trick to avoid having the nesting go too deep. As we know the case where the condition holds have already been dealt with and moved to the next iteration at this point, we can ... > + else { ... omit this extra "else" block and write what is inside for everybody (not just "those who did not pass the if condition above", which is what "else" tells us). > + ret = patch_update_file(&s, s.file_diff + i); > + if (ret == NEXT_FILE) { > + if (s.s.no_auto_advance && i == s.file_diff_nr - 1) > + i = 0; > + else > + i++; > + continue; > + } > + if (ret == QUIT) > + break; > + if (s.s.no_auto_advance && ret == PREVIOUS_FILE) { > + if (i == 0) > + i = s.file_diff_nr - 1; > + else > + i--; > + continue; > + } The asymmetry between next/quit and prev feels curious. The patch_update_file() helper returns QUIT when the user tells us to (regardless of auto-advance setting), PREVIOUS when '<' is given but that is only possible with auto-advance disabled, and NEXT in all other cases. The check inside the NEXT case for auto-advance is to decide if we want to overflow 'i' beyond file_diff_nr to complete the session, or we want to wrap-around back to the first file. But ret can be PREV only under auto-advance disabled, so the check there feels totally redundant. And we want to treat the list of files as a ring buffer only when auto-advance is set to false. This may work in practice but the logic feels convoluted. The patch_update_file() knows how many files there are to decide if we want to offer '<' and '>'. It also knows the file index within the file_diff_nr for the file it is handling. I wonder if it should do a bit more with its return value to help the caller? E.g., perhaps it can return the next 'i' if it wants the caller to advance (and decide to do the ring-buffer if needed), or if it wants to tell the caller that everything is done by returning some sentinel value (e.g., -1)? Then this part of the caller can just be if ((i = patch_update_file(&s, i)) < 0) break; /* all done */ perhaps? By the way, I just noticed that the new local variable you added to patch_update_file() is called "ret" but that is hiding a different variable "int ret" that is used to handle the '/' command. It should be renamed to avoid the name collision. ^ permalink raw reply [flat|nested] 54+ messages in thread
* Re: [PATCH v3 2/3] add-patch: Allow interfile navigation when selecting hunks 2026-02-06 18:54 ` Junio C Hamano @ 2026-02-06 20:32 ` Samuel Abraham 0 siblings, 0 replies; 54+ messages in thread From: Samuel Abraham @ 2026-02-06 20:32 UTC (permalink / raw) To: Junio C Hamano Cc: git, Patrick Steinhardt, Phillip Wood, SZEDER Gábor, Christian Couder, Kristoffer Haugsbakk, Ben Knoble, Karthik Nayak On Fri, Feb 6, 2026 at 7:54 PM Junio C Hamano <gitster@pobox.com> wrote: > > Abraham Samuel Adekunle <abrahamadekunle50@gmail.com> writes: > > > + for (i = 0; i < s.file_diff_nr;) { > > + if (s.file_diff[i].binary && !s.file_diff[i].hunk_nr) { > > binary_count++; > > + i++; > > + continue; > > + } > > This "continue" is a commonly seen good trick to avoid having the > nesting go too deep. As we know the case where the condition holds > have already been dealt with and moved to the next iteration at this > point, we can ... > > > + else { > > ... omit this extra "else" block and write what is inside for > everybody (not just "those who did not pass the if condition above", > which is what "else" tells us). Yes. > > > + ret = patch_update_file(&s, s.file_diff + i); > > + if (ret == NEXT_FILE) { > > + if (s.s.no_auto_advance && i == s.file_diff_nr - 1) > > + i = 0; > > + else > > + i++; > > + continue; > > + } > > + if (ret == QUIT) > > + break; > > + if (s.s.no_auto_advance && ret == PREVIOUS_FILE) { > > + if (i == 0) > > + i = s.file_diff_nr - 1; > > + else > > + i--; > > + continue; > > + } > > The asymmetry between next/quit and prev feels curious. > > The patch_update_file() helper returns QUIT when the user tells us > to (regardless of auto-advance setting), PREVIOUS when '<' is given > but that is only possible with auto-advance disabled, and NEXT in > all other cases. The check inside the NEXT case for auto-advance is > to decide if we want to overflow 'i' beyond file_diff_nr to complete > the session, or we want to wrap-around back to the first file. > > But ret can be PREV only under auto-advance disabled, so the check > there feels totally redundant. > > And we want to treat the list of files as a ring buffer only when > auto-advance is set to false. This may work in practice but the > logic feels convoluted. > > The patch_update_file() knows how many files there are to decide if > we want to offer '<' and '>'. It also knows the file index within > the file_diff_nr for the file it is handling. I wonder if it should > do a bit more with its return value to help the caller? E.g., > perhaps it can return the next 'i' if it wants the caller to advance > (and decide to do the ring-buffer if needed), or if it wants to tell > the caller that everything is done by returning some sentinel value > (e.g., -1)? Then this part of the caller can just be > > if ((i = patch_update_file(&s, i)) < 0) > break; /* all done */ > > perhaps? Yes this makes sense. Thank you > > By the way, I just noticed that the new local variable you added to > patch_update_file() is called "ret" but that is hiding a different > variable "int ret" that is used to handle the '/' command. It > should be renamed to avoid the name collision. > Okay thank you Abraham ^ permalink raw reply [flat|nested] 54+ messages in thread
* Re: [PATCH v3 2/3] add-patch: Allow interfile navigation when selecting hunks 2026-02-06 15:56 ` [PATCH v3 2/3] add-patch: Allow interfile navigation when selecting hunks Abraham Samuel Adekunle 2026-02-06 18:35 ` Junio C Hamano 2026-02-06 18:54 ` Junio C Hamano @ 2026-02-06 19:21 ` Junio C Hamano 2026-02-06 20:37 ` Samuel Abraham 2026-02-12 10:32 ` Samuel Abraham 2 siblings, 2 replies; 54+ messages in thread From: Junio C Hamano @ 2026-02-06 19:21 UTC (permalink / raw) To: Abraham Samuel Adekunle Cc: git, Patrick Steinhardt, Phillip Wood, SZEDER Gábor, Christian Couder, Kristoffer Haugsbakk, Ben Knoble, Karthik Nayak Abraham Samuel Adekunle <abrahamadekunle50@gmail.com> writes: > @@ -1566,11 +1589,14 @@ static int patch_update_file(struct add_p_state *s, > : 1)); > printf(_(s->mode->prompt_mode[prompt_mode_type]), > s->buf.buf); > + if (s->s.no_auto_advance && all_decided) > + printf(_("\n%s All hunks decided. What now? "), > + s->s.prompt_color); This gives an ordinary prompt for the hunk and then another one after it if we notice everything has been decided. I am wondering if it wants to be more like if (!s->auto_advance && all_decided) say What now? else ask the usual ? ^ permalink raw reply [flat|nested] 54+ messages in thread
* Re: [PATCH v3 2/3] add-patch: Allow interfile navigation when selecting hunks 2026-02-06 19:21 ` Junio C Hamano @ 2026-02-06 20:37 ` Samuel Abraham 2026-02-12 10:32 ` Samuel Abraham 1 sibling, 0 replies; 54+ messages in thread From: Samuel Abraham @ 2026-02-06 20:37 UTC (permalink / raw) To: Junio C Hamano Cc: git, Patrick Steinhardt, Phillip Wood, SZEDER Gábor, Christian Couder, Kristoffer Haugsbakk, Ben Knoble, Karthik Nayak On Fri, Feb 6, 2026 at 8:21 PM Junio C Hamano <gitster@pobox.com> wrote: > > Abraham Samuel Adekunle <abrahamadekunle50@gmail.com> writes: > > > @@ -1566,11 +1589,14 @@ static int patch_update_file(struct add_p_state *s, > > : 1)); > > printf(_(s->mode->prompt_mode[prompt_mode_type]), > > s->buf.buf); > > + if (s->s.no_auto_advance && all_decided) > > + printf(_("\n%s All hunks decided. What now? "), > > + s->s.prompt_color); > > This gives an ordinary prompt for the hunk and then another one > after it if we notice everything has been decided. I am wondering > if it wants to be more like > > if (!s->auto_advance && all_decided) > say What now? > else > ask the usual > > ? Okay noted Thank you Abraham ^ permalink raw reply [flat|nested] 54+ messages in thread
* Re: [PATCH v3 2/3] add-patch: Allow interfile navigation when selecting hunks 2026-02-06 19:21 ` Junio C Hamano 2026-02-06 20:37 ` Samuel Abraham @ 2026-02-12 10:32 ` Samuel Abraham 2026-02-12 17:25 ` Junio C Hamano 1 sibling, 1 reply; 54+ messages in thread From: Samuel Abraham @ 2026-02-12 10:32 UTC (permalink / raw) To: Junio C Hamano Cc: git, Patrick Steinhardt, Phillip Wood, SZEDER Gábor, Christian Couder, Kristoffer Haugsbakk, Ben Knoble, Karthik Nayak On Fri, Feb 6, 2026 at 8:21 PM Junio C Hamano <gitster@pobox.com> wrote: > > Abraham Samuel Adekunle <abrahamadekunle50@gmail.com> writes: > > > @@ -1566,11 +1589,14 @@ static int patch_update_file(struct add_p_state *s, > > : 1)); > > printf(_(s->mode->prompt_mode[prompt_mode_type]), > > s->buf.buf); > > + if (s->s.no_auto_advance && all_decided) > > + printf(_("\n%s All hunks decided. What now? "), > > + s->s.prompt_color); > > This gives an ordinary prompt for the hunk and then another one > after it if we notice everything has been decided. I am wondering > if it wants to be more like > > if (!s->auto_advance && all_decided) > say What now? > else > ask the usual > > ? Hello Junio Please just a small curiosity. If I do it this way, the user will not be able to see the options available once they have decided on all hunks and want to rework the file. The options for a hunk will not be visible if they navigate with say K or J and want to change decisions on a hunk. They will always be greeted with What now? without the available options. ^ permalink raw reply [flat|nested] 54+ messages in thread
* Re: [PATCH v3 2/3] add-patch: Allow interfile navigation when selecting hunks 2026-02-12 10:32 ` Samuel Abraham @ 2026-02-12 17:25 ` Junio C Hamano 2026-02-12 21:13 ` Samuel Abraham 0 siblings, 1 reply; 54+ messages in thread From: Junio C Hamano @ 2026-02-12 17:25 UTC (permalink / raw) To: Samuel Abraham Cc: git, Patrick Steinhardt, Phillip Wood, SZEDER Gábor, Christian Couder, Kristoffer Haugsbakk, Ben Knoble, Karthik Nayak Samuel Abraham <abrahamadekunle50@gmail.com> writes: > On Fri, Feb 6, 2026 at 8:21 PM Junio C Hamano <gitster@pobox.com> wrote: >> >> Abraham Samuel Adekunle <abrahamadekunle50@gmail.com> writes: >> >> > @@ -1566,11 +1589,14 @@ static int patch_update_file(struct add_p_state *s, >> > : 1)); >> > printf(_(s->mode->prompt_mode[prompt_mode_type]), >> > s->buf.buf); >> > + if (s->s.no_auto_advance && all_decided) >> > + printf(_("\n%s All hunks decided. What now? "), >> > + s->s.prompt_color); >> >> This gives an ordinary prompt for the hunk and then another one >> after it if we notice everything has been decided. I am wondering >> if it wants to be more like >> >> if (!s->auto_advance && all_decided) >> say What now? >> else >> ask the usual >> >> ? > > Hello Junio > Please just a small curiosity. > > If I do it this way, the user will not be able to see the options available > once they have decided on all hunks and want to rework the file. > The options for a hunk will not be visible if they navigate with say K or J > and want to change decisions on a hunk. > They will always be greeted with What now? without the available options. Ah, OK. But then after deciding on all hunks and not telling the prompt to move to another file, the user will keep seeing this extra line of prompt? It somehow smells like a waste of a whole line just to remind the user that all hunks in the file have now been decided. There was a separate topic that added "(was: [yn])" to the prompt when the prompt asks about a hunk that already has been decided on. As we only need a single bit "all hunks decided", can we do something similar, I wonder? At the beginning of the main prompt, we show which of the N available hunks we are currently at, e.g., (1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]? Perhaps we can add a third number to indicate how many of the available hunks the user has already decided, or something, that can be used to avoid this wasted line? Or is it a good thing that we are loud in this case using a whole line to remind the user that it may be time to move on? I dunno. In any case, even though I am not 100% sure that this design to devote an extra line for this single bit of information is the best one, I now understand the need for conveying it. Thanks. ^ permalink raw reply [flat|nested] 54+ messages in thread
* Re: [PATCH v3 2/3] add-patch: Allow interfile navigation when selecting hunks 2026-02-12 17:25 ` Junio C Hamano @ 2026-02-12 21:13 ` Samuel Abraham 2026-02-12 21:31 ` Junio C Hamano 0 siblings, 1 reply; 54+ messages in thread From: Samuel Abraham @ 2026-02-12 21:13 UTC (permalink / raw) To: Junio C Hamano Cc: git, Patrick Steinhardt, Phillip Wood, SZEDER Gábor, Christian Couder, Kristoffer Haugsbakk, Ben Knoble, Karthik Nayak On Thu, Feb 12, 2026 at 6:25 PM Junio C Hamano <gitster@pobox.com> wrote: > > Samuel Abraham <abrahamadekunle50@gmail.com> writes: > > > On Fri, Feb 6, 2026 at 8:21 PM Junio C Hamano <gitster@pobox.com> wrote: > >> > >> Abraham Samuel Adekunle <abrahamadekunle50@gmail.com> writes: > >> > >> > @@ -1566,11 +1589,14 @@ static int patch_update_file(struct add_p_state *s, > >> > : 1)); > >> > printf(_(s->mode->prompt_mode[prompt_mode_type]), > >> > s->buf.buf); > >> > + if (s->s.no_auto_advance && all_decided) > >> > + printf(_("\n%s All hunks decided. What now? "), > >> > + s->s.prompt_color); > >> > >> This gives an ordinary prompt for the hunk and then another one > >> after it if we notice everything has been decided. I am wondering > >> if it wants to be more like > >> > >> if (!s->auto_advance && all_decided) > >> say What now? > >> else > >> ask the usual > >> > >> ? > > > > Hello Junio > > Please just a small curiosity. > > > > If I do it this way, the user will not be able to see the options available > > once they have decided on all hunks and want to rework the file. > > The options for a hunk will not be visible if they navigate with say K or J > > and want to change decisions on a hunk. > > They will always be greeted with What now? without the available options. > > Ah, OK. > > But then after deciding on all hunks and not telling the prompt to > move to another file, the user will keep seeing this extra line of > prompt? > > It somehow smells like a waste of a whole line just to remind the > user that all hunks in the file have now been decided. > > There was a separate topic that added "(was: [yn])" to the prompt > when the prompt asks about a hunk that already has been decided on. > As we only need a single bit "all hunks decided", can we do > something similar, I wonder? At the beginning of the main prompt, > we show which of the N available hunks we are currently at, e.g., > > (1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]? > > Perhaps we can add a third number to indicate how many of the > available hunks the user has already decided, or something, that can > be used to avoid this wasted line? Or is it a good thing that we > are loud in this case using a whole line to remind the user that it > may be time to move on? I dunno. I thought of a suggestion where after deciding on all hunks in the file, the user will be able to see the "what now prompt", the options for the current hunk and also the previous decision on the hunk since at this point, all the hunks would have been decided on. I tried something like What now? (was: n) [y,n,q,a,d,s,e,>,<,p,P,?]? This does not show the number of the hunk we are currently at and the "Stage this hunk" since the decision had been made initially but the "whatnow" prompt still provides a chance to change the decision, while showing the previous decision on the hunk by asking "What now?" instead. The options have the default [y,n,q,a,d] and the remaining options are populated from the permit set for the hunk. SO the user can still carry out the normal actions on the hunk. In response to your earlier question, if the user decides on all hunks in a file and does not go to the next file, he'll see the prompt above and that is what will keep showing if he remains in the file, no extra line. If he navigates away, the hunk re-renders with the "what now" prompt when he comes back. If he had made all decisions in a file and decides to split a splittable hunk, then the normal prompt shows for those hunks since they are now undecided. What do you think about this? Thanks Abraham ^ permalink raw reply [flat|nested] 54+ messages in thread
* Re: [PATCH v3 2/3] add-patch: Allow interfile navigation when selecting hunks 2026-02-12 21:13 ` Samuel Abraham @ 2026-02-12 21:31 ` Junio C Hamano 2026-02-12 22:20 ` Samuel Abraham 0 siblings, 1 reply; 54+ messages in thread From: Junio C Hamano @ 2026-02-12 21:31 UTC (permalink / raw) To: Samuel Abraham Cc: git, Patrick Steinhardt, Phillip Wood, SZEDER Gábor, Christian Couder, Kristoffer Haugsbakk, Ben Knoble, Karthik Nayak Samuel Abraham <abrahamadekunle50@gmail.com> writes: >> There was a separate topic that added "(was: [yn])" to the prompt >> when the prompt asks about a hunk that already has been decided on. >> As we only need a single bit "all hunks decided", can we do >> something similar, I wonder? At the beginning of the main prompt, >> we show which of the N available hunks we are currently at, e.g., >> >> (1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]? >> >> Perhaps we can add a third number to indicate how many of the >> available hunks the user has already decided, or something, that can >> be used to avoid this wasted line? Or is it a good thing that we >> are loud in this case using a whole line to remind the user that it >> may be time to move on? I dunno. > > I thought of a suggestion where after deciding on all hunks in the > file, the user > will be able to see the "what now prompt", the options for the current hunk and > also the previous decision on the hunk since at this point, all the > hunks would have been decided on. > > I tried something like > > What now? (was: n) [y,n,q,a,d,s,e,>,<,p,P,?]? > > This does not show the number of the hunk we are currently at and the > "Stage this hunk" since the decision had been made initially but the "whatnow" > prompt still provides a chance to change the decision, while showing > the previous > decision on the hunk by asking "What now?" instead. > The options have the default [y,n,q,a,d] and the remaining options are populated > from the permit set for the hunk. SO the user can still carry out the > normal actions on > the hunk. I like the compactness of that myself, but I have to say that the end-users may feel lost and utterly confused with the distinction, if they are left without being explained why we switch between "Stage this" (which by the way changes phrasing depending on what you are doing) and "What now". Is it so important to indicate that everything in the hunk has been decided? They'd lose 'j' 'k' when there no longer remains undecided ones, and every hunk they revisit with 'J' or 'K' would say (was: X), which may be a clue enough that they are done with the file, and when they really really wanted to make sure, perhaps they can type '?' and that help can spend a line to say "Out of 8 hunks, you have already decided to use 3 hunks, and skip 5 hunks" or something? I dunno. ^ permalink raw reply [flat|nested] 54+ messages in thread
* Re: [PATCH v3 2/3] add-patch: Allow interfile navigation when selecting hunks 2026-02-12 21:31 ` Junio C Hamano @ 2026-02-12 22:20 ` Samuel Abraham 0 siblings, 0 replies; 54+ messages in thread From: Samuel Abraham @ 2026-02-12 22:20 UTC (permalink / raw) To: Junio C Hamano Cc: git, Patrick Steinhardt, Phillip Wood, SZEDER Gábor, Christian Couder, Kristoffer Haugsbakk, Ben Knoble, Karthik Nayak On Thu, Feb 12, 2026 at 10:31 PM Junio C Hamano <gitster@pobox.com> wrote: > > Samuel Abraham <abrahamadekunle50@gmail.com> writes: > > >> There was a separate topic that added "(was: [yn])" to the prompt > >> when the prompt asks about a hunk that already has been decided on. > >> As we only need a single bit "all hunks decided", can we do > >> something similar, I wonder? At the beginning of the main prompt, > >> we show which of the N available hunks we are currently at, e.g., > >> > >> (1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]? > >> > >> Perhaps we can add a third number to indicate how many of the > >> available hunks the user has already decided, or something, that can > >> be used to avoid this wasted line? Or is it a good thing that we > >> are loud in this case using a whole line to remind the user that it > >> may be time to move on? I dunno. > > > > I thought of a suggestion where after deciding on all hunks in the > > file, the user > > will be able to see the "what now prompt", the options for the current hunk and > > also the previous decision on the hunk since at this point, all the > > hunks would have been decided on. > > > > I tried something like > > > > What now? (was: n) [y,n,q,a,d,s,e,>,<,p,P,?]? > > > > This does not show the number of the hunk we are currently at and the > > "Stage this hunk" since the decision had been made initially but the "whatnow" > > prompt still provides a chance to change the decision, while showing > > the previous > > decision on the hunk by asking "What now?" instead. > > The options have the default [y,n,q,a,d] and the remaining options are populated > > from the permit set for the hunk. SO the user can still carry out the > > normal actions on > > the hunk. > > I like the compactness of that myself, but I have to say that the > end-users may feel lost and utterly confused with the distinction, > if they are left without being explained why we switch between > "Stage this" (which by the way changes phrasing depending on what > you are doing) and "What now". Yes I agree. > > Is it so important to indicate that everything in the hunk has been > decided? They'd lose 'j' 'k' when there no longer remains undecided > ones, and every hunk they revisit with 'J' or 'K' would say (was: X), > which may be a clue enough that they are done with the file, and > when they really really wanted to make sure, perhaps they can type > '?' and that help can spend a line to say "Out of 8 hunks, you have > already decided to use 3 hunks, and skip 5 hunks" or something? > > I dunno. Yes I also think this works. It is not so important to indicate that everything has been decided. The (was: X) shows that the hunk has been decided on and it should be enough. I will add the information to the help_patch section. Thanks Abraham ^ permalink raw reply [flat|nested] 54+ messages in thread
* [PATCH v3 3/3] add-patch: Allow proper 'git apply' when using the --rework-with-file flag 2026-02-06 15:52 ` [PATCH v3 0/3] introduce new option `rework-with-file` Abraham Samuel Adekunle 2026-02-06 15:54 ` [PATCH v3 1/3] interactive -p: add new `--rework-with-file` flag to interactive machinery Abraham Samuel Adekunle 2026-02-06 15:56 ` [PATCH v3 2/3] add-patch: Allow interfile navigation when selecting hunks Abraham Samuel Adekunle @ 2026-02-06 15:57 ` Abraham Samuel Adekunle 2026-02-06 19:02 ` Junio C Hamano 2026-02-06 19:19 ` [PATCH v3 0/3] introduce new option `rework-with-file` Junio C Hamano 2026-02-13 22:08 ` [PATCH v4 0/4] introduce new option `--auto-advance` Abraham Samuel Adekunle 4 siblings, 1 reply; 54+ messages in thread From: Abraham Samuel Adekunle @ 2026-02-06 15:57 UTC (permalink / raw) To: git Cc: Patrick Steinhardt, Phillip Wood, SZEDER Gábor, Christian Couder, Kristoffer Haugsbakk, Ben Knoble, Junio C Hamano, Karthik Nayak When the flag `--rework-with-file` 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. Modify the logic to allow all files to be applied only after the user has finished making hunk decisions and quits. This will ensure the last decision of the user is applied regardless of how the user navigates back and forth and decides. This change does not affect the default behaviour of applying the auto advancing after deciding on all hunks in a file. Signed-off-by: Abraham Samuel Adekunle <abrahamadekunle50@gmail.com> --- add-patch.c | 66 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 39 insertions(+), 27 deletions(-) diff --git a/add-patch.c b/add-patch.c index 2bd839f17e..9fb33715c2 100644 --- a/add-patch.c +++ b/add-patch.c @@ -1422,6 +1422,40 @@ N_("j - go to the next undecided hunk, roll over at the bottom\n" "< - go to the previous file\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; @@ -1456,7 +1490,6 @@ static enum patch_update_response patch_update_file(struct add_p_state *s, 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; int all_decided = 0; @@ -1818,32 +1851,8 @@ static enum patch_update_response patch_update_file(struct add_p_state *s, } } - /* 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); - } + if (!s->s.no_auto_advance) + apply_patch(s, file_diff); putchar('\n'); return ret; @@ -1922,6 +1931,9 @@ int run_add_p(struct repository *r, enum add_p_mode mode, } } } + for (i = 0; i < s.file_diff_nr; i++) + if (s.s.no_auto_advance) + apply_patch(&s, s.file_diff + i); if (s.file_diff_nr == 0) err(&s, _("No changes.")); -- 2.39.5 (Apple Git-154) ^ permalink raw reply related [flat|nested] 54+ messages in thread
* Re: [PATCH v3 3/3] add-patch: Allow proper 'git apply' when using the --rework-with-file flag 2026-02-06 15:57 ` [PATCH v3 3/3] add-patch: Allow proper 'git apply' when using the --rework-with-file flag Abraham Samuel Adekunle @ 2026-02-06 19:02 ` Junio C Hamano 2026-02-06 20:39 ` Samuel Abraham 0 siblings, 1 reply; 54+ messages in thread From: Junio C Hamano @ 2026-02-06 19:02 UTC (permalink / raw) To: Abraham Samuel Adekunle Cc: git, Patrick Steinhardt, Phillip Wood, SZEDER Gábor, Christian Couder, Kristoffer Haugsbakk, Ben Knoble, Karthik Nayak Abraham Samuel Adekunle <abrahamadekunle50@gmail.com> writes: > Subject: Re: [PATCH v3 3/3] add-patch: Allow proper 'git apply' when using the --rework-with-file flag Style. Downcase "Allow". Applies to [2/3]. Avoid "proper" as it is not obvious to everybody what you find proper and why you find it proper. Applies to any value-judgement adjective. Subject: [PATCH v3 3/3] add-patch: allow all-or-none application of a patch or something? > +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? */ Funny indentaion? > + 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); > + } > + > +} I suspect that the extraction of this helper function out of its original place in patch_update_file() should be done in its own patch. Do we need new tests to cover this new feature? ^ permalink raw reply [flat|nested] 54+ messages in thread
* Re: [PATCH v3 3/3] add-patch: Allow proper 'git apply' when using the --rework-with-file flag 2026-02-06 19:02 ` Junio C Hamano @ 2026-02-06 20:39 ` Samuel Abraham 0 siblings, 0 replies; 54+ messages in thread From: Samuel Abraham @ 2026-02-06 20:39 UTC (permalink / raw) To: Junio C Hamano Cc: git, Patrick Steinhardt, Phillip Wood, SZEDER Gábor, Christian Couder, Kristoffer Haugsbakk, Ben Knoble, Karthik Nayak On Fri, Feb 6, 2026 at 8:02 PM Junio C Hamano <gitster@pobox.com> wrote: > > Abraham Samuel Adekunle <abrahamadekunle50@gmail.com> writes: > > > Subject: Re: [PATCH v3 3/3] add-patch: Allow proper 'git apply' when using the --rework-with-file flag > > Style. Downcase "Allow". Applies to [2/3]. > > Avoid "proper" as it is not obvious to everybody what you find > proper and why you find it proper. Applies to any value-judgement > adjective. > > Subject: [PATCH v3 3/3] add-patch: allow all-or-none application of a patch > > or something? Okay > > > +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? */ > > Funny indentaion? Sorry > > > + 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); > > + } > > + > > +} > > I suspect that the extraction of this helper function out of its > original place in patch_update_file() should be done in its own > patch. Okay > > Do we need new tests to cover this new feature? Yes I will include the tests in the next version. Thanks Abraham ^ permalink raw reply [flat|nested] 54+ messages in thread
* Re: [PATCH v3 0/3] introduce new option `rework-with-file` 2026-02-06 15:52 ` [PATCH v3 0/3] introduce new option `rework-with-file` Abraham Samuel Adekunle ` (2 preceding siblings ...) 2026-02-06 15:57 ` [PATCH v3 3/3] add-patch: Allow proper 'git apply' when using the --rework-with-file flag Abraham Samuel Adekunle @ 2026-02-06 19:19 ` Junio C Hamano 2026-02-06 20:40 ` Samuel Abraham 2026-02-13 22:08 ` [PATCH v4 0/4] introduce new option `--auto-advance` Abraham Samuel Adekunle 4 siblings, 1 reply; 54+ messages in thread From: Junio C Hamano @ 2026-02-06 19:19 UTC (permalink / raw) To: Abraham Samuel Adekunle Cc: git, Patrick Steinhardt, Phillip Wood, SZEDER Gábor, Christian Couder, Kristoffer Haugsbakk, Ben Knoble, Karthik Nayak Abraham Samuel Adekunle <abrahamadekunle50@gmail.com> writes: > Abraham Samuel Adekunle (3): > interactive -p: add new `--rework-with-file` flag to interactive > machinery > add-patch: Allow interfile navigation when selecting hunks > add-patch: Allow proper 'git apply' when using the --rework-with-file > flag By the way, this series is conflicting with your other series that has been in 'next' and is ready to graduate. Perhaps it is time to consider rebasing. ^ permalink raw reply [flat|nested] 54+ messages in thread
* Re: [PATCH v3 0/3] introduce new option `rework-with-file` 2026-02-06 19:19 ` [PATCH v3 0/3] introduce new option `rework-with-file` Junio C Hamano @ 2026-02-06 20:40 ` Samuel Abraham 0 siblings, 0 replies; 54+ messages in thread From: Samuel Abraham @ 2026-02-06 20:40 UTC (permalink / raw) To: Junio C Hamano Cc: git, Patrick Steinhardt, Phillip Wood, SZEDER Gábor, Christian Couder, Kristoffer Haugsbakk, Ben Knoble, Karthik Nayak On Fri, Feb 6, 2026 at 8:19 PM Junio C Hamano <gitster@pobox.com> wrote: > > Abraham Samuel Adekunle <abrahamadekunle50@gmail.com> writes: > > > Abraham Samuel Adekunle (3): > > interactive -p: add new `--rework-with-file` flag to interactive > > machinery > > add-patch: Allow interfile navigation when selecting hunks > > add-patch: Allow proper 'git apply' when using the --rework-with-file > > flag > > By the way, this series is conflicting with your other series that > has been in 'next' and is ready to graduate. Perhaps it is time to > consider rebasing. :D Okay thank you very much. Abraham ^ permalink raw reply [flat|nested] 54+ messages in thread
* [PATCH v4 0/4] introduce new option `--auto-advance` 2026-02-06 15:52 ` [PATCH v3 0/3] introduce new option `rework-with-file` Abraham Samuel Adekunle ` (3 preceding siblings ...) 2026-02-06 19:19 ` [PATCH v3 0/3] introduce new option `rework-with-file` Junio C Hamano @ 2026-02-13 22:08 ` Abraham Samuel Adekunle 2026-02-13 22:09 ` [PATCH v4 1/4] interactive -p: add new `--auto-advance` flag Abraham Samuel Adekunle ` (4 more replies) 4 siblings, 5 replies; 54+ messages in thread From: Abraham Samuel Adekunle @ 2026-02-13 22:08 UTC (permalink / raw) To: git Cc: Patrick Steinhardt, Phillip Wood, SZEDER Gábor, Christian Couder, Kristoffer Haugsbakk, Ben Knoble, Junio C Hamano, Karthik Nayak, Lucas Seiki Oshiro, Chandra Pratap Hello, After after more reviews and deliberations, I have been able to rename the new option name to `--auto-advance`, where the --no-auto-advance implements the feature and does not auto advance while --auto-advance is the default and maintains the current behaviour. With the option, users can navigate in between files while deciding on hunks as they wish with the '>' and '<' option for going to the next and previous file respectively if there are more than one file. Patch 1 implements the new `--no-auto-advance` options, Patch 2 modifies the function `patch_update_file()` to instead take the index of the file as parameter instead of the file_diff. Patch 3 moves the 'git apply' logic into a function so that we can reuse this logic when implementing the all or none application of patches. Patch 4 implements the interfile navigation, and adds tests to the interactive test file. Changes in v4: ============== - Renamed option to `--no-auto-advance` with `auto-advance` being the default option - Modified the function signature of 'patch_update_file()' to accept the index of the file diff - Moved git apply logic into function for reuse - Removed the whatnow prompt. Now the hunks in the file keep showing even after all hunks have been decided - Added hunk summary to patch help remainder to show the user the hunk deails - Added tests ot t3701-add-interactive.sh Abraham Samuel Adekunle (4): interactive -p: add new `--auto-advance` flag add-patch: modify patch_update_file() signature add-patch: allow all-or-none application of patches add-patch: allow interfile navigation when selecting hunks add-interactive.c | 4 + add-interactive.h | 5 +- add-patch.c | 157 +++++++++++++++++++++++++++---------- builtin/add.c | 4 + builtin/checkout.c | 7 ++ builtin/reset.c | 4 + builtin/stash.c | 8 ++ t/t3701-add-interactive.sh | 100 +++++++++++++++++++++++ t/t9902-completion.sh | 1 + 9 files changed, 246 insertions(+), 44 deletions(-) -- 2.39.5 (Apple Git-154) ^ permalink raw reply [flat|nested] 54+ messages in thread
* [PATCH v4 1/4] interactive -p: add new `--auto-advance` flag 2026-02-13 22:08 ` [PATCH v4 0/4] introduce new option `--auto-advance` Abraham Samuel Adekunle @ 2026-02-13 22:09 ` Abraham Samuel Adekunle 2026-02-13 23:04 ` Junio C Hamano 2026-02-13 22:10 ` [PATCH v4 2/4] add-patch: modify patch_update_file() signature Abraham Samuel Adekunle ` (3 subsequent siblings) 4 siblings, 1 reply; 54+ messages in thread From: Abraham Samuel Adekunle @ 2026-02-13 22:09 UTC (permalink / raw) To: git Cc: Patrick Steinhardt, Phillip Wood, SZEDER Gábor, Christian Couder, Kristoffer Haugsbakk, Ben Knoble, Junio C Hamano, Karthik Nayak, Lucas Seiki Oshiro, Chandra Pratap 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 <abrahamadekunle50@gmail.com> --- add-interactive.c | 4 ++++ add-interactive.h | 5 +++-- builtin/add.c | 4 ++++ builtin/checkout.c | 7 +++++++ builtin/reset.c | 4 ++++ builtin/stash.c | 8 ++++++++ t/t9902-completion.sh | 1 + 7 files changed, 31 insertions(+), 2 deletions(-) diff --git a/add-interactive.c b/add-interactive.c index 95ec5a89f8..c3a36cd11f 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 = 1; s->use_color_interactive = check_color_config(r, "color.interactive"); @@ -124,6 +125,8 @@ void init_add_i_state(struct add_i_state *s, struct repository *r, die(_("%s cannot be negative"), "--inter-hunk-context"); s->interhunkcontext = add_p_opt->interhunkcontext; } + if (!add_p_opt->auto_advance) + s->auto_advance = 0; } void clear_add_i_state(struct add_i_state *s) @@ -1017,6 +1020,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..cea29a6965 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; @@ -28,7 +29,7 @@ struct add_i_state { int use_single_key; char *interactive_diff_filter, *interactive_diff_algorithm; - int context, interhunkcontext; + int context, interhunkcontext, 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 0ba4f03f2e..fad35a9284 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]; @@ -1803,6 +1806,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) { @@ -2001,6 +2006,8 @@ int cmd_checkout(int argc, OPT_BOOL(0, "guess", &opts.dwim_new_local_branch, N_("second guess 'git checkout <no-such-branch>' (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 c48d9845f8..88f95f9fc7 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 193e3ea47a..f98487f4cd 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 ffb9c8b522..2f9a597ec7 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 -- 2.39.5 (Apple Git-154) ^ permalink raw reply related [flat|nested] 54+ messages in thread
* Re: [PATCH v4 1/4] interactive -p: add new `--auto-advance` flag 2026-02-13 22:09 ` [PATCH v4 1/4] interactive -p: add new `--auto-advance` flag Abraham Samuel Adekunle @ 2026-02-13 23:04 ` Junio C Hamano 2026-02-14 9:16 ` Samuel Abraham 0 siblings, 1 reply; 54+ messages in thread From: Junio C Hamano @ 2026-02-13 23:04 UTC (permalink / raw) To: Abraham Samuel Adekunle Cc: git, Patrick Steinhardt, Phillip Wood, SZEDER Gábor, Christian Couder, Kristoffer Haugsbakk, Ben Knoble, Karthik Nayak, Lucas Seiki Oshiro, Chandra Pratap Abraham Samuel Adekunle <abrahamadekunle50@gmail.com> writes: > 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 <abrahamadekunle50@gmail.com> > --- > add-interactive.c | 4 ++++ > add-interactive.h | 5 +++-- > builtin/add.c | 4 ++++ > builtin/checkout.c | 7 +++++++ > builtin/reset.c | 4 ++++ > builtin/stash.c | 8 ++++++++ > t/t9902-completion.sh | 1 + > 7 files changed, 31 insertions(+), 2 deletions(-) > > diff --git a/add-interactive.c b/add-interactive.c > index 95ec5a89f8..c3a36cd11f 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 = 1; > > s->use_color_interactive = check_color_config(r, "color.interactive"); > > @@ -124,6 +125,8 @@ void init_add_i_state(struct add_i_state *s, struct repository *r, > die(_("%s cannot be negative"), "--inter-hunk-context"); > s->interhunkcontext = add_p_opt->interhunkcontext; > } > + if (!add_p_opt->auto_advance) > + s->auto_advance = 0; > } I am confused. Why do we need above two hunks in this function? Wouldn't it suffice to do s->auto_advance = add_p_opt->auto_advance; in the first hunk, instead of assigning 1 to it? > struct add_i_state { > struct repository *r; > @@ -28,7 +29,7 @@ struct add_i_state { > > int use_single_key; > char *interactive_diff_filter, *interactive_diff_algorithm; > - int context, interhunkcontext; > + int context, interhunkcontext, auto_advance; Please don't do this. The original is already bad to have two members on the same line, but is tolerated as they represent somewhat related concepts. The auto_advance member has nothing to do with these two. ^ permalink raw reply [flat|nested] 54+ messages in thread
* Re: [PATCH v4 1/4] interactive -p: add new `--auto-advance` flag 2026-02-13 23:04 ` Junio C Hamano @ 2026-02-14 9:16 ` Samuel Abraham 0 siblings, 0 replies; 54+ messages in thread From: Samuel Abraham @ 2026-02-14 9:16 UTC (permalink / raw) To: Junio C Hamano Cc: git, Patrick Steinhardt, Phillip Wood, SZEDER Gábor, Christian Couder, Kristoffer Haugsbakk, Ben Knoble, Karthik Nayak, Lucas Seiki Oshiro, Chandra Pratap On Sat, Feb 14, 2026 at 12:04 AM Junio C Hamano <gitster@pobox.com> wrote: > > Abraham Samuel Adekunle <abrahamadekunle50@gmail.com> writes: > > > 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 <abrahamadekunle50@gmail.com> > > --- > > add-interactive.c | 4 ++++ > > add-interactive.h | 5 +++-- > > builtin/add.c | 4 ++++ > > builtin/checkout.c | 7 +++++++ > > builtin/reset.c | 4 ++++ > > builtin/stash.c | 8 ++++++++ > > t/t9902-completion.sh | 1 + > > 7 files changed, 31 insertions(+), 2 deletions(-) > > > > diff --git a/add-interactive.c b/add-interactive.c > > index 95ec5a89f8..c3a36cd11f 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 = 1; > > > > s->use_color_interactive = check_color_config(r, "color.interactive"); > > > > @@ -124,6 +125,8 @@ void init_add_i_state(struct add_i_state *s, struct repository *r, > > die(_("%s cannot be negative"), "--inter-hunk-context"); > > s->interhunkcontext = add_p_opt->interhunkcontext; > > } > > + if (!add_p_opt->auto_advance) > > + s->auto_advance = 0; > > } > > I am confused. Why do we need above two hunks in this function? > Wouldn't it suffice to do > > s->auto_advance = add_p_opt->auto_advance; > > in the first hunk, instead of assigning 1 to it? Yes thank you > > > struct add_i_state { > > struct repository *r; > > @@ -28,7 +29,7 @@ struct add_i_state { > > > > int use_single_key; > > char *interactive_diff_filter, *interactive_diff_algorithm; > > - int context, interhunkcontext; > > + int context, interhunkcontext, auto_advance; > > Please don't do this. > > The original is already bad to have two members on the same line, > but is tolerated as they represent somewhat related concepts. The > auto_advance member has nothing to do with these two. > Okay ^ permalink raw reply [flat|nested] 54+ messages in thread
* [PATCH v4 2/4] add-patch: modify patch_update_file() signature 2026-02-13 22:08 ` [PATCH v4 0/4] introduce new option `--auto-advance` Abraham Samuel Adekunle 2026-02-13 22:09 ` [PATCH v4 1/4] interactive -p: add new `--auto-advance` flag Abraham Samuel Adekunle @ 2026-02-13 22:10 ` Abraham Samuel Adekunle 2026-02-13 23:33 ` Junio C Hamano 2026-02-13 22:11 ` [PATCH v4 3/4] add-patch: allow all-or-none application of patches Abraham Samuel Adekunle ` (2 subsequent siblings) 4 siblings, 1 reply; 54+ messages in thread From: Abraham Samuel Adekunle @ 2026-02-13 22:10 UTC (permalink / raw) To: git Cc: Patrick Steinhardt, Phillip Wood, SZEDER Gábor, Christian Couder, Kristoffer Haugsbakk, Ben Knoble, Junio C Hamano, Karthik Nayak, Lucas Seiki Oshiro, Chandra Pratap 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 <abrahamadekunle50@gmail.com> --- add-patch.c | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/add-patch.c b/add-patch.c index df8f2e6d74..673ea659ff 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 ssize_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; + ssize_t patch_update_resp = (ssize_t)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); @@ -1499,9 +1500,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) { @@ -1577,7 +1579,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 = -1; break; } @@ -1623,7 +1625,7 @@ static int patch_update_file(struct add_p_state *s, hunk->use = SKIP_HUNK; } } else if (ch == 'q') { - quit = 1; + patch_update_resp = -1; break; } else if (s->answer.buf[0] == 'K') { if (permitted & ALLOW_GOTO_PREVIOUS_HUNK) @@ -1810,7 +1812,7 @@ static int patch_update_file(struct add_p_state *s, } putchar('\n'); - return quit; + return patch_update_resp; } int run_add_p(struct repository *r, enum add_p_mode mode, @@ -1821,6 +1823,7 @@ int run_add_p(struct repository *r, enum add_p_mode mode, { r }, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT }; size_t i, binary_count = 0; + ssize_t patch_update_resp; init_add_i_state(&s.s, r, o); @@ -1859,11 +1862,17 @@ 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; + } + patch_update_resp = patch_update_file(&s, i); + if (patch_update_resp < 0) break; + i = (size_t)patch_update_resp; + } if (s.file_diff_nr == 0) err(&s, _("No changes.")); -- 2.39.5 (Apple Git-154) ^ permalink raw reply related [flat|nested] 54+ messages in thread
* Re: [PATCH v4 2/4] add-patch: modify patch_update_file() signature 2026-02-13 22:10 ` [PATCH v4 2/4] add-patch: modify patch_update_file() signature Abraham Samuel Adekunle @ 2026-02-13 23:33 ` Junio C Hamano 2026-02-14 10:14 ` Samuel Abraham 0 siblings, 1 reply; 54+ messages in thread From: Junio C Hamano @ 2026-02-13 23:33 UTC (permalink / raw) To: Abraham Samuel Adekunle Cc: git, Patrick Steinhardt, Phillip Wood, SZEDER Gábor, Christian Couder, Kristoffer Haugsbakk, Ben Knoble, Karthik Nayak, Lucas Seiki Oshiro, Chandra Pratap Abraham Samuel Adekunle <abrahamadekunle50@gmail.com> writes: > -static int patch_update_file(struct add_p_state *s, > - struct file_diff *file_diff) > +static ssize_t patch_update_file(struct add_p_state *s, size_t idx) Why ssize_t? Are we going to handle that many hunks that we do not expect to fit in a platform natural "int" type? If we are not doing anything about "idx" being more than half the type, which apparently is the case ... > { > 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; > + ssize_t patch_update_resp = (ssize_t)idx; ... with the cast that is not checked here, wouldn't it make sense to just use the platform natural "int" everywhere? Your code is not "safe" either way. I do not think we expect to handle 2 billion hunks, so even on 32-bit platforms, platform natural "int" should be plenty. Instead of religiously using size_t and ssize_t to count things without extra care, I'd rather see us check the error condition for real, if that is what we really care about (and that can still be done while leaving the codebase cleaner by sticking to the platform natural "int"). Enough ranting. Anyway. If we really are bothered that we cannot handle 3 billion hunks, we could avoid losing half the number range by returning s->file_diff.file_diff_nr (which is one more than there are elements in s->file_diff[] array) or ((size_t)-1). That would allow us to return size_t from here. I care about this a bit more than "why use size_t when int is perfectly fine", because some platforms that are not quite POSIX can have ssize_t that is not as wide as size_t. ^ permalink raw reply [flat|nested] 54+ messages in thread
* Re: [PATCH v4 2/4] add-patch: modify patch_update_file() signature 2026-02-13 23:33 ` Junio C Hamano @ 2026-02-14 10:14 ` Samuel Abraham 0 siblings, 0 replies; 54+ messages in thread From: Samuel Abraham @ 2026-02-14 10:14 UTC (permalink / raw) To: Junio C Hamano Cc: git, Patrick Steinhardt, Phillip Wood, SZEDER Gábor, Christian Couder, Kristoffer Haugsbakk, Ben Knoble, Karthik Nayak, Lucas Seiki Oshiro, Chandra Pratap On Sat, Feb 14, 2026 at 12:34 AM Junio C Hamano <gitster@pobox.com> wrote: > > Abraham Samuel Adekunle <abrahamadekunle50@gmail.com> writes: > > > -static int patch_update_file(struct add_p_state *s, > > - struct file_diff *file_diff) > > +static ssize_t patch_update_file(struct add_p_state *s, size_t idx) > > Why ssize_t? Are we going to handle that many hunks that we do not > expect to fit in a platform natural "int" type? If we are not doing > anything about "idx" being more than half the type, which apparently > is the case ... > > > { > > 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; > > + ssize_t patch_update_resp = (ssize_t)idx; > > ... with the cast that is not checked here, wouldn't it make sense > to just use the platform natural "int" everywhere? Your code is not > "safe" either way. I do not think we expect to handle 2 billion > hunks, so even on 32-bit platforms, platform natural "int" should be > plenty. Instead of religiously using size_t and ssize_t to count > things without extra care, I'd rather see us check the error > condition for real, if that is what we really care about (and that > can still be done while leaving the codebase cleaner by sticking to > the platform natural "int"). > > Enough ranting. Anyway. > > If we really are bothered that we cannot handle 3 billion hunks, we > could avoid losing half the number range by returning > s->file_diff.file_diff_nr (which is one more than there are elements > in s->file_diff[] array) or ((size_t)-1). That would allow us to > return size_t from here. I care about this a bit more than "why use > size_t when int is perfectly fine", because some platforms that are > not quite POSIX can have ssize_t that is not as wide as size_t. Hello Junio. Thank you for the review. I wanted to be able to return a negative value while also making sure to return a type of the same size as "i" since that is what we use to update the caller for the next or previous "i". But I know better now to think about platforms that are not quite POSIX. I will return the s->file_diff_nr and check for that instead. Thanks Abraham ^ permalink raw reply [flat|nested] 54+ messages in thread
* [PATCH v4 3/4] add-patch: allow all-or-none application of patches 2026-02-13 22:08 ` [PATCH v4 0/4] introduce new option `--auto-advance` Abraham Samuel Adekunle 2026-02-13 22:09 ` [PATCH v4 1/4] interactive -p: add new `--auto-advance` flag Abraham Samuel Adekunle 2026-02-13 22:10 ` [PATCH v4 2/4] add-patch: modify patch_update_file() signature Abraham Samuel Adekunle @ 2026-02-13 22:11 ` Abraham Samuel Adekunle 2026-02-13 22:12 ` [PATCH v4 4/4] add-patch: allow interfile navigation when selecting hunks Abraham Samuel Adekunle 2026-02-14 11:01 ` [PATCH v5 0/4] introduce new option `--auto-advance` Abraham Samuel Adekunle 4 siblings, 0 replies; 54+ messages in thread From: Abraham Samuel Adekunle @ 2026-02-13 22:11 UTC (permalink / raw) To: git Cc: Patrick Steinhardt, Phillip Wood, SZEDER Gábor, Christian Couder, Kristoffer Haugsbakk, Ben Knoble, Junio C Hamano, Karthik Nayak, Lucas Seiki Oshiro, Chandra Pratap 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 <abrahamadekunle50@gmail.com> --- add-patch.c | 62 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 35 insertions(+), 27 deletions(-) diff --git a/add-patch.c b/add-patch.c index 673ea659ff..7d4f17e432 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 ssize_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; @@ -1784,32 +1817,7 @@ static ssize_t patch_update_file(struct add_p_state *s, size_t idx) } } - /* 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; -- 2.39.5 (Apple Git-154) ^ permalink raw reply related [flat|nested] 54+ messages in thread
* [PATCH v4 4/4] add-patch: allow interfile navigation when selecting hunks 2026-02-13 22:08 ` [PATCH v4 0/4] introduce new option `--auto-advance` Abraham Samuel Adekunle ` (2 preceding siblings ...) 2026-02-13 22:11 ` [PATCH v4 3/4] add-patch: allow all-or-none application of patches Abraham Samuel Adekunle @ 2026-02-13 22:12 ` Abraham Samuel Adekunle 2026-02-14 11:01 ` [PATCH v5 0/4] introduce new option `--auto-advance` Abraham Samuel Adekunle 4 siblings, 0 replies; 54+ messages in thread From: Abraham Samuel Adekunle @ 2026-02-13 22:12 UTC (permalink / raw) To: git Cc: Patrick Steinhardt, Phillip Wood, SZEDER Gábor, Christian Couder, Kristoffer Haugsbakk, Ben Knoble, Junio C Hamano, Karthik Nayak, Lucas Seiki Oshiro, Chandra Pratap 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 <abrahamadekunle50@gmail.com> --- 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 7d4f17e432..6b9ae4da30 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 ssize_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; ssize_t patch_update_resp = (ssize_t)idx; @@ -1502,7 +1506,9 @@ static ssize_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) @@ -1534,8 +1540,12 @@ static ssize_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) { @@ -1584,6 +1594,14 @@ static ssize_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) @@ -1660,6 +1678,28 @@ static ssize_t patch_update_file(struct add_p_state *s, size_t idx) } else if (ch == 'q') { patch_update_resp = -1; 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, @@ -1805,6 +1845,18 @@ static ssize_t patch_update_file(struct add_p_state *s, size_t idx) * 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; @@ -1817,7 +1869,8 @@ static ssize_t patch_update_file(struct add_p_state *s, size_t idx) } } - apply_patch(s, file_diff); + if (s->s.auto_advance) + apply_patch(s, file_diff); putchar('\n'); return patch_update_resp; @@ -1881,6 +1934,9 @@ int run_add_p(struct repository *r, enum add_p_mode mode, break; i = (size_t)patch_update_resp; } + 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 5ce9c6dd60..6e120a4001 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 -- 2.39.5 (Apple Git-154) ^ permalink raw reply related [flat|nested] 54+ messages in thread
* [PATCH v5 0/4] introduce new option `--auto-advance` 2026-02-13 22:08 ` [PATCH v4 0/4] introduce new option `--auto-advance` Abraham Samuel Adekunle ` (3 preceding siblings ...) 2026-02-13 22:12 ` [PATCH v4 4/4] add-patch: allow interfile navigation when selecting hunks Abraham Samuel Adekunle @ 2026-02-14 11:01 ` Abraham Samuel Adekunle 2026-02-14 11:03 ` [PATCH v5 1/4] interactive -p: add new `--auto-advance` flag Abraham Samuel Adekunle ` (4 more replies) 4 siblings, 5 replies; 54+ messages in thread From: Abraham Samuel Adekunle @ 2026-02-14 11:01 UTC (permalink / raw) To: git Cc: Patrick Steinhardt, Phillip Wood, SZEDER Gábor, Christian Couder, Kristoffer Haugsbakk, Ben Knoble, Junio C Hamano, Karthik Nayak, Lucas Seiki Oshiro, Chandra Pratap Hello, After after more reviews and deliberations, I have been able to rename the new option name to `--auto-advance`, where the --no-auto-advance implements the feature and does not auto advance while --auto-advance is the default and maintains the current behaviour. With the option, users can navigate in between files while deciding on hunks as they wish with the '>' and '<' option for going to the next and previous file respectively if there are more than one file. Patch 1 implements the new `--no-auto-advance` options, Patch 2 modifies the function `patch_update_file()` to instead take the index of the file as parameter instead of the file_diff. Patch 3 moves the 'git apply' logic into a function so that we can reuse this logic when implementing the all or none application of patches. Patch 4 implements the interfile navigation, and adds tests to the interactive test file. Changes in v5: ============== - Moved 'auto_advance' member in struct add_i_state to its own line - Removed redundant lodic to set s->auto-advance in init_add_i_state() - Modified patch_update_file() to return size_t - Modified the logic which checks when to quit in run_add_p() to check for s->file_diff_nr instead of a negative value. Abraham Samuel Adekunle (4): interactive -p: add new `--auto-advance` flag add-patch: modify patch_update_file() signature add-patch: allow all-or-none application of patches add-patch: allow interfile navigation when selecting hunks add-interactive.c | 2 + add-interactive.h | 4 +- add-patch.c | 154 +++++++++++++++++++++++++++---------- builtin/add.c | 4 + builtin/checkout.c | 7 ++ builtin/reset.c | 4 + builtin/stash.c | 8 ++ t/t3701-add-interactive.sh | 100 ++++++++++++++++++++++++ t/t9902-completion.sh | 1 + 9 files changed, 241 insertions(+), 43 deletions(-) -- 2.39.5 (Apple Git-154) ^ permalink raw reply [flat|nested] 54+ messages in thread
* [PATCH v5 1/4] interactive -p: add new `--auto-advance` flag 2026-02-14 11:01 ` [PATCH v5 0/4] introduce new option `--auto-advance` Abraham Samuel Adekunle @ 2026-02-14 11:03 ` Abraham Samuel Adekunle 2026-02-14 11:04 ` [PATCH v5 2/4] add-patch: modify patch_update_file() signature Abraham Samuel Adekunle ` (3 subsequent siblings) 4 siblings, 0 replies; 54+ messages in thread From: Abraham Samuel Adekunle @ 2026-02-14 11:03 UTC (permalink / raw) To: git Cc: Patrick Steinhardt, Phillip Wood, SZEDER Gábor, Christian Couder, Kristoffer Haugsbakk, Ben Knoble, Junio C Hamano, Karthik Nayak, Lucas Seiki Oshiro, Chandra Pratap 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 <abrahamadekunle50@gmail.com> --- 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 95ec5a89f8..1580639682 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 0ba4f03f2e..fad35a9284 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]; @@ -1803,6 +1806,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) { @@ -2001,6 +2006,8 @@ int cmd_checkout(int argc, OPT_BOOL(0, "guess", &opts.dwim_new_local_branch, N_("second guess 'git checkout <no-such-branch>' (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 c48d9845f8..88f95f9fc7 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 193e3ea47a..f98487f4cd 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 ffb9c8b522..2f9a597ec7 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 -- 2.39.5 (Apple Git-154) ^ permalink raw reply related [flat|nested] 54+ messages in thread
* [PATCH v5 2/4] add-patch: modify patch_update_file() signature 2026-02-14 11:01 ` [PATCH v5 0/4] introduce new option `--auto-advance` Abraham Samuel Adekunle 2026-02-14 11:03 ` [PATCH v5 1/4] interactive -p: add new `--auto-advance` flag Abraham Samuel Adekunle @ 2026-02-14 11:04 ` Abraham Samuel Adekunle 2026-02-14 11:06 ` [PATCH v5 3/4] add-patch: allow all-or-none application of patches Abraham Samuel Adekunle ` (2 subsequent siblings) 4 siblings, 0 replies; 54+ messages in thread From: Abraham Samuel Adekunle @ 2026-02-14 11:04 UTC (permalink / raw) To: git Cc: Patrick Steinhardt, Phillip Wood, SZEDER Gábor, Christian Couder, Kristoffer Haugsbakk, Ben Knoble, Junio C Hamano, Karthik Nayak, Lucas Seiki Oshiro, Chandra Pratap 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 <abrahamadekunle50@gmail.com> --- add-patch.c | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/add-patch.c b/add-patch.c index df8f2e6d74..8e21ea1246 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); @@ -1499,9 +1500,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) { @@ -1577,7 +1579,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; } @@ -1623,7 +1625,7 @@ static int patch_update_file(struct add_p_state *s, 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) @@ -1810,7 +1812,7 @@ static int patch_update_file(struct add_p_state *s, } putchar('\n'); - return quit; + return patch_update_resp; } int run_add_p(struct repository *r, enum add_p_mode mode, @@ -1859,11 +1861,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.")); -- 2.39.5 (Apple Git-154) ^ permalink raw reply related [flat|nested] 54+ messages in thread
* [PATCH v5 3/4] add-patch: allow all-or-none application of patches 2026-02-14 11:01 ` [PATCH v5 0/4] introduce new option `--auto-advance` Abraham Samuel Adekunle 2026-02-14 11:03 ` [PATCH v5 1/4] interactive -p: add new `--auto-advance` flag Abraham Samuel Adekunle 2026-02-14 11:04 ` [PATCH v5 2/4] add-patch: modify patch_update_file() signature Abraham Samuel Adekunle @ 2026-02-14 11:06 ` Abraham Samuel Adekunle 2026-02-14 11:06 ` [PATCH v5 4/4] add-patch: allow interfile navigation when selecting hunks Abraham Samuel Adekunle 2026-02-20 22:32 ` [PATCH v5 0/4] introduce new option `--auto-advance` Junio C Hamano 4 siblings, 0 replies; 54+ messages in thread From: Abraham Samuel Adekunle @ 2026-02-14 11:06 UTC (permalink / raw) To: git Cc: Patrick Steinhardt, Phillip Wood, SZEDER Gábor, Christian Couder, Kristoffer Haugsbakk, Ben Knoble, Junio C Hamano, Karthik Nayak, Lucas Seiki Oshiro, Chandra Pratap 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 <abrahamadekunle50@gmail.com> --- add-patch.c | 62 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 35 insertions(+), 27 deletions(-) diff --git a/add-patch.c b/add-patch.c index 8e21ea1246..07526e7fb6 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; @@ -1784,32 +1817,7 @@ static size_t patch_update_file(struct add_p_state *s, size_t idx) } } - /* 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; -- 2.39.5 (Apple Git-154) ^ permalink raw reply related [flat|nested] 54+ messages in thread
* [PATCH v5 4/4] add-patch: allow interfile navigation when selecting hunks 2026-02-14 11:01 ` [PATCH v5 0/4] introduce new option `--auto-advance` Abraham Samuel Adekunle ` (2 preceding siblings ...) 2026-02-14 11:06 ` [PATCH v5 3/4] add-patch: allow all-or-none application of patches Abraham Samuel Adekunle @ 2026-02-14 11:06 ` Abraham Samuel Adekunle 2026-02-20 22:32 ` [PATCH v5 0/4] introduce new option `--auto-advance` Junio C Hamano 4 siblings, 0 replies; 54+ messages in thread From: Abraham Samuel Adekunle @ 2026-02-14 11:06 UTC (permalink / raw) To: git Cc: Patrick Steinhardt, Phillip Wood, SZEDER Gábor, Christian Couder, Kristoffer Haugsbakk, Ben Knoble, Junio C Hamano, Karthik Nayak, Lucas Seiki Oshiro, Chandra Pratap 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 <abrahamadekunle50@gmail.com> --- 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 07526e7fb6..b3fb08f416 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; @@ -1502,7 +1506,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) @@ -1534,8 +1540,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) { @@ -1584,6 +1594,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) @@ -1660,6 +1678,28 @@ static size_t patch_update_file(struct add_p_state *s, size_t idx) } 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, @@ -1805,6 +1845,18 @@ static size_t patch_update_file(struct add_p_state *s, size_t idx) * 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; @@ -1817,7 +1869,8 @@ static size_t patch_update_file(struct add_p_state *s, size_t idx) } } - apply_patch(s, file_diff); + if (s->s.auto_advance) + apply_patch(s, file_diff); putchar('\n'); return patch_update_resp; @@ -1878,6 +1931,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 5ce9c6dd60..6e120a4001 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 -- 2.39.5 (Apple Git-154) ^ permalink raw reply related [flat|nested] 54+ messages in thread
* Re: [PATCH v5 0/4] introduce new option `--auto-advance` 2026-02-14 11:01 ` [PATCH v5 0/4] introduce new option `--auto-advance` Abraham Samuel Adekunle ` (3 preceding siblings ...) 2026-02-14 11:06 ` [PATCH v5 4/4] add-patch: allow interfile navigation when selecting hunks Abraham Samuel Adekunle @ 2026-02-20 22:32 ` Junio C Hamano 2026-02-21 9:06 ` Samuel Abraham 4 siblings, 1 reply; 54+ messages in thread From: Junio C Hamano @ 2026-02-20 22:32 UTC (permalink / raw) To: Abraham Samuel Adekunle Cc: git, Patrick Steinhardt, Phillip Wood, SZEDER Gábor, Christian Couder, Kristoffer Haugsbakk, Ben Knoble, Karthik Nayak, Lucas Seiki Oshiro, Chandra Pratap Abraham Samuel Adekunle <abrahamadekunle50@gmail.com> writes: > After after more reviews and deliberations, I have been able to > rename the new option name to `--auto-advance`, where the > --no-auto-advance implements the feature and does not auto advance > while --auto-advance is the default and maintains the current > behaviour. Haven't seen any reviews on this latest round, and I think what we have here may be good enough. Shall we merge it down to 'next'? Any comments? Thanks. ^ permalink raw reply [flat|nested] 54+ messages in thread
* Re: [PATCH v5 0/4] introduce new option `--auto-advance` 2026-02-20 22:32 ` [PATCH v5 0/4] introduce new option `--auto-advance` Junio C Hamano @ 2026-02-21 9:06 ` Samuel Abraham 0 siblings, 0 replies; 54+ messages in thread From: Samuel Abraham @ 2026-02-21 9:06 UTC (permalink / raw) To: Junio C Hamano Cc: git, Patrick Steinhardt, Phillip Wood, SZEDER Gábor, Christian Couder, Kristoffer Haugsbakk, Ben Knoble, Karthik Nayak, Lucas Seiki Oshiro, Chandra Pratap On Fri, Feb 20, 2026 at 11:32 PM Junio C Hamano <gitster@pobox.com> wrote: > > Abraham Samuel Adekunle <abrahamadekunle50@gmail.com> writes: > > > After after more reviews and deliberations, I have been able to > > rename the new option name to `--auto-advance`, where the > > --no-auto-advance implements the feature and does not auto advance > > while --auto-advance is the default and maintains the current > > behaviour. > > Haven't seen any reviews on this latest round, and I think what we > have here may be good enough. Shall we merge it down to 'next'? > Any comments? > Okay. Thank you Junio, Phillip and everyone for the patience and guidance in completing this patch series and also the one for my GSoC microproject. It has been a great learning process. I appreciate it. Thanks Abraham ^ permalink raw reply [flat|nested] 54+ messages in thread
end of thread, other threads:[~2026-02-21 9:06 UTC | newest] Thread overview: 54+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-01-23 11:56 [RFC PATCH 0/1] add-patch: Allow reworking with a file after deciding on its hunks Abraham Samuel Adekunle 2026-01-23 11:58 ` [RFC PATCH 1/1] add-patch: Allow reworking with a file after deciding on all " Abraham Samuel Adekunle 2026-01-23 16:38 ` Junio C Hamano 2026-01-23 21:43 ` Samuel Abraham 2026-01-27 15:43 ` [PATCH v2 0/1] Allow reworking with a file when making hunk decisions Abraham Samuel Adekunle 2026-01-27 15:45 ` [PATCH v2 1/1] Allow reworking with a file after deciding on all its hunks Abraham Samuel Adekunle 2026-01-27 20:48 ` Junio C Hamano 2026-01-28 11:26 ` Samuel Abraham 2026-01-30 9:22 ` Samuel Abraham 2026-01-30 16:29 ` Junio C Hamano 2026-01-30 17:36 ` Samuel Abraham 2026-01-31 19:25 ` Junio C Hamano 2026-02-02 11:14 ` Samuel Abraham 2026-02-02 17:26 ` Junio C Hamano 2026-02-03 9:55 ` Samuel Abraham 2026-01-27 17:04 ` [PATCH v2 0/1] Allow reworking with a file when making hunk decisions Junio C Hamano 2026-01-28 9:49 ` Samuel Abraham 2026-02-06 15:52 ` [PATCH v3 0/3] introduce new option `rework-with-file` Abraham Samuel Adekunle 2026-02-06 15:54 ` [PATCH v3 1/3] interactive -p: add new `--rework-with-file` flag to interactive machinery Abraham Samuel Adekunle 2026-02-06 18:25 ` Junio C Hamano 2026-02-06 20:21 ` Samuel Abraham 2026-02-06 15:56 ` [PATCH v3 2/3] add-patch: Allow interfile navigation when selecting hunks Abraham Samuel Adekunle 2026-02-06 18:35 ` Junio C Hamano 2026-02-06 20:22 ` Samuel Abraham 2026-02-06 18:54 ` Junio C Hamano 2026-02-06 20:32 ` Samuel Abraham 2026-02-06 19:21 ` Junio C Hamano 2026-02-06 20:37 ` Samuel Abraham 2026-02-12 10:32 ` Samuel Abraham 2026-02-12 17:25 ` Junio C Hamano 2026-02-12 21:13 ` Samuel Abraham 2026-02-12 21:31 ` Junio C Hamano 2026-02-12 22:20 ` Samuel Abraham 2026-02-06 15:57 ` [PATCH v3 3/3] add-patch: Allow proper 'git apply' when using the --rework-with-file flag Abraham Samuel Adekunle 2026-02-06 19:02 ` Junio C Hamano 2026-02-06 20:39 ` Samuel Abraham 2026-02-06 19:19 ` [PATCH v3 0/3] introduce new option `rework-with-file` Junio C Hamano 2026-02-06 20:40 ` Samuel Abraham 2026-02-13 22:08 ` [PATCH v4 0/4] introduce new option `--auto-advance` Abraham Samuel Adekunle 2026-02-13 22:09 ` [PATCH v4 1/4] interactive -p: add new `--auto-advance` flag Abraham Samuel Adekunle 2026-02-13 23:04 ` Junio C Hamano 2026-02-14 9:16 ` Samuel Abraham 2026-02-13 22:10 ` [PATCH v4 2/4] add-patch: modify patch_update_file() signature Abraham Samuel Adekunle 2026-02-13 23:33 ` Junio C Hamano 2026-02-14 10:14 ` Samuel Abraham 2026-02-13 22:11 ` [PATCH v4 3/4] add-patch: allow all-or-none application of patches Abraham Samuel Adekunle 2026-02-13 22:12 ` [PATCH v4 4/4] add-patch: allow interfile navigation when selecting hunks Abraham Samuel Adekunle 2026-02-14 11:01 ` [PATCH v5 0/4] introduce new option `--auto-advance` Abraham Samuel Adekunle 2026-02-14 11:03 ` [PATCH v5 1/4] interactive -p: add new `--auto-advance` flag Abraham Samuel Adekunle 2026-02-14 11:04 ` [PATCH v5 2/4] add-patch: modify patch_update_file() signature Abraham Samuel Adekunle 2026-02-14 11:06 ` [PATCH v5 3/4] add-patch: allow all-or-none application of patches Abraham Samuel Adekunle 2026-02-14 11:06 ` [PATCH v5 4/4] add-patch: allow interfile navigation when selecting hunks Abraham Samuel Adekunle 2026-02-20 22:32 ` [PATCH v5 0/4] introduce new option `--auto-advance` Junio C Hamano 2026-02-21 9:06 ` Samuel Abraham
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox