* Broken handling of "J" hunks for "add --interactive"?
@ 2025-10-02 9:23 Windl, Ulrich
2025-10-03 12:16 ` [PATCH] add-patch: roll over to next undecided hunk René Scharfe
` (2 more replies)
0 siblings, 3 replies; 37+ messages in thread
From: Windl, Ulrich @ 2025-10-02 9:23 UTC (permalink / raw)
To: git@vger.kernel.org
Thank you for filling out a Git bug report!
Please answer the following questions to help us understand your issue.
What did you do before the bug happened? (Steps to reproduce your issue)
git add --interactive
answer some "y", some "n", one "J"
What did you expect to happen? (Expected behavior)
git will ask at end for exactly the one "J" hunk
What happened instead? (Actual behavior)
git asked about the hunk rejected before the "J" hunk also
(asked for two hunks instead of one)
What's different between what you expected and what actually happened?
I did not observer that in an older version of git (like 2.26.2)
Anything else you want to add:
Please review the rest of the bug report below.
You can delete any lines you don't wish to share.
[System Info]
git version:
git version 2.51.0
cpu: x86_64
no commit associated with this build
sizeof-long: 8
sizeof-size_t: 8
shell-path: /bin/sh
libcurl: 8.6.0
OpenSSL: OpenSSL 3.1.4 24 Oct 2023
zlib: 1.2.13
SHA-1: SHA1_DC
SHA-256: SHA256_BLK
default-ref-format: files
default-hash: sha1
uname: Linux 6.4.0-150600.23.65-default #1 SMP PREEMPT_DYNAMIC Tue Aug 12 00:37:41 UTC 2025 (aedcb04) x86_64
compiler info: gnuc: 7.5
libc info: glibc: 2.38
$SHELL (typically, interactive shell): /bin/bash
[Enabled Hooks]
^ permalink raw reply [flat|nested] 37+ messages in thread* [PATCH] add-patch: roll over to next undecided hunk 2025-10-02 9:23 Broken handling of "J" hunks for "add --interactive"? Windl, Ulrich @ 2025-10-03 12:16 ` René Scharfe 2025-10-03 13:41 ` Phillip Wood ` (2 more replies) 2025-10-05 15:45 ` [PATCH v2 0/5] " René Scharfe 2025-10-06 17:18 ` [PATCH v3 0/6] add-patch: roll over to next undecided hunk René Scharfe 2 siblings, 3 replies; 37+ messages in thread From: René Scharfe @ 2025-10-03 12:16 UTC (permalink / raw) To: Windl, Ulrich, git@vger.kernel.org; +Cc: Junio C Hamano git add --patch presents diff hunks one after the other, asking whether to add them. If we mark some as undecided, e.g. with J, then it will start over after reaching the last hunk. It always starts over at the very first hunk, though, even if we already decided on it. Skip decided hunks when rolling over instead. Reported-by: Windl, Ulrich <u.windl@ukr.de> Signed-off-by: René Scharfe <l.s.r@web.de> --- add-patch.c | 9 ++++++++- t/t3701-add-interactive.sh | 20 ++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/add-patch.c b/add-patch.c index b0389c5d5b..42a8394c92 100644 --- a/add-patch.c +++ b/add-patch.c @@ -1436,8 +1436,15 @@ static int patch_update_file(struct add_p_state *s, render_diff_header(s, file_diff, colored, &s->buf); fputs(s->buf.buf, stdout); for (;;) { - if (hunk_index >= file_diff->hunk_nr) + if (hunk_index >= file_diff->hunk_nr) { hunk_index = 0; + for (i = 0; i < file_diff->hunk_nr; i++) { + if (file_diff->hunk[i].use == UNDECIDED_HUNK) { + hunk_index = i; + break; + } + } + } hunk = file_diff->hunk_nr ? file_diff->hunk + hunk_index : &file_diff->head; diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh index d9fe289a7a..fa6ec5f835 100755 --- a/t/t3701-add-interactive.sh +++ b/t/t3701-add-interactive.sh @@ -1321,6 +1321,26 @@ test_expect_success 'stash accepts -U and --inter-hunk-context' ' test_grep "@@ -2,20 +2,20 @@" actual ' +test_expect_success 'roll over to next undecided (1)' ' + test_write_lines a b c d e f g h i j k l m n o p q >file && + git add file && + test_write_lines X b c d e f g h X j k l m n o p X >file && + test_write_lines J y y q | git add -p >actual && + test_write_lines 1 2 3 1 >expect && + sed -ne "s-/.*--" -e "s-^(--p" <actual >hunks && + test_cmp expect hunks +' + +test_expect_success 'roll over to next undecided (2)' ' + test_write_lines a b c d e f g h i j k l m n o p q >file && + git add file && + test_write_lines X b c d e f g h X j k l m n o p X >file && + test_write_lines y J y q | git add -p >actual && + test_write_lines 1 2 3 2 >expect && + sed -ne "s-/.*--" -e "s-^(--p" <actual >hunks && + test_cmp expect hunks +' + test_expect_success 'set up base for -p color tests' ' echo commit >file && git commit -am "commit state" && -- 2.51.0 ^ permalink raw reply related [flat|nested] 37+ messages in thread
* Re: [PATCH] add-patch: roll over to next undecided hunk 2025-10-03 12:16 ` [PATCH] add-patch: roll over to next undecided hunk René Scharfe @ 2025-10-03 13:41 ` Phillip Wood 2025-10-03 14:10 ` René Scharfe 2025-10-03 16:11 ` Junio C Hamano 2025-10-03 21:18 ` Junio C Hamano 2 siblings, 1 reply; 37+ messages in thread From: Phillip Wood @ 2025-10-03 13:41 UTC (permalink / raw) To: René Scharfe, Windl, Ulrich, git@vger.kernel.org; +Cc: Junio C Hamano Hi René On 03/10/2025 13:16, René Scharfe wrote: > git add --patch presents diff hunks one after the other, asking whether > to add them. If we mark some as undecided, e.g. with J, then it will > start over after reaching the last hunk. It always starts over at the > very first hunk, though, even if we already decided on it. Skip > decided hunks when rolling over instead. Nice > @@ -1436,8 +1436,15 @@ static int patch_update_file(struct add_p_state *s, > render_diff_header(s, file_diff, colored, &s->buf); > fputs(s->buf.buf, stdout); > for (;;) { > - if (hunk_index >= file_diff->hunk_nr) > + if (hunk_index >= file_diff->hunk_nr) { > hunk_index = 0; > + for (i = 0; i < file_diff->hunk_nr; i++) { > + if (file_diff->hunk[i].use == UNDECIDED_HUNK) { > + hunk_index = i; > + break; > + } > + } > + } > hunk = file_diff->hunk_nr > ? file_diff->hunk + hunk_index If there were no undecided hunks then this will be out of bounds because hunk_index >= file_diff->hunk_nr. Are we absolutely certain that we cannot reach this point without at least one hunk being undecided? > +test_expect_success 'roll over to next undecided (1)' ' > + test_write_lines a b c d e f g h i j k l m n o p q >file && > + git add file && > + test_write_lines X b c d e f g h X j k l m n o p X >file && > + test_write_lines J y y q | git add -p >actual && > + test_write_lines 1 2 3 1 >expect && > + sed -ne "s-/.*--" -e "s-^(--p" <actual >hunks && > + test_cmp expect hunks > +' I'm not sure what this first test adds, the one below checks that we find the first undecided hunk which seems to be the important thing to check. Thanks Phillip > +test_expect_success 'roll over to next undecided (2)' ' > + test_write_lines a b c d e f g h i j k l m n o p q >file && > + git add file && > + test_write_lines X b c d e f g h X j k l m n o p X >file && > + test_write_lines y J y q | git add -p >actual && > + test_write_lines 1 2 3 2 >expect && > + sed -ne "s-/.*--" -e "s-^(--p" <actual >hunks && > + test_cmp expect hunks > +' > + > test_expect_success 'set up base for -p color tests' ' > echo commit >file && > git commit -am "commit state" && ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH] add-patch: roll over to next undecided hunk 2025-10-03 13:41 ` Phillip Wood @ 2025-10-03 14:10 ` René Scharfe 2025-10-08 13:47 ` Phillip Wood 0 siblings, 1 reply; 37+ messages in thread From: René Scharfe @ 2025-10-03 14:10 UTC (permalink / raw) To: phillip.wood, Windl, Ulrich, git@vger.kernel.org; +Cc: Junio C Hamano On 10/3/25 3:41 PM, Phillip Wood wrote: > >> @@ -1436,8 +1436,15 @@ static int patch_update_file(struct add_p_state *s, >> render_diff_header(s, file_diff, colored, &s->buf); >> fputs(s->buf.buf, stdout); >> for (;;) { >> - if (hunk_index >= file_diff->hunk_nr) >> + if (hunk_index >= file_diff->hunk_nr) { >> hunk_index = 0; >> + for (i = 0; i < file_diff->hunk_nr; i++) { >> + if (file_diff->hunk[i].use == UNDECIDED_HUNK) { >> + hunk_index = i; >> + break; >> + } >> + } >> + } >> hunk = file_diff->hunk_nr >> ? file_diff->hunk + hunk_index > > If there were no undecided hunks then this will be out of bounds > because hunk_index >= file_diff->hunk_nr. Are we absolutely certain > that we cannot reach this point without at least one hunk being > undecided? The new loop only sets hunk_index if i < file_diff->hunk_nr. If it finds no undecided hunk then it does nothing. >> +test_expect_success 'roll over to next undecided (1)' ' >> + test_write_lines a b c d e f g h i j k l m n o p q >file && >> + git add file && >> + test_write_lines X b c d e f g h X j k l m n o p X >file && >> + test_write_lines J y y q | git add -p >actual && >> + test_write_lines 1 2 3 1 >expect && >> + sed -ne "s-/.*--" -e "s-^(--p" <actual >hunks && >> + test_cmp expect hunks >> +' > > I'm not sure what this first test adds, the one below checks that we > find the first undecided hunk which seems to be the important thing > to check. It's a regression test for the case that the original code got right by accident. It may seem superfluous, but I actually triggered it in my first attempt at a fix. René ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH] add-patch: roll over to next undecided hunk 2025-10-03 14:10 ` René Scharfe @ 2025-10-08 13:47 ` Phillip Wood 0 siblings, 0 replies; 37+ messages in thread From: Phillip Wood @ 2025-10-08 13:47 UTC (permalink / raw) To: René Scharfe, phillip.wood, Windl, Ulrich, git@vger.kernel.org Cc: Junio C Hamano On 03/10/2025 15:10, René Scharfe wrote: > On 10/3/25 3:41 PM, Phillip Wood wrote: >> >>> @@ -1436,8 +1436,15 @@ static int patch_update_file(struct add_p_state *s, >>> render_diff_header(s, file_diff, colored, &s->buf); >>> fputs(s->buf.buf, stdout); >>> for (;;) { >>> - if (hunk_index >= file_diff->hunk_nr) >>> + if (hunk_index >= file_diff->hunk_nr) { >>> hunk_index = 0; >>> + for (i = 0; i < file_diff->hunk_nr; i++) { >>> + if (file_diff->hunk[i].use == UNDECIDED_HUNK) { >>> + hunk_index = i; >>> + break; >>> + } >>> + } >>> + } >>> hunk = file_diff->hunk_nr >>> ? file_diff->hunk + hunk_index >> >> If there were no undecided hunks then this will be out of bounds >> because hunk_index >= file_diff->hunk_nr. Are we absolutely certain >> that we cannot reach this point without at least one hunk being >> undecided? > > The new loop only sets hunk_index if i < file_diff->hunk_nr. If > it finds no undecided hunk then it does nothing. Exactly - that's what I was worried about. However I'd missed the fact that we still set hunk_index to zero before the loop so I thought it was unchanged from file_diff->hunk_nr when in fact it is unchanged from zero which is safe. >>> +test_expect_success 'roll over to next undecided (1)' ' >>> + test_write_lines a b c d e f g h i j k l m n o p q >file && >>> + git add file && >>> + test_write_lines X b c d e f g h X j k l m n o p X >file && >>> + test_write_lines J y y q | git add -p >actual && >>> + test_write_lines 1 2 3 1 >expect && >>> + sed -ne "s-/.*--" -e "s-^(--p" <actual >hunks && >>> + test_cmp expect hunks >>> +' >> >> I'm not sure what this first test adds, the one below checks that we >> find the first undecided hunk which seems to be the important thing >> to check. > > It's a regression test for the case that the original code got > right by accident. It may seem superfluous, but I actually > triggered it in my first attempt at a fix. Ah, interesting, I'd assumed it was superfluous but it seems it isn't. I've had a quick read through of what Junio has in "seen" from the last iteration of this series and it looked like a nice improvement to the usability. Thanks Phillip > René > ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH] add-patch: roll over to next undecided hunk 2025-10-03 12:16 ` [PATCH] add-patch: roll over to next undecided hunk René Scharfe 2025-10-03 13:41 ` Phillip Wood @ 2025-10-03 16:11 ` Junio C Hamano 2025-10-03 19:53 ` René Scharfe 2025-10-03 21:18 ` Junio C Hamano 2 siblings, 1 reply; 37+ messages in thread From: Junio C Hamano @ 2025-10-03 16:11 UTC (permalink / raw) To: René Scharfe; +Cc: Windl, Ulrich, git@vger.kernel.org René Scharfe <l.s.r@web.de> writes: > git add --patch presents diff hunks one after the other, asking whether > to add them. If we mark some as undecided, e.g. with J, then it will Perhaps "mark" -> "leave". I somehow find it awkward to say "mark as undecided", as I have always viewed J/K as a way to skip a hunk, leaving it undecided. Besides, "J" lets you revisit a hunk that you earlier have decided to use of hold off, and it leaves your last decision on that hunk. A statement that implies "J marks as undecided" is misleading. > start over after reaching the last hunk. It always starts over at the > very first hunk, though, even if we already decided on it. Skip > decided hunks when rolling over instead. Nicely analyzed. > Reported-by: Windl, Ulrich <u.windl@ukr.de> > Signed-off-by: René Scharfe <l.s.r@web.de> > --- > add-patch.c | 9 ++++++++- > t/t3701-add-interactive.sh | 20 ++++++++++++++++++++ > 2 files changed, 28 insertions(+), 1 deletion(-) > > diff --git a/add-patch.c b/add-patch.c > index b0389c5d5b..42a8394c92 100644 > --- a/add-patch.c > +++ b/add-patch.c > @@ -1436,8 +1436,15 @@ static int patch_update_file(struct add_p_state *s, > render_diff_header(s, file_diff, colored, &s->buf); > fputs(s->buf.buf, stdout); > for (;;) { > - if (hunk_index >= file_diff->hunk_nr) > + if (hunk_index >= file_diff->hunk_nr) { > hunk_index = 0; > + for (i = 0; i < file_diff->hunk_nr; i++) { > + if (file_diff->hunk[i].use == UNDECIDED_HUNK) { > + hunk_index = i; > + break; > + } > + } > + } OK. This is probably a closely related tangent, but last night I was looking this function and found that its per-hunk loop does completely bogus thing. For example, find a case where you have more than one hunks, among which there are splittable and non-splittable hunks (a hunk is splittable if there are context lines between an added or a removed line). Start cycling the hunks without making any decisions with "J" or "K". Once you visited a splittable hunk (where you'd see 's' among the possible choices), coming back to an unsplittable hunk will now let you split it! 's' may not be visible among the choices, but telling it to 's'plit will give you "Split into 1", which is a technically correct nonsense. This is because the handling of "permitted" in that function only adds, without resetting at the end of processing the current hunk. Yet it does something like this: for (;;) { ... strbuf_reset(&s->buf); if (file_diff->hunk_nr) { ... add choices to the prompt ... if (hunk->splittable_into > 1) { permitted |= ALLOW_SPLIT; strbuf_addstr(&s->buf, ",s"); } ... } ... printf(_(s->mode->prompt_mode[prompt_mode_type]), s->buf.buf); if (*s->s.reset_color_interactive) fputs(s->s.reset_color_interactive, stdout); fflush(stdout); if (read_single_character(s) == EOF) break; ch = tolower(s->answer.buf[0]); ... dispatch on the command character ... if (ch == 'y') { ... } else if (s->answer.buf[0] == 's') { size_t splittable_into = hunk->splittable_into; if (!(permitted & ALLOW_SPLIT)) { err(s, _("Sorry, cannot split this hunk")); } else if (!split_hunk(s, file_diff, hunk - file_diff->hunk)) { color_fprintf_ln(stdout, s->s.header_color, _("Split into %d hunks."), (int)splittable_into); rendered_hunk_index = -1; } ... Notice that the prompt is built correctly but that information is *not* used when deciding if the operation is possible? This is another ancient regression that was introduced while rewriting this program in C near the end of 2019, I think. And this causes many other bugs in this area, like 'k' at the very first hunk gets complaint "No previous hunk" only once (you move to the next one with 'j' and come back to the first hunk with 'k', and then 'k' no longer complains, even though it is not among the choice). With this bug, however, we have gained a bit of useful feature, I think. Even though j/J should not be offered when we are at the last hunk for a file, we do wrap-around to the first hunk. I just checked the original code before the C rewrite, and even though it were written defensively so that incrementing the current hunk number to 5 when you have only 4 hunks would take you back to the initial hunk (instead of barfing), because we did not have this "permitted is never reset" bug, it actually did not allow you to go beyond the end with j/J. Today's code seems to have inherited this defensive adjustment to stay within the available hunks, and with the "permitted is never reset" bug, we are taken back to the first hunk. ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH] add-patch: roll over to next undecided hunk 2025-10-03 16:11 ` Junio C Hamano @ 2025-10-03 19:53 ` René Scharfe 2025-10-03 20:39 ` Junio C Hamano 2025-10-03 20:42 ` Junio C Hamano 0 siblings, 2 replies; 37+ messages in thread From: René Scharfe @ 2025-10-03 19:53 UTC (permalink / raw) To: Junio C Hamano; +Cc: Windl, Ulrich, git@vger.kernel.org On 10/3/25 6:11 PM, Junio C Hamano wrote: > René Scharfe <l.s.r@web.de> writes: > >> git add --patch presents diff hunks one after the other, asking whether >> to add them. If we mark some as undecided, e.g. with J, then it will > > Perhaps "mark" -> "leave". > > I somehow find it awkward to say "mark as undecided", as I have > always viewed J/K as a way to skip a hunk, leaving it undecided. > > Besides, "J" lets you revisit a hunk that you earlier have decided > to use of hold off, and it leaves your last decision on that hunk. > A statement that implies "J marks as undecided" is misleading. Right, j/J/k/K leave the use/skip/undecided status of the current hunk unchanged. "leave this hunk undecided" in the documentation is misleading as well, because these options will not leave a hunk undecided if we made a decision on it before: j - leave this hunk undecided, see next undecided hunk J - leave this hunk undecided, see next hunk k - leave this hunk undecided, see previous undecided hunk K - leave this hunk undecided, see previous hunk Perhaps omit it? j - go to next undecided hunk J - go to next hunk k - go to previous undecided hunk K - go to previous hunk Weird that one can switch between use and skip, but there's no way to revert back to undecided. >> start over after reaching the last hunk. It always starts over at the >> very first hunk, though, even if we already decided on it. Skip >> decided hunks when rolling over instead. > > Nicely analyzed. > >> Reported-by: Windl, Ulrich <u.windl@ukr.de> >> Signed-off-by: René Scharfe <l.s.r@web.de> >> --- >> add-patch.c | 9 ++++++++- >> t/t3701-add-interactive.sh | 20 ++++++++++++++++++++ >> 2 files changed, 28 insertions(+), 1 deletion(-) >> >> diff --git a/add-patch.c b/add-patch.c >> index b0389c5d5b..42a8394c92 100644 >> --- a/add-patch.c >> +++ b/add-patch.c >> @@ -1436,8 +1436,15 @@ static int patch_update_file(struct add_p_state *s, >> render_diff_header(s, file_diff, colored, &s->buf); >> fputs(s->buf.buf, stdout); >> for (;;) { >> - if (hunk_index >= file_diff->hunk_nr) >> + if (hunk_index >= file_diff->hunk_nr) { >> hunk_index = 0; >> + for (i = 0; i < file_diff->hunk_nr; i++) { >> + if (file_diff->hunk[i].use == UNDECIDED_HUNK) { >> + hunk_index = i; >> + break; >> + } >> + } >> + } > > OK. > > This is probably a closely related tangent, but last night I was > looking this function and found that its per-hunk loop does > completely bogus thing. For example, find a case where you have > more than one hunks, among which there are splittable and > non-splittable hunks (a hunk is splittable if there are context > lines between an added or a removed line). Start cycling the hunks > without making any decisions with "J" or "K". Once you visited a > splittable hunk (where you'd see 's' among the possible choices), > coming back to an unsplittable hunk will now let you split it! 's' > may not be visible among the choices, but telling it to 's'plit will > give you "Split into 1", which is a technically correct nonsense. > > This is because the handling of "permitted" in that function only > adds, without resetting at the end of processing the current hunk. > Yet it does something like this: > > for (;;) { > ... > strbuf_reset(&s->buf); > if (file_diff->hunk_nr) { > ... add choices to the prompt ... > if (hunk->splittable_into > 1) { > permitted |= ALLOW_SPLIT; > strbuf_addstr(&s->buf, ",s"); > } > ... > } > ... > printf(_(s->mode->prompt_mode[prompt_mode_type]), > s->buf.buf); > if (*s->s.reset_color_interactive) > fputs(s->s.reset_color_interactive, stdout); > fflush(stdout); > if (read_single_character(s) == EOF) > break; > ch = tolower(s->answer.buf[0]); > ... dispatch on the command character ... > if (ch == 'y') { > ... > } else if (s->answer.buf[0] == 's') { > size_t splittable_into = hunk->splittable_into; > if (!(permitted & ALLOW_SPLIT)) { > err(s, _("Sorry, cannot split this hunk")); > } else if (!split_hunk(s, file_diff, > hunk - file_diff->hunk)) { > color_fprintf_ln(stdout, s->s.header_color, > _("Split into %d hunks."), > (int)splittable_into); > rendered_hunk_index = -1; > } > ... > > Notice that the prompt is built correctly but that information is > *not* used when deciding if the operation is possible? > > This is another ancient regression that was introduced while > rewriting this program in C near the end of 2019, I think. And this > causes many other bugs in this area, like 'k' at the very first hunk > gets complaint "No previous hunk" only once (you move to the next > one with 'j' and come back to the first hunk with 'k', and then 'k' > no longer complains, even though it is not among the choice). This should be easy to fix by resetting permitted at the start of the loop, no? Patch below. > With this bug, however, we have gained a bit of useful feature, I > think. Even though j/J should not be offered when we are at the > last hunk for a file, we do wrap-around to the first hunk. I just > checked the original code before the C rewrite, and even though it > were written defensively so that incrementing the current hunk > number to 5 when you have only 4 hunks would take you back to the > initial hunk (instead of barfing), because we did not have this > "permitted is never reset" bug, it actually did not allow you to go > beyond the end with j/J. Today's code seems to have inherited this > defensive adjustment to stay within the available hunks, and with > the "permitted is never reset" bug, we are taken back to the first > hunk. y/n/e on the last hunk roll over, which makes sense to me. Their movement part is not mentioned in the documentation, by the way. With the patch below j/J are stopped by the floor, as seemingly intended. Not sure if the (now accidental) roll-over behavior is better for them. add-patch.c | 19 ++++++++++--------- t/t3701-add-interactive.sh | 19 +++++++++++++++++++ 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/add-patch.c b/add-patch.c index 42a8394c92..1012840019 100644 --- a/add-patch.c +++ b/add-patch.c @@ -1418,15 +1418,6 @@ static int patch_update_file(struct add_p_state *s, struct child_process cp = CHILD_PROCESS_INIT; int colored = !!s->colored.len, quit = 0, use_pager = 0; enum prompt_mode_type prompt_mode_type; - enum { - ALLOW_GOTO_PREVIOUS_HUNK = 1 << 0, - ALLOW_GOTO_PREVIOUS_UNDECIDED_HUNK = 1 << 1, - ALLOW_GOTO_NEXT_HUNK = 1 << 2, - ALLOW_GOTO_NEXT_UNDECIDED_HUNK = 1 << 3, - ALLOW_SEARCH_AND_GOTO = 1 << 4, - ALLOW_SPLIT = 1 << 5, - ALLOW_EDIT = 1 << 6 - } permitted = 0; /* Empty added files have no hunks */ if (!file_diff->hunk_nr && !file_diff->added) @@ -1436,6 +1427,16 @@ static int patch_update_file(struct add_p_state *s, render_diff_header(s, file_diff, colored, &s->buf); fputs(s->buf.buf, stdout); for (;;) { + enum { + ALLOW_GOTO_PREVIOUS_HUNK = 1 << 0, + ALLOW_GOTO_PREVIOUS_UNDECIDED_HUNK = 1 << 1, + ALLOW_GOTO_NEXT_HUNK = 1 << 2, + ALLOW_GOTO_NEXT_UNDECIDED_HUNK = 1 << 3, + ALLOW_SEARCH_AND_GOTO = 1 << 4, + ALLOW_SPLIT = 1 << 5, + ALLOW_EDIT = 1 << 6 + } permitted = 0; + if (hunk_index >= file_diff->hunk_nr) { hunk_index = 0; for (i = 0; i < file_diff->hunk_nr; i++) { diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh index fa6ec5f835..33b307b8ff 100755 --- a/t/t3701-add-interactive.sh +++ b/t/t3701-add-interactive.sh @@ -1341,6 +1341,25 @@ test_expect_success 'roll over to next undecided (2)' ' test_cmp expect hunks ' +test_expect_success 'invalid options are rejected' ' + test_write_lines a b c d e f g h i j k >file && + git add file && + test_write_lines X b c d e f g h X j X >file && + test_write_lines j j J k k K s q | git add -p >out && + sed -ne "s/ @@.*//" -e "s/ \$//" -e "/^(/p" <out >actual && + cat >expect <<-EOF && + (1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,p,?]? + (2/2) Stage this hunk [y,n,q,a,d,k,K,g,/,s,e,p,?]? No next hunk + (2/2) Stage this hunk [y,n,q,a,d,k,K,g,/,s,e,p,?]? No next hunk + (2/2) Stage this hunk [y,n,q,a,d,k,K,g,/,s,e,p,?]? + (1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,p,?]? No previous hunk + (1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,p,?]? No previous hunk + (1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,p,?]? Sorry, cannot split this hunk + (1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,p,?]? + EOF + test_cmp expect actual +' + test_expect_success 'set up base for -p color tests' ' echo commit >file && git commit -am "commit state" && ^ permalink raw reply related [flat|nested] 37+ messages in thread
* Re: [PATCH] add-patch: roll over to next undecided hunk 2025-10-03 19:53 ` René Scharfe @ 2025-10-03 20:39 ` Junio C Hamano 2025-10-03 20:42 ` Junio C Hamano 1 sibling, 0 replies; 37+ messages in thread From: Junio C Hamano @ 2025-10-03 20:39 UTC (permalink / raw) To: René Scharfe; +Cc: Windl, Ulrich, git@vger.kernel.org René Scharfe <l.s.r@web.de> writes: > Weird that one can switch between use and skip, but there's no > way to revert back to undecided. Yes, but Phillip's "if you split the resulting hunks will revert to undecided" topic, together with "you can split one hunk into one" bug that is caused by the "permitted is never reset" bug, if you can navigate back to what you already decided to use or skip, you can say "split" to revert it undecided ;-). > This should be easy to fix by resetting permitted at the start of the > loop, no? Patch below. > >> With this bug, however, we have gained a bit of useful feature, I >> think. Even though j/J should not be offered when we are at the >> last hunk for a file, we do wrap-around to the first hunk. I just >> checked the original code before the C rewrite, and even though it >> were written defensively so that incrementing the current hunk >> number to 5 when you have only 4 hunks would take you back to the >> initial hunk (instead of barfing), because we did not have this >> "permitted is never reset" bug, it actually did not allow you to go >> beyond the end with j/J. Today's code seems to have inherited this >> defensive adjustment to stay within the available hunks, and with >> the "permitted is never reset" bug, we are taken back to the first >> hunk. > y/n/e on the last hunk roll over, which makes sense to me. Their > movement part is not mentioned in the documentation, by the way. > > With the patch below j/J are stopped by the floor, as seemingly > intended. Not sure if the (now accidental) roll-over behavior is > better for them. Yes. Even if it is accidental, people are too used the roll-over behaviour. So at least we should always allow J/K and probably allow j/k as long as there at least is a single undecided hunk, if we were to do this fix, and make the prompt string to match. I only am aware of this bugginess in "j,k,J,K,s" but that is only because I did not look at others. I wouldn't be surprised if they were even buggier. Thanks. ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH] add-patch: roll over to next undecided hunk 2025-10-03 19:53 ` René Scharfe 2025-10-03 20:39 ` Junio C Hamano @ 2025-10-03 20:42 ` Junio C Hamano 1 sibling, 0 replies; 37+ messages in thread From: Junio C Hamano @ 2025-10-03 20:42 UTC (permalink / raw) To: René Scharfe; +Cc: Windl, Ulrich, git@vger.kernel.org René Scharfe <l.s.r@web.de> writes: > On 10/3/25 6:11 PM, Junio C Hamano wrote: >> René Scharfe <l.s.r@web.de> writes: >> >>> git add --patch presents diff hunks one after the other, asking whether >>> to add them. If we mark some as undecided, e.g. with J, then it will >> >> Perhaps "mark" -> "leave". >> >> I somehow find it awkward to say "mark as undecided", as I have >> always viewed J/K as a way to skip a hunk, leaving it undecided. >> >> Besides, "J" lets you revisit a hunk that you earlier have decided >> to use of hold off, and it leaves your last decision on that hunk. >> A statement that implies "J marks as undecided" is misleading. > > Right, j/J/k/K leave the use/skip/undecided status of the current hunk > unchanged. Yes. If the one you are walking away with 'J' were already selected, the scenario you describe in the proposed log message would not work, so "mark" -> "leave" is the right thing to do in that context. But ... > "leave this hunk undecided" in the documentation is > misleading as well, because these options will not leave a hunk > undecided if we made a decision on it before: ... as you say, I agree that your updated version > j - go to next undecided hunk > J - go to next hunk > k - go to previous undecided hunk > K - go to previous hunk in the documentation or help would be a good change. > Weird that one can switch between use and skip, but there's no > way to revert back to undecided. Another thing that is missing (and these two are not a regression in the C version, but the same in my scripted original) is that once you decided on _all_ hunks of a file, there is no way to come back and tell the tool that you changed your mind. ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH] add-patch: roll over to next undecided hunk 2025-10-03 12:16 ` [PATCH] add-patch: roll over to next undecided hunk René Scharfe 2025-10-03 13:41 ` Phillip Wood 2025-10-03 16:11 ` Junio C Hamano @ 2025-10-03 21:18 ` Junio C Hamano 2 siblings, 0 replies; 37+ messages in thread From: Junio C Hamano @ 2025-10-03 21:18 UTC (permalink / raw) To: René Scharfe; +Cc: Windl, Ulrich, git@vger.kernel.org René Scharfe <l.s.r@web.de> writes: > git add --patch presents diff hunks one after the other, asking whether > to add them. If we mark some as undecided, e.g. with J, then it will > start over after reaching the last hunk. It always starts over at the > very first hunk, though, even if we already decided on it. Skip > decided hunks when rolling over instead. Wait a bit. With 'J', the user wants to walk the list of hunks, both decided and undecided ones, no? So ... > diff --git a/add-patch.c b/add-patch.c > index b0389c5d5b..42a8394c92 100644 > --- a/add-patch.c > +++ b/add-patch.c > @@ -1436,8 +1436,15 @@ static int patch_update_file(struct add_p_state *s, > render_diff_header(s, file_diff, colored, &s->buf); > fputs(s->buf.buf, stdout); > for (;;) { > - if (hunk_index >= file_diff->hunk_nr) > + if (hunk_index >= file_diff->hunk_nr) { > hunk_index = 0; > + for (i = 0; i < file_diff->hunk_nr; i++) { > + if (file_diff->hunk[i].use == UNDECIDED_HUNK) { > + hunk_index = i; > + break; > + } > + } > + } ... why is it a good idea to skip decided ones? With 'j', the story is probably different, but I didn't dig deep enough. With 'K', we seem to do } else if (s->answer.buf[0] == 'K') { if (permitted & ALLOW_GOTO_PREVIOUS_HUNK) hunk_index--; else err(s, _("No previous hunk")); and there is *no* guard around here to say "hey, hunk_index has gone negative, so we must move back to the last hunk", similar to what is happening here that does "hunk_index has gone beyond the end, so let's wrap around to the first one". This is another bug that is maked by the fact that hunk_index is measured in size_t (even though there is no reason to do so). Using unsigned hunk_ix is simply crazy here, especially for a code that wants "just do hunk_index++ and adjust if it goes beyond the end" and similarly "just do hunk_index-- and adjust if it goes beyond the beginning". In any case, as the result, going back with 'K' when we are on the first hunk is broken with the current code because we'd stay there, and with this patch, we'd move to the first undecided hunk. Neither is correct---we should move to the last hunk, I would think. ^ permalink raw reply [flat|nested] 37+ messages in thread
* [PATCH v2 0/5] add-patch: roll over to next undecided hunk 2025-10-02 9:23 Broken handling of "J" hunks for "add --interactive"? Windl, Ulrich 2025-10-03 12:16 ` [PATCH] add-patch: roll over to next undecided hunk René Scharfe @ 2025-10-05 15:45 ` René Scharfe 2025-10-05 15:55 ` [PATCH v2 1/5] add-patch: improve help for options j, J, k, and K René Scharfe ` (4 more replies) 2025-10-06 17:18 ` [PATCH v3 0/6] add-patch: roll over to next undecided hunk René Scharfe 2 siblings, 5 replies; 37+ messages in thread From: René Scharfe @ 2025-10-05 15:45 UTC (permalink / raw) To: git@vger.kernel.org; +Cc: Windl, Ulrich, Junio C Hamano, Phillip Wood Changes since v1: - add patches 1 and 5 to address issues that came up in conversation - split out roll-over of option j - let options J, k, and K roll over as well - broader test coverage add-patch: improve help for options j, J, k, and K add-patch: document that option J rolls over add-patch: let options y, n, j, and e roll over to next undecided add-patch: let options k and K roll over like j and J add-patch: reset "permitted" at loop start Documentation/git-add.adoc | 8 ++-- add-patch.c | 52 +++++++++++++++++--------- t/t3701-add-interactive.sh | 76 ++++++++++++++++++++++++++++++-------- 3 files changed, 99 insertions(+), 37 deletions(-) -- 2.51.0 ^ permalink raw reply [flat|nested] 37+ messages in thread
* [PATCH v2 1/5] add-patch: improve help for options j, J, k, and K 2025-10-05 15:45 ` [PATCH v2 0/5] " René Scharfe @ 2025-10-05 15:55 ` René Scharfe 2025-10-05 21:30 ` Junio C Hamano 2025-10-31 10:08 ` [EXT] " Windl, Ulrich 2025-10-05 15:55 ` [PATCH v2 2/5] add-patch: document that option J rolls over René Scharfe ` (3 subsequent siblings) 4 siblings, 2 replies; 37+ messages in thread From: René Scharfe @ 2025-10-05 15:55 UTC (permalink / raw) To: git@vger.kernel.org; +Cc: Windl, Ulrich, Junio C Hamano, Phillip Wood The options j, J, k, and K don't affect the status of the current hunk. They just go to a different one. This is true whether the current hunk is undecided or not. Avoid misunderstanding by no longer mentioning the current hunk explicitly in their help texts. Signed-off-by: René Scharfe <l.s.r@web.de> --- Documentation/git-add.adoc | 8 ++++---- add-patch.c | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Documentation/git-add.adoc b/Documentation/git-add.adoc index ad629c46c5..3266ccf105 100644 --- a/Documentation/git-add.adoc +++ b/Documentation/git-add.adoc @@ -342,10 +342,10 @@ patch:: d - do not stage this hunk or any of the later hunks in the file g - select a hunk to go to / - search for a hunk matching the given regex - j - leave this hunk undecided, see next undecided hunk - J - leave this hunk undecided, see next hunk - k - leave this hunk undecided, see previous undecided hunk - K - leave this hunk undecided, see previous hunk + j - go to the next undecided hunk + J - go to the next hunk + k - go to the previous undecided hunk + K - go to the previous hunk s - split the current hunk into smaller hunks e - manually edit the current hunk p - print the current hunk diff --git a/add-patch.c b/add-patch.c index b0389c5d5b..912266a3f8 100644 --- a/add-patch.c +++ b/add-patch.c @@ -1397,10 +1397,10 @@ static size_t display_hunks(struct add_p_state *s, } static const char help_patch_remainder[] = -N_("j - leave this hunk undecided, see next undecided hunk\n" - "J - leave this hunk undecided, see next hunk\n" - "k - leave this hunk undecided, see previous undecided hunk\n" - "K - leave this hunk undecided, see previous hunk\n" +N_("j - go to the next undecided hunk\n" + "J - go to the next hunk\n" + "k - go to the previous undecided hunk\n" + "K - go to the previous hunk\n" "g - select a hunk to go to\n" "/ - search for a hunk matching the given regex\n" "s - split the current hunk into smaller hunks\n" -- 2.51.0 ^ permalink raw reply related [flat|nested] 37+ messages in thread
* Re: [PATCH v2 1/5] add-patch: improve help for options j, J, k, and K 2025-10-05 15:55 ` [PATCH v2 1/5] add-patch: improve help for options j, J, k, and K René Scharfe @ 2025-10-05 21:30 ` Junio C Hamano 2025-10-06 17:17 ` René Scharfe 2025-10-31 10:08 ` [EXT] " Windl, Ulrich 1 sibling, 1 reply; 37+ messages in thread From: Junio C Hamano @ 2025-10-05 21:30 UTC (permalink / raw) To: René Scharfe; +Cc: git@vger.kernel.org, Windl, Ulrich, Phillip Wood René Scharfe <l.s.r@web.de> writes: > The options j, J, k, and K don't affect the status of the current hunk. > They just go to a different one. This is true whether the current hunk > is undecided or not. Avoid misunderstanding by no longer mentioning > the current hunk explicitly in their help texts. > > Signed-off-by: René Scharfe <l.s.r@web.de> > --- > Documentation/git-add.adoc | 8 ++++---- > add-patch.c | 8 ++++---- > 2 files changed, 8 insertions(+), 8 deletions(-) > > diff --git a/Documentation/git-add.adoc b/Documentation/git-add.adoc > index ad629c46c5..3266ccf105 100644 > --- a/Documentation/git-add.adoc > +++ b/Documentation/git-add.adoc > @@ -342,10 +342,10 @@ patch:: > d - do not stage this hunk or any of the later hunks in the file > g - select a hunk to go to > / - search for a hunk matching the given regex > - j - leave this hunk undecided, see next undecided hunk > - J - leave this hunk undecided, see next hunk > - k - leave this hunk undecided, see previous undecided hunk > - K - leave this hunk undecided, see previous hunk > + j - go to the next undecided hunk > + J - go to the next hunk > + k - go to the previous undecided hunk > + K - go to the previous hunk These obviously make sense, but I wonder if y/n should also say that they not just make a decision on the current hunk, but after doing so they move you forward (and if so, that may fall within the theme of this step, which is to improve the help text on options). ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH v2 1/5] add-patch: improve help for options j, J, k, and K 2025-10-05 21:30 ` Junio C Hamano @ 2025-10-06 17:17 ` René Scharfe 2025-10-06 17:58 ` Junio C Hamano 0 siblings, 1 reply; 37+ messages in thread From: René Scharfe @ 2025-10-06 17:17 UTC (permalink / raw) To: Junio C Hamano; +Cc: git@vger.kernel.org, Windl, Ulrich, Phillip Wood On 10/5/25 11:30 PM, Junio C Hamano wrote: > René Scharfe <l.s.r@web.de> writes: > >> The options j, J, k, and K don't affect the status of the current hunk. >> They just go to a different one. This is true whether the current hunk >> is undecided or not. Avoid misunderstanding by no longer mentioning >> the current hunk explicitly in their help texts. >> >> Signed-off-by: René Scharfe <l.s.r@web.de> >> --- >> Documentation/git-add.adoc | 8 ++++---- >> add-patch.c | 8 ++++---- >> 2 files changed, 8 insertions(+), 8 deletions(-) >> >> diff --git a/Documentation/git-add.adoc b/Documentation/git-add.adoc >> index ad629c46c5..3266ccf105 100644 >> --- a/Documentation/git-add.adoc >> +++ b/Documentation/git-add.adoc >> @@ -342,10 +342,10 @@ patch:: >> d - do not stage this hunk or any of the later hunks in the file >> g - select a hunk to go to >> / - search for a hunk matching the given regex >> - j - leave this hunk undecided, see next undecided hunk >> - J - leave this hunk undecided, see next hunk >> - k - leave this hunk undecided, see previous undecided hunk >> - K - leave this hunk undecided, see previous hunk >> + j - go to the next undecided hunk >> + J - go to the next hunk >> + k - go to the previous undecided hunk >> + K - go to the previous hunk > > These obviously make sense, but I wonder if y/n should also say that > they not just make a decision on the current hunk, but after doing > so they move you forward Yes. > (and if so, that may fall within the theme > of this step, which is to improve the help text on options). I see it more narrowly: This patch removes unnecessary references to the hunk's status, while a y/n doc patch would add missing pieces. Hmm, would the help text need to adapt to whether the current hunk is the last undecided one? E.g., "stage this hunk, implies 'j'" if j is an allowed option and "stage this hunk and quit" otherwise? Stuff for a separate series, I think. René ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH v2 1/5] add-patch: improve help for options j, J, k, and K 2025-10-06 17:17 ` René Scharfe @ 2025-10-06 17:58 ` Junio C Hamano 0 siblings, 0 replies; 37+ messages in thread From: Junio C Hamano @ 2025-10-06 17:58 UTC (permalink / raw) To: René Scharfe; +Cc: git@vger.kernel.org, Windl, Ulrich, Phillip Wood René Scharfe <l.s.r@web.de> writes: > I see it more narrowly: This patch removes unnecessary references to the > hunk's status, while a y/n doc patch would add missing pieces. Good. > Hmm, would the help text need to adapt to whether the current hunk is > the last undecided one? E.g., "stage this hunk, implies 'j'" if j is an > allowed option and "stage this hunk and quit" otherwise? Stuff for a > separate series, I think. Sounds good. ^ permalink raw reply [flat|nested] 37+ messages in thread
* RE: [EXT] [PATCH v2 1/5] add-patch: improve help for options j, J, k, and K 2025-10-05 15:55 ` [PATCH v2 1/5] add-patch: improve help for options j, J, k, and K René Scharfe 2025-10-05 21:30 ` Junio C Hamano @ 2025-10-31 10:08 ` Windl, Ulrich 2025-11-01 8:18 ` Junio C Hamano 1 sibling, 1 reply; 37+ messages in thread From: Windl, Ulrich @ 2025-10-31 10:08 UTC (permalink / raw) To: René Scharfe, git@vger.kernel.org; +Cc: Junio C Hamano, Phillip Wood Hi! Sorry for the delay, but I was on vacation without access to this mailbox. For the patch diff --git a/Documentation/git-add.adoc b/Documentation/git-add.adoc index ad629c46c5..3266ccf105 100644 I don't see an actual improvement, and I'd prefer the previous version of the doc. Likwise for diff --git a/add-patch.c b/add-patch.c index b0389c5d5b..912266a3f8 100644 Kind regards, Ulrich Windl > -----Original Message----- > From: René Scharfe <l.s.r@web.de> > Sent: Sunday, October 5, 2025 5:55 PM > To: git@vger.kernel.org > Cc: Windl, Ulrich <u.windl@ukr.de>; Junio C Hamano <gitster@pobox.com>; > Phillip Wood <phillip.wood@dunelm.org.uk> > Subject: [EXT] [PATCH v2 1/5] add-patch: improve help for options j, J, k, and > K > > Sicherheits-Hinweis: Diese E-Mail wurde von einer Person außerhalb des UKR > gesendet. Seien Sie vorsichtig vor gefälschten Absendern, wenn Sie auf Links > klicken, Anhänge öffnen oder weitere Aktionen ausführen, bevor Sie die > Echtheit überprüft haben. > > The options j, J, k, and K don't affect the status of the current hunk. > They just go to a different one. This is true whether the current hunk > is undecided or not. Avoid misunderstanding by no longer mentioning > the current hunk explicitly in their help texts. > > Signed-off-by: René Scharfe <l.s.r@web.de> > --- > Documentation/git-add.adoc | 8 ++++---- > add-patch.c | 8 ++++---- > 2 files changed, 8 insertions(+), 8 deletions(-) > > diff --git a/Documentation/git-add.adoc b/Documentation/git-add.adoc > index ad629c46c5..3266ccf105 100644 > --- a/Documentation/git-add.adoc > +++ b/Documentation/git-add.adoc > @@ -342,10 +342,10 @@ patch:: > d - do not stage this hunk or any of the later hunks in the file > g - select a hunk to go to > / - search for a hunk matching the given regex > - j - leave this hunk undecided, see next undecided hunk > - J - leave this hunk undecided, see next hunk > - k - leave this hunk undecided, see previous undecided hunk > - K - leave this hunk undecided, see previous hunk > + j - go to the next undecided hunk > + J - go to the next hunk > + k - go to the previous undecided hunk > + K - go to the previous hunk > s - split the current hunk into smaller hunks > e - manually edit the current hunk > p - print the current hunk > diff --git a/add-patch.c b/add-patch.c > index b0389c5d5b..912266a3f8 100644 > --- a/add-patch.c > +++ b/add-patch.c > @@ -1397,10 +1397,10 @@ static size_t display_hunks(struct add_p_state > *s, > } > > static const char help_patch_remainder[] = > -N_("j - leave this hunk undecided, see next undecided hunk\n" > - "J - leave this hunk undecided, see next hunk\n" > - "k - leave this hunk undecided, see previous undecided hunk\n" > - "K - leave this hunk undecided, see previous hunk\n" > +N_("j - go to the next undecided hunk\n" > + "J - go to the next hunk\n" > + "k - go to the previous undecided hunk\n" > + "K - go to the previous hunk\n" > "g - select a hunk to go to\n" > "/ - search for a hunk matching the given regex\n" > "s - split the current hunk into smaller hunks\n" > -- > 2.51.0 ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [EXT] [PATCH v2 1/5] add-patch: improve help for options j, J, k, and K 2025-10-31 10:08 ` [EXT] " Windl, Ulrich @ 2025-11-01 8:18 ` Junio C Hamano 2025-11-03 12:43 ` [EXT] " Windl, Ulrich 0 siblings, 1 reply; 37+ messages in thread From: Junio C Hamano @ 2025-11-01 8:18 UTC (permalink / raw) To: Windl, Ulrich; +Cc: René Scharfe, git@vger.kernel.org, Phillip Wood "Windl, Ulrich" <u.windl@ukr.de> writes: > For the patch > diff --git a/Documentation/git-add.adoc b/Documentation/git-add.adoc > index ad629c46c5..3266ccf105 100644 > > I don't see an actual improvement, and I'd prefer the previous > version of the doc. We'd prefer to see something more concrete that refuses the reasoning that led to the change, than a subjective "I don't see, I'd prefer". At least, the commit log messge given by 2c3cc43f (add-patch: improve help for options j, J, k, and K, 2025-10-06) explains why the change is an improvement, and I found it sensible. (<b5034851-65bd-49da-b270-48b68d9210ff@web.de>) The old description said 'j' leaves this hunk undecided and goes to the next undecided hunk, but it is both pointless and misleading to say 'leave this hunk undecided'. Unlike 'y' or 'n', the movement options 'j', 'k' are not about changing the state of the current thing we are on (so it is pointless to say "LEAVE it undecided"), and more importantly, when we say 'j', the state of the current thing we are on may not necessarily be 'undecided' (so it is misleading to say "leave it UNDECIDED"). ^ permalink raw reply [flat|nested] 37+ messages in thread
* RE: [EXT] Re: [PATCH v2 1/5] add-patch: improve help for options j, J, k, and K 2025-11-01 8:18 ` Junio C Hamano @ 2025-11-03 12:43 ` Windl, Ulrich 0 siblings, 0 replies; 37+ messages in thread From: Windl, Ulrich @ 2025-11-03 12:43 UTC (permalink / raw) To: Junio C Hamano; +Cc: René Scharfe, git@vger.kernel.org, Phillip Wood OK, fair enough: I think the original wording is more clear, so I don't see the need to change it at all. Possible corner cases ecepted, e.g. whether "next" can wrap at the end or not. Kind regards, Ulrich Windl > -----Original Message----- > From: Junio C Hamano <gitster@pobox.com> > Sent: Saturday, November 1, 2025 9:19 AM > To: Windl, Ulrich <u.windl@ukr.de> > Cc: René Scharfe <l.s.r@web.de>; git@vger.kernel.org; Phillip Wood > <phillip.wood@dunelm.org.uk> > Subject: [EXT] Re: [PATCH v2 1/5] add-patch: improve help for options j, J, k, > and K > > Sicherheits-Hinweis: Diese E-Mail wurde von einer Person außerhalb des UKR > gesendet. Seien Sie vorsichtig vor gefälschten Absendern, wenn Sie auf Links > klicken, Anhänge öffnen oder weitere Aktionen ausführen, bevor Sie die > Echtheit überprüft haben. > > "Windl, Ulrich" <u.windl@ukr.de> writes: > > > For the patch > > diff --git a/Documentation/git-add.adoc b/Documentation/git-add.adoc > > index ad629c46c5..3266ccf105 100644 > > > > I don't see an actual improvement, and I'd prefer the previous > > version of the doc. > > We'd prefer to see something more concrete that refuses the > reasoning that led to the change, than a subjective "I don't see, > I'd prefer". > > At least, the commit log messge given by 2c3cc43f (add-patch: > improve help for options j, J, k, and K, 2025-10-06) explains why > the change is an improvement, and I found it sensible. > (<b5034851-65bd-49da-b270-48b68d9210ff@web.de>) > > The old description said 'j' leaves this hunk undecided and goes to > the next undecided hunk, but it is both pointless and misleading to > say 'leave this hunk undecided'. Unlike 'y' or 'n', the movement > options 'j', 'k' are not about changing the state of the current > thing we are on (so it is pointless to say "LEAVE it undecided"), > and more importantly, when we say 'j', the state of the current > thing we are on may not necessarily be 'undecided' (so it is > misleading to say "leave it UNDECIDED"). ^ permalink raw reply [flat|nested] 37+ messages in thread
* [PATCH v2 2/5] add-patch: document that option J rolls over 2025-10-05 15:45 ` [PATCH v2 0/5] " René Scharfe 2025-10-05 15:55 ` [PATCH v2 1/5] add-patch: improve help for options j, J, k, and K René Scharfe @ 2025-10-05 15:55 ` René Scharfe 2025-10-05 21:30 ` Junio C Hamano 2025-10-05 15:55 ` [PATCH v2 3/5] add-patch: let options y, n, j, and e roll over to next undecided René Scharfe ` (2 subsequent siblings) 4 siblings, 1 reply; 37+ messages in thread From: René Scharfe @ 2025-10-05 15:55 UTC (permalink / raw) To: git@vger.kernel.org; +Cc: Windl, Ulrich, Junio C Hamano, Phillip Wood The variable "permitted" is only not reset after moving to a different hunk, so it only accumulates permission and doesn't necessarily reflect those of the current hunk. This may be a bug, but is actually useful with the option J, which can be used at the last hunk to roll over to the first hunk. Make this particular behavior official. Suggested-by: Junio C Hamano <gitster@pobox.com> Signed-off-by: René Scharfe <l.s.r@web.de> --- Documentation/git-add.adoc | 2 +- add-patch.c | 4 ++-- t/t3701-add-interactive.sh | 18 ++++++++++++++---- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/Documentation/git-add.adoc b/Documentation/git-add.adoc index 3266ccf105..5c05a3a7f9 100644 --- a/Documentation/git-add.adoc +++ b/Documentation/git-add.adoc @@ -343,7 +343,7 @@ patch:: g - select a hunk to go to / - search for a hunk matching the given regex j - go to the next undecided hunk - J - go to the next hunk + J - go to the next hunk, roll over at the bottom k - go to the previous undecided hunk K - go to the previous hunk s - split the current hunk into smaller hunks diff --git a/add-patch.c b/add-patch.c index 912266a3f8..bef2ba7a25 100644 --- a/add-patch.c +++ b/add-patch.c @@ -1398,7 +1398,7 @@ static size_t display_hunks(struct add_p_state *s, static const char help_patch_remainder[] = N_("j - go to the next undecided hunk\n" - "J - go to the next hunk\n" + "J - go to the next hunk, roll over at the bottom\n" "k - go to the previous undecided hunk\n" "K - go to the previous hunk\n" "g - select a hunk to go to\n" @@ -1493,7 +1493,7 @@ static int patch_update_file(struct add_p_state *s, permitted |= ALLOW_GOTO_NEXT_UNDECIDED_HUNK; strbuf_addstr(&s->buf, ",j"); } - if (hunk_index + 1 < file_diff->hunk_nr) { + if (file_diff->hunk_nr > 1) { permitted |= ALLOW_GOTO_NEXT_HUNK; strbuf_addstr(&s->buf, ",J"); } diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh index d9fe289a7a..d5d2e120ab 100755 --- a/t/t3701-add-interactive.sh +++ b/t/t3701-add-interactive.sh @@ -334,7 +334,7 @@ test_expect_success 'different prompts for mode change/deleted' ' cat >expect <<-\EOF && (1/1) Stage deletion [y,n,q,a,d,p,?]? (1/2) Stage mode change [y,n,q,a,d,j,J,g,/,p,?]? - (2/2) Stage this hunk [y,n,q,a,d,K,g,/,e,p,?]? + (2/2) Stage this hunk [y,n,q,a,d,K,J,g,/,e,p,?]? EOF test_cmp expect actual.filtered ' @@ -521,7 +521,7 @@ test_expect_success 'split hunk setup' ' test_expect_success 'goto hunk 1 with "g 1"' ' test_when_finished "git reset" && tr _ " " >expect <<-EOF && - (2/2) Stage this hunk [y,n,q,a,d,K,g,/,e,p,?]? + 1: -1,2 +1,3 +15 + (2/2) Stage this hunk [y,n,q,a,d,K,J,g,/,e,p,?]? + 1: -1,2 +1,3 +15 _ 2: -2,4 +3,8 +21 go to which hunk? @@ -1,2 +1,3 @@ _10 @@ -550,7 +550,7 @@ test_expect_success 'goto hunk 1 with "g1"' ' test_expect_success 'navigate to hunk via regex /pattern' ' test_when_finished "git reset" && tr _ " " >expect <<-EOF && - (2/2) Stage this hunk [y,n,q,a,d,K,g,/,e,p,?]? @@ -1,2 +1,3 @@ + (2/2) Stage this hunk [y,n,q,a,d,K,J,g,/,e,p,?]? @@ -1,2 +1,3 @@ _10 +15 _20 @@ -805,7 +805,7 @@ test_expect_success 'colors can be overridden' ' <YELLOW>(1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,p,?]? <RESET><MAGENTA>@@ -3 +3,2 @@<RESET> <CYAN> more-context<RESET> <BLUE>+<RESET><BLUE>another-one<RESET> - <YELLOW>(2/2) Stage this hunk [y,n,q,a,d,K,g,/,e,p,?]? <RESET><MAGENTA>@@ -1,3 +1,3 @@<RESET> + <YELLOW>(2/2) Stage this hunk [y,n,q,a,d,K,J,g,/,e,p,?]? <RESET><MAGENTA>@@ -1,3 +1,3 @@<RESET> <CYAN> context<RESET> <BOLD>-old<RESET> <BLUE>+new<RESET> @@ -1354,4 +1354,14 @@ do ' done +test_expect_success 'option J rolls over' ' + test_write_lines a b c d e f g h i >file && + git add file && + test_write_lines X b c d e f g h X >file && + test_write_lines J J q | git add -p >out && + test_write_lines 1 2 1 >expect && + sed -n -e "s-/.*--" -e "s/^(//p" <out >actual && + test_cmp expect actual +' + test_done -- 2.51.0 ^ permalink raw reply related [flat|nested] 37+ messages in thread
* Re: [PATCH v2 2/5] add-patch: document that option J rolls over 2025-10-05 15:55 ` [PATCH v2 2/5] add-patch: document that option J rolls over René Scharfe @ 2025-10-05 21:30 ` Junio C Hamano 0 siblings, 0 replies; 37+ messages in thread From: Junio C Hamano @ 2025-10-05 21:30 UTC (permalink / raw) To: René Scharfe; +Cc: git@vger.kernel.org, Windl, Ulrich, Phillip Wood René Scharfe <l.s.r@web.de> writes: > The variable "permitted" is only not reset after moving to a different "only not" -> "not". > hunk, so it only accumulates permission and doesn't necessarily reflect > those of the current hunk. This may be a bug, but is actually useful > with the option J, which can be used at the last hunk to roll over to > the first hunk. Make this particular behavior official. > > Suggested-by: Junio C Hamano <gitster@pobox.com> > Signed-off-by: René Scharfe <l.s.r@web.de> > --- > Documentation/git-add.adoc | 2 +- > add-patch.c | 4 ++-- > t/t3701-add-interactive.sh | 18 ++++++++++++++---- > 3 files changed, 17 insertions(+), 7 deletions(-) > > diff --git a/Documentation/git-add.adoc b/Documentation/git-add.adoc > index 3266ccf105..5c05a3a7f9 100644 > --- a/Documentation/git-add.adoc > +++ b/Documentation/git-add.adoc > @@ -343,7 +343,7 @@ patch:: > g - select a hunk to go to > / - search for a hunk matching the given regex > j - go to the next undecided hunk > - J - go to the next hunk > + J - go to the next hunk, roll over at the bottom > k - go to the previous undecided hunk > K - go to the previous hunk > s - split the current hunk into smaller hunks > diff --git a/add-patch.c b/add-patch.c > index 912266a3f8..bef2ba7a25 100644 > --- a/add-patch.c > +++ b/add-patch.c > @@ -1398,7 +1398,7 @@ static size_t display_hunks(struct add_p_state *s, > > static const char help_patch_remainder[] = > N_("j - go to the next undecided hunk\n" > - "J - go to the next hunk\n" > + "J - go to the next hunk, roll over at the bottom\n" > "k - go to the previous undecided hunk\n" > "K - go to the previous hunk\n" > "g - select a hunk to go to\n" > @@ -1493,7 +1493,7 @@ static int patch_update_file(struct add_p_state *s, > permitted |= ALLOW_GOTO_NEXT_UNDECIDED_HUNK; > strbuf_addstr(&s->buf, ",j"); > } > - if (hunk_index + 1 < file_diff->hunk_nr) { > + if (file_diff->hunk_nr > 1) { > permitted |= ALLOW_GOTO_NEXT_HUNK; > strbuf_addstr(&s->buf, ",J"); > } > diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh > index d9fe289a7a..d5d2e120ab 100755 > --- a/t/t3701-add-interactive.sh > +++ b/t/t3701-add-interactive.sh > @@ -334,7 +334,7 @@ test_expect_success 'different prompts for mode change/deleted' ' > cat >expect <<-\EOF && > (1/1) Stage deletion [y,n,q,a,d,p,?]? > (1/2) Stage mode change [y,n,q,a,d,j,J,g,/,p,?]? > - (2/2) Stage this hunk [y,n,q,a,d,K,g,/,e,p,?]? > + (2/2) Stage this hunk [y,n,q,a,d,K,J,g,/,e,p,?]? > EOF > test_cmp expect actual.filtered > ' > @@ -521,7 +521,7 @@ test_expect_success 'split hunk setup' ' > test_expect_success 'goto hunk 1 with "g 1"' ' > test_when_finished "git reset" && > tr _ " " >expect <<-EOF && > - (2/2) Stage this hunk [y,n,q,a,d,K,g,/,e,p,?]? + 1: -1,2 +1,3 +15 > + (2/2) Stage this hunk [y,n,q,a,d,K,J,g,/,e,p,?]? + 1: -1,2 +1,3 +15 > _ 2: -2,4 +3,8 +21 > go to which hunk? @@ -1,2 +1,3 @@ > _10 > @@ -550,7 +550,7 @@ test_expect_success 'goto hunk 1 with "g1"' ' > test_expect_success 'navigate to hunk via regex /pattern' ' > test_when_finished "git reset" && > tr _ " " >expect <<-EOF && > - (2/2) Stage this hunk [y,n,q,a,d,K,g,/,e,p,?]? @@ -1,2 +1,3 @@ > + (2/2) Stage this hunk [y,n,q,a,d,K,J,g,/,e,p,?]? @@ -1,2 +1,3 @@ > _10 > +15 > _20 > @@ -805,7 +805,7 @@ test_expect_success 'colors can be overridden' ' > <YELLOW>(1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,p,?]? <RESET><MAGENTA>@@ -3 +3,2 @@<RESET> > <CYAN> more-context<RESET> > <BLUE>+<RESET><BLUE>another-one<RESET> > - <YELLOW>(2/2) Stage this hunk [y,n,q,a,d,K,g,/,e,p,?]? <RESET><MAGENTA>@@ -1,3 +1,3 @@<RESET> > + <YELLOW>(2/2) Stage this hunk [y,n,q,a,d,K,J,g,/,e,p,?]? <RESET><MAGENTA>@@ -1,3 +1,3 @@<RESET> > <CYAN> context<RESET> > <BOLD>-old<RESET> > <BLUE>+new<RESET> > @@ -1354,4 +1354,14 @@ do > ' > done > > +test_expect_success 'option J rolls over' ' > + test_write_lines a b c d e f g h i >file && > + git add file && > + test_write_lines X b c d e f g h X >file && > + test_write_lines J J q | git add -p >out && > + test_write_lines 1 2 1 >expect && > + sed -n -e "s-/.*--" -e "s/^(//p" <out >actual && > + test_cmp expect actual > +' > + > test_done ^ permalink raw reply [flat|nested] 37+ messages in thread
* [PATCH v2 3/5] add-patch: let options y, n, j, and e roll over to next undecided 2025-10-05 15:45 ` [PATCH v2 0/5] " René Scharfe 2025-10-05 15:55 ` [PATCH v2 1/5] add-patch: improve help for options j, J, k, and K René Scharfe 2025-10-05 15:55 ` [PATCH v2 2/5] add-patch: document that option J rolls over René Scharfe @ 2025-10-05 15:55 ` René Scharfe 2025-10-05 15:55 ` [PATCH v2 4/5] add-patch: let options k and K roll over like j and J René Scharfe 2025-10-05 15:55 ` [PATCH v2 5/5] add-patch: reset "permitted" at loop start René Scharfe 4 siblings, 0 replies; 37+ messages in thread From: René Scharfe @ 2025-10-05 15:55 UTC (permalink / raw) To: git@vger.kernel.org; +Cc: Windl, Ulrich, Junio C Hamano, Phillip Wood The options y, n, and e mark the current hunk as decided. If there's another undecided hunk towards the bottom of the hunk array they go there. If there isn't, but there is another undecided hunk towards the top then they go to the very first hunk, no matter if it has already been decided on. The option j does basically the same move. Technically it is not allowed if there's no undecided hunk towards the bottom, but the variable "permitted" is never reset, so this permission is retained from the very first hunk. That may a bug, but this behavior is at least consistent with y, n, and e and arguably more useful than refusing to move. Improve the roll-over behavior of these four options by moving to the first undecided hunk instead of hunk 1, consistent with what they do when not rolling over. Reported-by: Windl, Ulrich <u.windl@ukr.de> Signed-off-by: René Scharfe <l.s.r@web.de> --- Documentation/git-add.adoc | 2 +- add-patch.c | 11 +++++++++-- t/t3701-add-interactive.sh | 22 ++++++++++++++++++++++ 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/Documentation/git-add.adoc b/Documentation/git-add.adoc index 5c05a3a7f9..596cdeff93 100644 --- a/Documentation/git-add.adoc +++ b/Documentation/git-add.adoc @@ -342,7 +342,7 @@ patch:: d - do not stage this hunk or any of the later hunks in the file g - select a hunk to go to / - search for a hunk matching the given regex - j - go to the next undecided hunk + j - go to the next undecided hunk, roll over at the bottom J - go to the next hunk, roll over at the bottom k - go to the previous undecided hunk K - go to the previous hunk diff --git a/add-patch.c b/add-patch.c index bef2ba7a25..da75618dcb 100644 --- a/add-patch.c +++ b/add-patch.c @@ -1397,7 +1397,7 @@ static size_t display_hunks(struct add_p_state *s, } static const char help_patch_remainder[] = -N_("j - go to the next undecided hunk\n" +N_("j - go to the next undecided hunk, roll over at the bottom\n" "J - go to the next hunk, roll over at the bottom\n" "k - go to the previous undecided hunk\n" "K - go to the previous hunk\n" @@ -1408,6 +1408,11 @@ N_("j - go to the next undecided hunk\n" "p - print the current hunk, 'P' to use the pager\n" "? - print help\n"); +static size_t inc_mod(size_t a, size_t m) +{ + return a < m - 1 ? a + 1 : 0; +} + static int patch_update_file(struct add_p_state *s, struct file_diff *file_diff) { @@ -1451,7 +1456,9 @@ static int patch_update_file(struct add_p_state *s, break; } - for (i = hunk_index + 1; i < file_diff->hunk_nr; i++) + for (i = inc_mod(hunk_index, file_diff->hunk_nr); + i != hunk_index; + i = inc_mod(i, file_diff->hunk_nr)) if (file_diff->hunk[i].use == UNDECIDED_HUNK) { undecided_next = i; break; diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh index d5d2e120ab..8086d3da71 100755 --- a/t/t3701-add-interactive.sh +++ b/t/t3701-add-interactive.sh @@ -1364,4 +1364,26 @@ test_expect_success 'option J rolls over' ' test_cmp expect actual ' +test_expect_success 'options y, n, j, e roll over to next undecided (1)' ' + test_write_lines a b c d e f g h i j k l m n o p q >file && + git add file && + test_write_lines X b c d e f g h X j k l m n o p X >file && + test_set_editor : && + test_write_lines g3 y g3 n g3 j g3 e q | git add -p >out && + test_write_lines 1 3 1 3 1 3 1 3 1 >expect && + sed -n -e "s-/.*--" -e "s/^(//p" <out >actual && + test_cmp expect actual +' + +test_expect_success 'options y, n, j, e roll over to next undecided (2)' ' + test_write_lines a b c d e f g h i j k l m n o p q >file && + git add file && + test_write_lines X b c d e f g h X j k l m n o p X >file && + test_set_editor : && + test_write_lines y g3 y g3 n g3 j g3 e q | git add -p >out && + test_write_lines 1 2 3 2 3 2 3 2 3 2 >expect && + sed -n -e "s-/.*--" -e "s/^(//p" <out >actual && + test_cmp expect actual +' + test_done -- 2.51.0 ^ permalink raw reply related [flat|nested] 37+ messages in thread
* [PATCH v2 4/5] add-patch: let options k and K roll over like j and J 2025-10-05 15:45 ` [PATCH v2 0/5] " René Scharfe ` (2 preceding siblings ...) 2025-10-05 15:55 ` [PATCH v2 3/5] add-patch: let options y, n, j, and e roll over to next undecided René Scharfe @ 2025-10-05 15:55 ` René Scharfe 2025-10-05 20:55 ` Junio C Hamano 2025-10-05 15:55 ` [PATCH v2 5/5] add-patch: reset "permitted" at loop start René Scharfe 4 siblings, 1 reply; 37+ messages in thread From: René Scharfe @ 2025-10-05 15:55 UTC (permalink / raw) To: git@vger.kernel.org; +Cc: Windl, Ulrich, Junio C Hamano, Phillip Wood Options j and J roll over at the bottom and go to the first undecided hunk and hunk 1, respectively. Let options k and K do the same when they reach the top of the hunk array, so let them go to the last undecided hunk and the last hunk, respectively, for consistency. Signed-off-by: René Scharfe <l.s.r@web.de> --- Documentation/git-add.adoc | 4 ++-- add-patch.c | 18 ++++++++++++----- t/t3701-add-interactive.sh | 40 +++++++++++++++++++------------------- 3 files changed, 35 insertions(+), 27 deletions(-) diff --git a/Documentation/git-add.adoc b/Documentation/git-add.adoc index 596cdeff93..3116a2cac5 100644 --- a/Documentation/git-add.adoc +++ b/Documentation/git-add.adoc @@ -344,8 +344,8 @@ patch:: / - search for a hunk matching the given regex j - go to the next undecided hunk, roll over at the bottom J - go to the next hunk, roll over at the bottom - k - go to the previous undecided hunk - K - go to the previous hunk + k - go to the previous undecided hunk, roll over at the top + K - go to the previous hunk, roll over at the top s - split the current hunk into smaller hunks e - manually edit the current hunk p - print the current hunk diff --git a/add-patch.c b/add-patch.c index da75618dcb..52e881d3b0 100644 --- a/add-patch.c +++ b/add-patch.c @@ -1399,8 +1399,8 @@ static size_t display_hunks(struct add_p_state *s, static const char help_patch_remainder[] = N_("j - go to the next undecided hunk, roll over at the bottom\n" "J - go to the next hunk, roll over at the bottom\n" - "k - go to the previous undecided hunk\n" - "K - go to the previous hunk\n" + "k - go to the previous undecided hunk, roll over at the top\n" + "K - go to the previous hunk, roll over at the top\n" "g - select a hunk to go to\n" "/ - search for a hunk matching the given regex\n" "s - split the current hunk into smaller hunks\n" @@ -1408,6 +1408,11 @@ N_("j - go to the next undecided hunk, roll over at the bottom\n" "p - print the current hunk, 'P' to use the pager\n" "? - print help\n"); +static size_t dec_mod(size_t a, size_t m) +{ + return a > 0 ? a - 1 : m - 1; +} + static size_t inc_mod(size_t a, size_t m) { return a < m - 1 ? a + 1 : 0; @@ -1450,7 +1455,9 @@ static int patch_update_file(struct add_p_state *s, undecided_next = -1; if (file_diff->hunk_nr) { - for (i = hunk_index - 1; i >= 0; i--) + for (i = dec_mod(hunk_index, file_diff->hunk_nr); + i != hunk_index; + i = dec_mod(i, file_diff->hunk_nr)) if (file_diff->hunk[i].use == UNDECIDED_HUNK) { undecided_previous = i; break; @@ -1492,7 +1499,7 @@ static int patch_update_file(struct add_p_state *s, permitted |= ALLOW_GOTO_PREVIOUS_UNDECIDED_HUNK; strbuf_addstr(&s->buf, ",k"); } - if (hunk_index) { + if (file_diff->hunk_nr > 1) { permitted |= ALLOW_GOTO_PREVIOUS_HUNK; strbuf_addstr(&s->buf, ",K"); } @@ -1584,7 +1591,8 @@ static int patch_update_file(struct add_p_state *s, } } else if (s->answer.buf[0] == 'K') { if (permitted & ALLOW_GOTO_PREVIOUS_HUNK) - hunk_index--; + hunk_index = dec_mod(hunk_index, + file_diff->hunk_nr); else err(s, _("No previous hunk")); } else if (s->answer.buf[0] == 'J') { diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh index 8086d3da71..385e55c783 100755 --- a/t/t3701-add-interactive.sh +++ b/t/t3701-add-interactive.sh @@ -333,7 +333,7 @@ test_expect_success 'different prompts for mode change/deleted' ' sed -n "s/^\(([0-9/]*) Stage .*?\).*/\1/p" actual >actual.filtered && cat >expect <<-\EOF && (1/1) Stage deletion [y,n,q,a,d,p,?]? - (1/2) Stage mode change [y,n,q,a,d,j,J,g,/,p,?]? + (1/2) Stage mode change [y,n,q,a,d,k,K,j,J,g,/,p,?]? (2/2) Stage this hunk [y,n,q,a,d,K,J,g,/,e,p,?]? EOF test_cmp expect actual.filtered @@ -527,7 +527,7 @@ test_expect_success 'goto hunk 1 with "g 1"' ' _10 +15 _20 - (1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,p,?]?_ + (1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,?]?_ EOF test_write_lines s y g 1 | git add -p >actual && tail -n 7 <actual >actual.trimmed && @@ -540,7 +540,7 @@ test_expect_success 'goto hunk 1 with "g1"' ' _10 +15 _20 - (1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,p,?]?_ + (1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,?]?_ EOF test_write_lines s y g1 | git add -p >actual && tail -n 4 <actual >actual.trimmed && @@ -554,7 +554,7 @@ test_expect_success 'navigate to hunk via regex /pattern' ' _10 +15 _20 - (1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,p,?]?_ + (1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,?]?_ EOF test_write_lines s y /1,2 | git add -p >actual && tail -n 5 <actual >actual.trimmed && @@ -567,7 +567,7 @@ test_expect_success 'navigate to hunk via regex / pattern' ' _10 +15 _20 - (1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,p,?]?_ + (1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,?]?_ EOF test_write_lines s y / 1,2 | git add -p >actual && tail -n 4 <actual >actual.trimmed && @@ -579,11 +579,11 @@ test_expect_success 'print again the hunk' ' tr _ " " >expect <<-EOF && +15 20 - (1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,p,?]? @@ -1,2 +1,3 @@ + (1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,?]? @@ -1,2 +1,3 @@ 10 +15 20 - (1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,p,?]?_ + (1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,?]?_ EOF test_write_lines s y g 1 p | git add -p >actual && tail -n 7 <actual >actual.trimmed && @@ -595,11 +595,11 @@ test_expect_success TTY 'print again the hunk (PAGER)' ' cat >expect <<-EOF && <GREEN>+<RESET><GREEN>15<RESET> 20<RESET> - <BOLD;BLUE>(1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,p,?]? <RESET>PAGER <CYAN>@@ -1,2 +1,3 @@<RESET> + <BOLD;BLUE>(1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,?]? <RESET>PAGER <CYAN>@@ -1,2 +1,3 @@<RESET> PAGER 10<RESET> PAGER <GREEN>+<RESET><GREEN>15<RESET> PAGER 20<RESET> - <BOLD;BLUE>(1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,p,?]? <RESET> + <BOLD;BLUE>(1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,?]? <RESET> EOF test_write_lines s y g 1 P | ( @@ -802,7 +802,7 @@ test_expect_success 'colors can be overridden' ' <BOLD>-old<RESET> <BLUE>+<RESET><BLUE>new<RESET> <CYAN> more-context<RESET> - <YELLOW>(1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,p,?]? <RESET><MAGENTA>@@ -3 +3,2 @@<RESET> + <YELLOW>(1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,?]? <RESET><MAGENTA>@@ -3 +3,2 @@<RESET> <CYAN> more-context<RESET> <BLUE>+<RESET><BLUE>another-one<RESET> <YELLOW>(2/2) Stage this hunk [y,n,q,a,d,K,J,g,/,e,p,?]? <RESET><MAGENTA>@@ -1,3 +1,3 @@<RESET> @@ -810,7 +810,7 @@ test_expect_success 'colors can be overridden' ' <BOLD>-old<RESET> <BLUE>+new<RESET> <CYAN> more-context<RESET> - <YELLOW>(1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,p,?]? <RESET> + <YELLOW>(1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,?]? <RESET> EOF test_cmp expect actual ' @@ -1354,34 +1354,34 @@ do ' done -test_expect_success 'option J rolls over' ' +test_expect_success 'options J, K roll over' ' test_write_lines a b c d e f g h i >file && git add file && test_write_lines X b c d e f g h X >file && - test_write_lines J J q | git add -p >out && - test_write_lines 1 2 1 >expect && + test_write_lines J J K q | git add -p >out && + test_write_lines 1 2 1 2 >expect && sed -n -e "s-/.*--" -e "s/^(//p" <out >actual && test_cmp expect actual ' -test_expect_success 'options y, n, j, e roll over to next undecided (1)' ' +test_expect_success 'options y, n, j, k, e roll over to next undecided (1)' ' test_write_lines a b c d e f g h i j k l m n o p q >file && git add file && test_write_lines X b c d e f g h X j k l m n o p X >file && test_set_editor : && - test_write_lines g3 y g3 n g3 j g3 e q | git add -p >out && - test_write_lines 1 3 1 3 1 3 1 3 1 >expect && + test_write_lines g3 y g3 n g3 j g3 e k q | git add -p >out && + test_write_lines 1 3 1 3 1 3 1 3 1 2 >expect && sed -n -e "s-/.*--" -e "s/^(//p" <out >actual && test_cmp expect actual ' -test_expect_success 'options y, n, j, e roll over to next undecided (2)' ' +test_expect_success 'options y, n, j, k, e roll over to next undecided (2)' ' test_write_lines a b c d e f g h i j k l m n o p q >file && git add file && test_write_lines X b c d e f g h X j k l m n o p X >file && test_set_editor : && - test_write_lines y g3 y g3 n g3 j g3 e q | git add -p >out && - test_write_lines 1 2 3 2 3 2 3 2 3 2 >expect && + test_write_lines y g3 y g3 n g3 j g3 e g1 k q | git add -p >out && + test_write_lines 1 2 3 2 3 2 3 2 3 2 1 2 >expect && sed -n -e "s-/.*--" -e "s/^(//p" <out >actual && test_cmp expect actual ' -- 2.51.0 ^ permalink raw reply related [flat|nested] 37+ messages in thread
* Re: [PATCH v2 4/5] add-patch: let options k and K roll over like j and J 2025-10-05 15:55 ` [PATCH v2 4/5] add-patch: let options k and K roll over like j and J René Scharfe @ 2025-10-05 20:55 ` Junio C Hamano 2025-10-06 17:18 ` René Scharfe 0 siblings, 1 reply; 37+ messages in thread From: Junio C Hamano @ 2025-10-05 20:55 UTC (permalink / raw) To: René Scharfe; +Cc: git@vger.kernel.org, Windl, Ulrich, Phillip Wood René Scharfe <l.s.r@web.de> writes: > @@ -1584,7 +1591,8 @@ static int patch_update_file(struct add_p_state *s, > } > } else if (s->answer.buf[0] == 'K') { > if (permitted & ALLOW_GOTO_PREVIOUS_HUNK) > - hunk_index--; > + hunk_index = dec_mod(hunk_index, > + file_diff->hunk_nr); > else > err(s, _("No previous hunk")); I was wondering if we want to always allow J and K; even when you have only one hunk, you can still wrap around to come back to the current hunk, and that we can do without any extra checking logic. But it is also OK to require 2 or more hunks to "switch" to the other hunk, which is what you do with if (file_diff->hunk_nr > 1) { permitted |= ALLOW_GOTO_PREVIOUS_HUNK; strbuf_addstr(&s->buf, ",K"); } to require more than 1. But the error message "No previous hunk" sounds somewhat awkward. If user accepts the circular nature of how we decide what "previous" is, then when we have a single hunk, the current hunk itself _is_ the previous hunk, but because we insist that there are at least 2, that interpretation would not work. With "wraparound" semantics, "No other hunk(s)", would be a better way to give the error, no? The same comment applies to 'J'. > } else if (s->answer.buf[0] == 'J') { This makes perfect sense, but then, after this post-context we have this: if (permitted & ALLOW_GOTO_NEXT_HUNK) hunk_index++; else err(s, _("No next hunk")); and it sticks out that the post-increment of hunk_index here is not using inc_mod() for symmetry. I am wondering if with that updated (I would not say "fixed"), if we can lose the "oops we overflowed so let's wrap around" belt-and-suspender code at the beginning of the loop, i.e. for (;;) { enum { ALLOW_GOTO_PREVIOUS_HUNK = 1 << 0, ... ALLOW_EDIT = 1 << 6 } permitted = 0; if (hunk_index >= file_diff->hunk_nr) hunk_index = 0; or if there still are other code that rely on this "oops we overflowed" adjustment? Other than that the resulting code with the whole series applied was a very pleasant read. Thanks. ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH v2 4/5] add-patch: let options k and K roll over like j and J 2025-10-05 20:55 ` Junio C Hamano @ 2025-10-06 17:18 ` René Scharfe 0 siblings, 0 replies; 37+ messages in thread From: René Scharfe @ 2025-10-06 17:18 UTC (permalink / raw) To: Junio C Hamano; +Cc: git@vger.kernel.org, Windl, Ulrich, Phillip Wood On 10/5/25 10:55 PM, Junio C Hamano wrote: > René Scharfe <l.s.r@web.de> writes: > >> @@ -1584,7 +1591,8 @@ static int patch_update_file(struct add_p_state *s, >> } >> } else if (s->answer.buf[0] == 'K') { >> if (permitted & ALLOW_GOTO_PREVIOUS_HUNK) >> - hunk_index--; >> + hunk_index = dec_mod(hunk_index, >> + file_diff->hunk_nr); >> else >> err(s, _("No previous hunk")); > > I was wondering if we want to always allow J and K; even when you > have only one hunk, you can still wrap around to come back to the > current hunk, and that we can do without any extra checking logic. > > But it is also OK to require 2 or more hunks to "switch" to the > other hunk, which is what you do with > > if (file_diff->hunk_nr > 1) { > permitted |= ALLOW_GOTO_PREVIOUS_HUNK; > strbuf_addstr(&s->buf, ",K"); > } > > to require more than 1. But the error message "No previous hunk" > sounds somewhat awkward. If user accepts the circular nature of how > we decide what "previous" is, then when we have a single hunk, the > current hunk itself _is_ the previous hunk, but because we insist > that there are at least 2, that interpretation would not work. With > "wraparound" semantics, "No other hunk(s)", would be a better way to > give the error, no? The same comment applies to 'J'. OK. >> } else if (s->answer.buf[0] == 'J') { > > This makes perfect sense, but then, after this post-context we have this: > > if (permitted & ALLOW_GOTO_NEXT_HUNK) > hunk_index++; > else > err(s, _("No next hunk")); > > and it sticks out that the post-increment of hunk_index here is not > using inc_mod() for symmetry. This symmetry _is_ tantalizing. Had the call originally, removed it because it was unnecessary and didn't fit the narrative. > I am wondering if with that updated (I would not say "fixed"), if we > can lose the "oops we overflowed so let's wrap around" belt-and-suspender > code at the beginning of the loop, i.e. > > for (;;) { > enum { > ALLOW_GOTO_PREVIOUS_HUNK = 1 << 0, > ... > ALLOW_EDIT = 1 << 6 > } permitted = 0; > > if (hunk_index >= file_diff->hunk_nr) > hunk_index = 0; > > or if there still are other code that rely on this "oops we > overflowed" adjustment? Good question, gave me the idea that a and d should roll over as well. Other than that there's just the so-called soft_increment, which would need something like this: diff --git a/add-patch.c b/add-patch.c index b0389c5d5b..59a9eb586d 100644 --- a/add-patch.c +++ b/add-patch.c @@ -1546,8 +1546,7 @@ 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; + hunk_index = undecided_next < 0 ? 0 : undecided_next; } else if (ch == 'n') { hunk->use = SKIP_HUNK; goto soft_increment; Or undecided_next could be set to 0 before the if/else cascade, then this becomes a simple assignment and we can get rid of the goto. Couldn't find a way to remove the back-to-square-1 check that would be significantly better overall and thus worth the hassle, though. René ^ permalink raw reply related [flat|nested] 37+ messages in thread
* [PATCH v2 5/5] add-patch: reset "permitted" at loop start 2025-10-05 15:45 ` [PATCH v2 0/5] " René Scharfe ` (3 preceding siblings ...) 2025-10-05 15:55 ` [PATCH v2 4/5] add-patch: let options k and K roll over like j and J René Scharfe @ 2025-10-05 15:55 ` René Scharfe 4 siblings, 0 replies; 37+ messages in thread From: René Scharfe @ 2025-10-05 15:55 UTC (permalink / raw) To: git@vger.kernel.org; +Cc: Windl, Ulrich, Junio C Hamano, Phillip Wood Don't accumulate allowed options from any visited hunks, start fresh at the top of the loop instead and only record the allowed options for the current hunk. Reported-by: Junio C Hamano <gitster@pobox.com> Signed-off-by: René Scharfe <l.s.r@web.de> --- add-patch.c | 19 ++++++++++--------- t/t3701-add-interactive.sh | 14 ++++++++++++++ 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/add-patch.c b/add-patch.c index 52e881d3b0..7b489d0a75 100644 --- a/add-patch.c +++ b/add-patch.c @@ -1428,15 +1428,6 @@ static int patch_update_file(struct add_p_state *s, struct child_process cp = CHILD_PROCESS_INIT; int colored = !!s->colored.len, quit = 0, use_pager = 0; enum prompt_mode_type prompt_mode_type; - enum { - ALLOW_GOTO_PREVIOUS_HUNK = 1 << 0, - ALLOW_GOTO_PREVIOUS_UNDECIDED_HUNK = 1 << 1, - ALLOW_GOTO_NEXT_HUNK = 1 << 2, - ALLOW_GOTO_NEXT_UNDECIDED_HUNK = 1 << 3, - ALLOW_SEARCH_AND_GOTO = 1 << 4, - ALLOW_SPLIT = 1 << 5, - ALLOW_EDIT = 1 << 6 - } permitted = 0; /* Empty added files have no hunks */ if (!file_diff->hunk_nr && !file_diff->added) @@ -1446,6 +1437,16 @@ static int patch_update_file(struct add_p_state *s, render_diff_header(s, file_diff, colored, &s->buf); fputs(s->buf.buf, stdout); for (;;) { + enum { + ALLOW_GOTO_PREVIOUS_HUNK = 1 << 0, + ALLOW_GOTO_PREVIOUS_UNDECIDED_HUNK = 1 << 1, + ALLOW_GOTO_NEXT_HUNK = 1 << 2, + ALLOW_GOTO_NEXT_UNDECIDED_HUNK = 1 << 3, + ALLOW_SEARCH_AND_GOTO = 1 << 4, + ALLOW_SPLIT = 1 << 5, + ALLOW_EDIT = 1 << 6 + } permitted = 0; + if (hunk_index >= file_diff->hunk_nr) hunk_index = 0; hunk = file_diff->hunk_nr diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh index 385e55c783..8c24a76e59 100755 --- a/t/t3701-add-interactive.sh +++ b/t/t3701-add-interactive.sh @@ -1386,4 +1386,18 @@ test_expect_success 'options y, n, j, k, e roll over to next undecided (2)' ' test_cmp expect actual ' +test_expect_success 'invalid option s is rejected' ' + test_write_lines a b c d e f g h i j k >file && + git add file && + test_write_lines X b X d e f g h i j X >file && + test_write_lines j s q | git add -p >out && + sed -ne "s/ @@.*//" -e "s/ \$//" -e "/^(/p" <out >actual && + cat >expect <<-EOF && + (1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,s,e,p,?]? + (2/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,?]? Sorry, cannot split this hunk + (2/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,?]? + EOF + test_cmp expect actual +' + test_done -- 2.51.0 ^ permalink raw reply related [flat|nested] 37+ messages in thread
* [PATCH v3 0/6] add-patch: roll over to next undecided hunk 2025-10-02 9:23 Broken handling of "J" hunks for "add --interactive"? Windl, Ulrich 2025-10-03 12:16 ` [PATCH] add-patch: roll over to next undecided hunk René Scharfe 2025-10-05 15:45 ` [PATCH v2 0/5] " René Scharfe @ 2025-10-06 17:18 ` René Scharfe 2025-10-06 17:19 ` [PATCH v3 1/6] add-patch: improve help for options j, J, k, and K René Scharfe ` (6 more replies) 2 siblings, 7 replies; 37+ messages in thread From: René Scharfe @ 2025-10-06 17:18 UTC (permalink / raw) To: git@vger.kernel.org; +Cc: Windl, Ulrich, Junio C Hamano, Phillip Wood Changes since v1: - added patch 5 for a and d - made error messages direction-neutral - removed stray "only" from commit message of patch 2 add-patch: improve help for options j, J, k, and K add-patch: document that option J rolls over add-patch: let options y, n, j, and e roll over to next undecided add-patch: let options k and K roll over like j and J add-patch: let options a and d roll over like y and n add-patch: reset "permitted" at loop start Documentation/git-add.adoc | 8 ++-- add-patch.c | 75 ++++++++++++++++++++++++++----------- t/t3701-add-interactive.sh | 76 ++++++++++++++++++++++++++++++-------- 3 files changed, 118 insertions(+), 41 deletions(-) Interdiff against v2: diff --git a/add-patch.c b/add-patch.c index 7b489d0a75..45839ceac5 100644 --- a/add-patch.c +++ b/add-patch.c @@ -1418,6 +1418,17 @@ static size_t inc_mod(size_t a, size_t m) return a < m - 1 ? a + 1 : 0; } +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++) { + if (file_diff->hunk[i].use == UNDECIDED_HUNK) { + *idx = i; + return true; + } + } + return false; +} + static int patch_update_file(struct add_p_state *s, struct file_diff *file_diff) { @@ -1573,6 +1584,8 @@ static int patch_update_file(struct add_p_state *s, if (hunk->use == UNDECIDED_HUNK) hunk->use = USE_HUNK; } + if (!get_first_undecided(file_diff, &hunk_index)) + hunk_index = 0; } else if (hunk->use == UNDECIDED_HUNK) { hunk->use = USE_HUNK; } @@ -1583,6 +1596,8 @@ static int patch_update_file(struct add_p_state *s, if (hunk->use == UNDECIDED_HUNK) hunk->use = SKIP_HUNK; } + if (!get_first_undecided(file_diff, &hunk_index)) + hunk_index = 0; } else if (hunk->use == UNDECIDED_HUNK) { hunk->use = SKIP_HUNK; } @@ -1595,22 +1610,22 @@ static int patch_update_file(struct add_p_state *s, hunk_index = dec_mod(hunk_index, file_diff->hunk_nr); else - err(s, _("No previous hunk")); + err(s, _("No other hunk")); } else if (s->answer.buf[0] == 'J') { if (permitted & ALLOW_GOTO_NEXT_HUNK) hunk_index++; else - err(s, _("No next hunk")); + err(s, _("No other hunk")); } else if (s->answer.buf[0] == 'k') { if (permitted & ALLOW_GOTO_PREVIOUS_UNDECIDED_HUNK) hunk_index = undecided_previous; else - err(s, _("No previous hunk")); + err(s, _("No other undecided hunk")); } else if (s->answer.buf[0] == 'j') { if (permitted & ALLOW_GOTO_NEXT_UNDECIDED_HUNK) hunk_index = undecided_next; else - err(s, _("No next hunk")); + err(s, _("No other undecided hunk")); } else if (s->answer.buf[0] == 'g') { char *pend; unsigned long response; diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh index 8c24a76e59..403aaee356 100755 --- a/t/t3701-add-interactive.sh +++ b/t/t3701-add-interactive.sh @@ -1364,24 +1364,24 @@ test_expect_success 'options J, K roll over' ' test_cmp expect actual ' -test_expect_success 'options y, n, j, k, e roll over to next undecided (1)' ' +test_expect_success 'options y, n, a, d, j, k, e roll over to next undecided (1)' ' test_write_lines a b c d e f g h i j k l m n o p q >file && git add file && test_write_lines X b c d e f g h X j k l m n o p X >file && test_set_editor : && - test_write_lines g3 y g3 n g3 j g3 e k q | git add -p >out && - test_write_lines 1 3 1 3 1 3 1 3 1 2 >expect && + test_write_lines g3 y g3 n g3 a g3 d g3 j g3 e k q | git add -p >out && + test_write_lines 1 3 1 3 1 3 1 3 1 3 1 3 1 2 >expect && sed -n -e "s-/.*--" -e "s/^(//p" <out >actual && test_cmp expect actual ' -test_expect_success 'options y, n, j, k, e roll over to next undecided (2)' ' +test_expect_success 'options y, n, a, d, j, k, e roll over to next undecided (2)' ' test_write_lines a b c d e f g h i j k l m n o p q >file && git add file && test_write_lines X b c d e f g h X j k l m n o p X >file && test_set_editor : && - test_write_lines y g3 y g3 n g3 j g3 e g1 k q | git add -p >out && - test_write_lines 1 2 3 2 3 2 3 2 3 2 1 2 >expect && + test_write_lines y g3 y g3 n g3 a g3 d g3 j g3 e g1 k q | git add -p >out && + test_write_lines 1 2 3 2 3 2 3 2 3 2 3 2 3 2 1 2 >expect && sed -n -e "s-/.*--" -e "s/^(//p" <out >actual && test_cmp expect actual ' -- 2.51.0 ^ permalink raw reply related [flat|nested] 37+ messages in thread
* [PATCH v3 1/6] add-patch: improve help for options j, J, k, and K 2025-10-06 17:18 ` [PATCH v3 0/6] add-patch: roll over to next undecided hunk René Scharfe @ 2025-10-06 17:19 ` René Scharfe 2025-10-06 17:20 ` [PATCH v3 2/6] add-patch: document that option J rolls over René Scharfe ` (5 subsequent siblings) 6 siblings, 0 replies; 37+ messages in thread From: René Scharfe @ 2025-10-06 17:19 UTC (permalink / raw) To: git@vger.kernel.org; +Cc: Windl, Ulrich, Junio C Hamano, Phillip Wood The options j, J, k, and K don't affect the status of the current hunk. They just go to a different one. This is true whether the current hunk is undecided or not. Avoid misunderstanding by no longer mentioning the current hunk explicitly in their help texts. Signed-off-by: René Scharfe <l.s.r@web.de> --- Documentation/git-add.adoc | 8 ++++---- add-patch.c | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Documentation/git-add.adoc b/Documentation/git-add.adoc index ad629c46c5..3266ccf105 100644 --- a/Documentation/git-add.adoc +++ b/Documentation/git-add.adoc @@ -342,10 +342,10 @@ patch:: d - do not stage this hunk or any of the later hunks in the file g - select a hunk to go to / - search for a hunk matching the given regex - j - leave this hunk undecided, see next undecided hunk - J - leave this hunk undecided, see next hunk - k - leave this hunk undecided, see previous undecided hunk - K - leave this hunk undecided, see previous hunk + j - go to the next undecided hunk + J - go to the next hunk + k - go to the previous undecided hunk + K - go to the previous hunk s - split the current hunk into smaller hunks e - manually edit the current hunk p - print the current hunk diff --git a/add-patch.c b/add-patch.c index b0389c5d5b..912266a3f8 100644 --- a/add-patch.c +++ b/add-patch.c @@ -1397,10 +1397,10 @@ static size_t display_hunks(struct add_p_state *s, } static const char help_patch_remainder[] = -N_("j - leave this hunk undecided, see next undecided hunk\n" - "J - leave this hunk undecided, see next hunk\n" - "k - leave this hunk undecided, see previous undecided hunk\n" - "K - leave this hunk undecided, see previous hunk\n" +N_("j - go to the next undecided hunk\n" + "J - go to the next hunk\n" + "k - go to the previous undecided hunk\n" + "K - go to the previous hunk\n" "g - select a hunk to go to\n" "/ - search for a hunk matching the given regex\n" "s - split the current hunk into smaller hunks\n" -- 2.51.0 ^ permalink raw reply related [flat|nested] 37+ messages in thread
* [PATCH v3 2/6] add-patch: document that option J rolls over 2025-10-06 17:18 ` [PATCH v3 0/6] add-patch: roll over to next undecided hunk René Scharfe 2025-10-06 17:19 ` [PATCH v3 1/6] add-patch: improve help for options j, J, k, and K René Scharfe @ 2025-10-06 17:20 ` René Scharfe 2025-10-06 17:21 ` [PATCH v3 3/6] add-patch: let options y, n, j, and e roll over to next undecided René Scharfe ` (4 subsequent siblings) 6 siblings, 0 replies; 37+ messages in thread From: René Scharfe @ 2025-10-06 17:20 UTC (permalink / raw) To: git@vger.kernel.org; +Cc: Windl, Ulrich, Junio C Hamano, Phillip Wood The variable "permitted" is not reset after moving to a different hunk, so it only accumulates permission and doesn't necessarily reflect those of the current hunk. This may be a bug, but is actually useful with the option J, which can be used at the last hunk to roll over to the first hunk. Make this particular behavior official. Also adjust the error message, as it will only be shown if there's just a single hunk. Suggested-by: Junio C Hamano <gitster@pobox.com> Signed-off-by: René Scharfe <l.s.r@web.de> --- Documentation/git-add.adoc | 2 +- add-patch.c | 6 +++--- t/t3701-add-interactive.sh | 18 ++++++++++++++---- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/Documentation/git-add.adoc b/Documentation/git-add.adoc index 3266ccf105..5c05a3a7f9 100644 --- a/Documentation/git-add.adoc +++ b/Documentation/git-add.adoc @@ -343,7 +343,7 @@ patch:: g - select a hunk to go to / - search for a hunk matching the given regex j - go to the next undecided hunk - J - go to the next hunk + J - go to the next hunk, roll over at the bottom k - go to the previous undecided hunk K - go to the previous hunk s - split the current hunk into smaller hunks diff --git a/add-patch.c b/add-patch.c index 912266a3f8..1f466ec9c0 100644 --- a/add-patch.c +++ b/add-patch.c @@ -1398,7 +1398,7 @@ static size_t display_hunks(struct add_p_state *s, static const char help_patch_remainder[] = N_("j - go to the next undecided hunk\n" - "J - go to the next hunk\n" + "J - go to the next hunk, roll over at the bottom\n" "k - go to the previous undecided hunk\n" "K - go to the previous hunk\n" "g - select a hunk to go to\n" @@ -1493,7 +1493,7 @@ static int patch_update_file(struct add_p_state *s, permitted |= ALLOW_GOTO_NEXT_UNDECIDED_HUNK; strbuf_addstr(&s->buf, ",j"); } - if (hunk_index + 1 < file_diff->hunk_nr) { + if (file_diff->hunk_nr > 1) { permitted |= ALLOW_GOTO_NEXT_HUNK; strbuf_addstr(&s->buf, ",J"); } @@ -1584,7 +1584,7 @@ static int patch_update_file(struct add_p_state *s, if (permitted & ALLOW_GOTO_NEXT_HUNK) hunk_index++; else - err(s, _("No next hunk")); + err(s, _("No other hunk")); } else if (s->answer.buf[0] == 'k') { if (permitted & ALLOW_GOTO_PREVIOUS_UNDECIDED_HUNK) hunk_index = undecided_previous; diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh index d9fe289a7a..d5d2e120ab 100755 --- a/t/t3701-add-interactive.sh +++ b/t/t3701-add-interactive.sh @@ -334,7 +334,7 @@ test_expect_success 'different prompts for mode change/deleted' ' cat >expect <<-\EOF && (1/1) Stage deletion [y,n,q,a,d,p,?]? (1/2) Stage mode change [y,n,q,a,d,j,J,g,/,p,?]? - (2/2) Stage this hunk [y,n,q,a,d,K,g,/,e,p,?]? + (2/2) Stage this hunk [y,n,q,a,d,K,J,g,/,e,p,?]? EOF test_cmp expect actual.filtered ' @@ -521,7 +521,7 @@ test_expect_success 'split hunk setup' ' test_expect_success 'goto hunk 1 with "g 1"' ' test_when_finished "git reset" && tr _ " " >expect <<-EOF && - (2/2) Stage this hunk [y,n,q,a,d,K,g,/,e,p,?]? + 1: -1,2 +1,3 +15 + (2/2) Stage this hunk [y,n,q,a,d,K,J,g,/,e,p,?]? + 1: -1,2 +1,3 +15 _ 2: -2,4 +3,8 +21 go to which hunk? @@ -1,2 +1,3 @@ _10 @@ -550,7 +550,7 @@ test_expect_success 'goto hunk 1 with "g1"' ' test_expect_success 'navigate to hunk via regex /pattern' ' test_when_finished "git reset" && tr _ " " >expect <<-EOF && - (2/2) Stage this hunk [y,n,q,a,d,K,g,/,e,p,?]? @@ -1,2 +1,3 @@ + (2/2) Stage this hunk [y,n,q,a,d,K,J,g,/,e,p,?]? @@ -1,2 +1,3 @@ _10 +15 _20 @@ -805,7 +805,7 @@ test_expect_success 'colors can be overridden' ' <YELLOW>(1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,p,?]? <RESET><MAGENTA>@@ -3 +3,2 @@<RESET> <CYAN> more-context<RESET> <BLUE>+<RESET><BLUE>another-one<RESET> - <YELLOW>(2/2) Stage this hunk [y,n,q,a,d,K,g,/,e,p,?]? <RESET><MAGENTA>@@ -1,3 +1,3 @@<RESET> + <YELLOW>(2/2) Stage this hunk [y,n,q,a,d,K,J,g,/,e,p,?]? <RESET><MAGENTA>@@ -1,3 +1,3 @@<RESET> <CYAN> context<RESET> <BOLD>-old<RESET> <BLUE>+new<RESET> @@ -1354,4 +1354,14 @@ do ' done +test_expect_success 'option J rolls over' ' + test_write_lines a b c d e f g h i >file && + git add file && + test_write_lines X b c d e f g h X >file && + test_write_lines J J q | git add -p >out && + test_write_lines 1 2 1 >expect && + sed -n -e "s-/.*--" -e "s/^(//p" <out >actual && + test_cmp expect actual +' + test_done -- 2.51.0 ^ permalink raw reply related [flat|nested] 37+ messages in thread
* [PATCH v3 3/6] add-patch: let options y, n, j, and e roll over to next undecided 2025-10-06 17:18 ` [PATCH v3 0/6] add-patch: roll over to next undecided hunk René Scharfe 2025-10-06 17:19 ` [PATCH v3 1/6] add-patch: improve help for options j, J, k, and K René Scharfe 2025-10-06 17:20 ` [PATCH v3 2/6] add-patch: document that option J rolls over René Scharfe @ 2025-10-06 17:21 ` René Scharfe 2025-10-06 17:22 ` [PATCH v3 4/6] add-patch: let options k and K roll over like j and J René Scharfe ` (3 subsequent siblings) 6 siblings, 0 replies; 37+ messages in thread From: René Scharfe @ 2025-10-06 17:21 UTC (permalink / raw) To: git@vger.kernel.org; +Cc: Windl, Ulrich, Junio C Hamano, Phillip Wood The options y, n, and e mark the current hunk as decided. If there's another undecided hunk towards the bottom of the hunk array they go there. If there isn't, but there is another undecided hunk towards the top then they go to the very first hunk, no matter if it has already been decided on. The option j does basically the same move. Technically it is not allowed if there's no undecided hunk towards the bottom, but the variable "permitted" is never reset, so this permission is retained from the very first hunk. That may a bug, but this behavior is at least consistent with y, n, and e and arguably more useful than refusing to move. Improve the roll-over behavior of these four options by moving to the first undecided hunk instead of hunk 1, consistent with what they do when not rolling over. Also adjust the error message for j, as it will only be shown if there's no other undecided hunk in either direction. Reported-by: Windl, Ulrich <u.windl@ukr.de> Signed-off-by: René Scharfe <l.s.r@web.de> --- Documentation/git-add.adoc | 2 +- add-patch.c | 13 ++++++++++--- t/t3701-add-interactive.sh | 22 ++++++++++++++++++++++ 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/Documentation/git-add.adoc b/Documentation/git-add.adoc index 5c05a3a7f9..596cdeff93 100644 --- a/Documentation/git-add.adoc +++ b/Documentation/git-add.adoc @@ -342,7 +342,7 @@ patch:: d - do not stage this hunk or any of the later hunks in the file g - select a hunk to go to / - search for a hunk matching the given regex - j - go to the next undecided hunk + j - go to the next undecided hunk, roll over at the bottom J - go to the next hunk, roll over at the bottom k - go to the previous undecided hunk K - go to the previous hunk diff --git a/add-patch.c b/add-patch.c index 1f466ec9c0..106bfcb275 100644 --- a/add-patch.c +++ b/add-patch.c @@ -1397,7 +1397,7 @@ static size_t display_hunks(struct add_p_state *s, } static const char help_patch_remainder[] = -N_("j - go to the next undecided hunk\n" +N_("j - go to the next undecided hunk, roll over at the bottom\n" "J - go to the next hunk, roll over at the bottom\n" "k - go to the previous undecided hunk\n" "K - go to the previous hunk\n" @@ -1408,6 +1408,11 @@ N_("j - go to the next undecided hunk\n" "p - print the current hunk, 'P' to use the pager\n" "? - print help\n"); +static size_t inc_mod(size_t a, size_t m) +{ + return a < m - 1 ? a + 1 : 0; +} + static int patch_update_file(struct add_p_state *s, struct file_diff *file_diff) { @@ -1451,7 +1456,9 @@ static int patch_update_file(struct add_p_state *s, break; } - for (i = hunk_index + 1; i < file_diff->hunk_nr; i++) + for (i = inc_mod(hunk_index, file_diff->hunk_nr); + i != hunk_index; + i = inc_mod(i, file_diff->hunk_nr)) if (file_diff->hunk[i].use == UNDECIDED_HUNK) { undecided_next = i; break; @@ -1594,7 +1601,7 @@ static int patch_update_file(struct add_p_state *s, if (permitted & ALLOW_GOTO_NEXT_UNDECIDED_HUNK) hunk_index = undecided_next; else - err(s, _("No next hunk")); + err(s, _("No other undecided hunk")); } else if (s->answer.buf[0] == 'g') { char *pend; unsigned long response; diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh index d5d2e120ab..8086d3da71 100755 --- a/t/t3701-add-interactive.sh +++ b/t/t3701-add-interactive.sh @@ -1364,4 +1364,26 @@ test_expect_success 'option J rolls over' ' test_cmp expect actual ' +test_expect_success 'options y, n, j, e roll over to next undecided (1)' ' + test_write_lines a b c d e f g h i j k l m n o p q >file && + git add file && + test_write_lines X b c d e f g h X j k l m n o p X >file && + test_set_editor : && + test_write_lines g3 y g3 n g3 j g3 e q | git add -p >out && + test_write_lines 1 3 1 3 1 3 1 3 1 >expect && + sed -n -e "s-/.*--" -e "s/^(//p" <out >actual && + test_cmp expect actual +' + +test_expect_success 'options y, n, j, e roll over to next undecided (2)' ' + test_write_lines a b c d e f g h i j k l m n o p q >file && + git add file && + test_write_lines X b c d e f g h X j k l m n o p X >file && + test_set_editor : && + test_write_lines y g3 y g3 n g3 j g3 e q | git add -p >out && + test_write_lines 1 2 3 2 3 2 3 2 3 2 >expect && + sed -n -e "s-/.*--" -e "s/^(//p" <out >actual && + test_cmp expect actual +' + test_done -- 2.51.0 ^ permalink raw reply related [flat|nested] 37+ messages in thread
* [PATCH v3 4/6] add-patch: let options k and K roll over like j and J 2025-10-06 17:18 ` [PATCH v3 0/6] add-patch: roll over to next undecided hunk René Scharfe ` (2 preceding siblings ...) 2025-10-06 17:21 ` [PATCH v3 3/6] add-patch: let options y, n, j, and e roll over to next undecided René Scharfe @ 2025-10-06 17:22 ` René Scharfe 2025-10-06 17:23 ` [PATCH v3 5/6] add-patch: let options a and d roll over like y and n René Scharfe ` (2 subsequent siblings) 6 siblings, 0 replies; 37+ messages in thread From: René Scharfe @ 2025-10-06 17:22 UTC (permalink / raw) To: git@vger.kernel.org; +Cc: Windl, Ulrich, Junio C Hamano, Phillip Wood Options j and J roll over at the bottom and go to the first undecided hunk and hunk 1, respectively. Let options k and K do the same when they reach the top of the hunk array, so let them go to the last undecided hunk and the last hunk, respectively, for consistency. Also use the same direction-neutral error messages. Signed-off-by: René Scharfe <l.s.r@web.de> --- Documentation/git-add.adoc | 4 ++-- add-patch.c | 22 ++++++++++++++------- t/t3701-add-interactive.sh | 40 +++++++++++++++++++------------------- 3 files changed, 37 insertions(+), 29 deletions(-) diff --git a/Documentation/git-add.adoc b/Documentation/git-add.adoc index 596cdeff93..3116a2cac5 100644 --- a/Documentation/git-add.adoc +++ b/Documentation/git-add.adoc @@ -344,8 +344,8 @@ patch:: / - search for a hunk matching the given regex j - go to the next undecided hunk, roll over at the bottom J - go to the next hunk, roll over at the bottom - k - go to the previous undecided hunk - K - go to the previous hunk + k - go to the previous undecided hunk, roll over at the top + K - go to the previous hunk, roll over at the top s - split the current hunk into smaller hunks e - manually edit the current hunk p - print the current hunk diff --git a/add-patch.c b/add-patch.c index 106bfcb275..4f314c16ec 100644 --- a/add-patch.c +++ b/add-patch.c @@ -1399,8 +1399,8 @@ static size_t display_hunks(struct add_p_state *s, static const char help_patch_remainder[] = N_("j - go to the next undecided hunk, roll over at the bottom\n" "J - go to the next hunk, roll over at the bottom\n" - "k - go to the previous undecided hunk\n" - "K - go to the previous hunk\n" + "k - go to the previous undecided hunk, roll over at the top\n" + "K - go to the previous hunk, roll over at the top\n" "g - select a hunk to go to\n" "/ - search for a hunk matching the given regex\n" "s - split the current hunk into smaller hunks\n" @@ -1408,6 +1408,11 @@ N_("j - go to the next undecided hunk, roll over at the bottom\n" "p - print the current hunk, 'P' to use the pager\n" "? - print help\n"); +static size_t dec_mod(size_t a, size_t m) +{ + return a > 0 ? a - 1 : m - 1; +} + static size_t inc_mod(size_t a, size_t m) { return a < m - 1 ? a + 1 : 0; @@ -1450,7 +1455,9 @@ static int patch_update_file(struct add_p_state *s, undecided_next = -1; if (file_diff->hunk_nr) { - for (i = hunk_index - 1; i >= 0; i--) + for (i = dec_mod(hunk_index, file_diff->hunk_nr); + i != hunk_index; + i = dec_mod(i, file_diff->hunk_nr)) if (file_diff->hunk[i].use == UNDECIDED_HUNK) { undecided_previous = i; break; @@ -1492,7 +1499,7 @@ static int patch_update_file(struct add_p_state *s, permitted |= ALLOW_GOTO_PREVIOUS_UNDECIDED_HUNK; strbuf_addstr(&s->buf, ",k"); } - if (hunk_index) { + if (file_diff->hunk_nr > 1) { permitted |= ALLOW_GOTO_PREVIOUS_HUNK; strbuf_addstr(&s->buf, ",K"); } @@ -1584,9 +1591,10 @@ static int patch_update_file(struct add_p_state *s, } } else if (s->answer.buf[0] == 'K') { if (permitted & ALLOW_GOTO_PREVIOUS_HUNK) - hunk_index--; + hunk_index = dec_mod(hunk_index, + file_diff->hunk_nr); else - err(s, _("No previous hunk")); + err(s, _("No other hunk")); } else if (s->answer.buf[0] == 'J') { if (permitted & ALLOW_GOTO_NEXT_HUNK) hunk_index++; @@ -1596,7 +1604,7 @@ static int patch_update_file(struct add_p_state *s, if (permitted & ALLOW_GOTO_PREVIOUS_UNDECIDED_HUNK) hunk_index = undecided_previous; else - err(s, _("No previous hunk")); + err(s, _("No other undecided hunk")); } else if (s->answer.buf[0] == 'j') { if (permitted & ALLOW_GOTO_NEXT_UNDECIDED_HUNK) hunk_index = undecided_next; diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh index 8086d3da71..385e55c783 100755 --- a/t/t3701-add-interactive.sh +++ b/t/t3701-add-interactive.sh @@ -333,7 +333,7 @@ test_expect_success 'different prompts for mode change/deleted' ' sed -n "s/^\(([0-9/]*) Stage .*?\).*/\1/p" actual >actual.filtered && cat >expect <<-\EOF && (1/1) Stage deletion [y,n,q,a,d,p,?]? - (1/2) Stage mode change [y,n,q,a,d,j,J,g,/,p,?]? + (1/2) Stage mode change [y,n,q,a,d,k,K,j,J,g,/,p,?]? (2/2) Stage this hunk [y,n,q,a,d,K,J,g,/,e,p,?]? EOF test_cmp expect actual.filtered @@ -527,7 +527,7 @@ test_expect_success 'goto hunk 1 with "g 1"' ' _10 +15 _20 - (1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,p,?]?_ + (1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,?]?_ EOF test_write_lines s y g 1 | git add -p >actual && tail -n 7 <actual >actual.trimmed && @@ -540,7 +540,7 @@ test_expect_success 'goto hunk 1 with "g1"' ' _10 +15 _20 - (1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,p,?]?_ + (1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,?]?_ EOF test_write_lines s y g1 | git add -p >actual && tail -n 4 <actual >actual.trimmed && @@ -554,7 +554,7 @@ test_expect_success 'navigate to hunk via regex /pattern' ' _10 +15 _20 - (1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,p,?]?_ + (1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,?]?_ EOF test_write_lines s y /1,2 | git add -p >actual && tail -n 5 <actual >actual.trimmed && @@ -567,7 +567,7 @@ test_expect_success 'navigate to hunk via regex / pattern' ' _10 +15 _20 - (1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,p,?]?_ + (1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,?]?_ EOF test_write_lines s y / 1,2 | git add -p >actual && tail -n 4 <actual >actual.trimmed && @@ -579,11 +579,11 @@ test_expect_success 'print again the hunk' ' tr _ " " >expect <<-EOF && +15 20 - (1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,p,?]? @@ -1,2 +1,3 @@ + (1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,?]? @@ -1,2 +1,3 @@ 10 +15 20 - (1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,p,?]?_ + (1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,?]?_ EOF test_write_lines s y g 1 p | git add -p >actual && tail -n 7 <actual >actual.trimmed && @@ -595,11 +595,11 @@ test_expect_success TTY 'print again the hunk (PAGER)' ' cat >expect <<-EOF && <GREEN>+<RESET><GREEN>15<RESET> 20<RESET> - <BOLD;BLUE>(1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,p,?]? <RESET>PAGER <CYAN>@@ -1,2 +1,3 @@<RESET> + <BOLD;BLUE>(1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,?]? <RESET>PAGER <CYAN>@@ -1,2 +1,3 @@<RESET> PAGER 10<RESET> PAGER <GREEN>+<RESET><GREEN>15<RESET> PAGER 20<RESET> - <BOLD;BLUE>(1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,p,?]? <RESET> + <BOLD;BLUE>(1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,?]? <RESET> EOF test_write_lines s y g 1 P | ( @@ -802,7 +802,7 @@ test_expect_success 'colors can be overridden' ' <BOLD>-old<RESET> <BLUE>+<RESET><BLUE>new<RESET> <CYAN> more-context<RESET> - <YELLOW>(1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,p,?]? <RESET><MAGENTA>@@ -3 +3,2 @@<RESET> + <YELLOW>(1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,?]? <RESET><MAGENTA>@@ -3 +3,2 @@<RESET> <CYAN> more-context<RESET> <BLUE>+<RESET><BLUE>another-one<RESET> <YELLOW>(2/2) Stage this hunk [y,n,q,a,d,K,J,g,/,e,p,?]? <RESET><MAGENTA>@@ -1,3 +1,3 @@<RESET> @@ -810,7 +810,7 @@ test_expect_success 'colors can be overridden' ' <BOLD>-old<RESET> <BLUE>+new<RESET> <CYAN> more-context<RESET> - <YELLOW>(1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,p,?]? <RESET> + <YELLOW>(1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,?]? <RESET> EOF test_cmp expect actual ' @@ -1354,34 +1354,34 @@ do ' done -test_expect_success 'option J rolls over' ' +test_expect_success 'options J, K roll over' ' test_write_lines a b c d e f g h i >file && git add file && test_write_lines X b c d e f g h X >file && - test_write_lines J J q | git add -p >out && - test_write_lines 1 2 1 >expect && + test_write_lines J J K q | git add -p >out && + test_write_lines 1 2 1 2 >expect && sed -n -e "s-/.*--" -e "s/^(//p" <out >actual && test_cmp expect actual ' -test_expect_success 'options y, n, j, e roll over to next undecided (1)' ' +test_expect_success 'options y, n, j, k, e roll over to next undecided (1)' ' test_write_lines a b c d e f g h i j k l m n o p q >file && git add file && test_write_lines X b c d e f g h X j k l m n o p X >file && test_set_editor : && - test_write_lines g3 y g3 n g3 j g3 e q | git add -p >out && - test_write_lines 1 3 1 3 1 3 1 3 1 >expect && + test_write_lines g3 y g3 n g3 j g3 e k q | git add -p >out && + test_write_lines 1 3 1 3 1 3 1 3 1 2 >expect && sed -n -e "s-/.*--" -e "s/^(//p" <out >actual && test_cmp expect actual ' -test_expect_success 'options y, n, j, e roll over to next undecided (2)' ' +test_expect_success 'options y, n, j, k, e roll over to next undecided (2)' ' test_write_lines a b c d e f g h i j k l m n o p q >file && git add file && test_write_lines X b c d e f g h X j k l m n o p X >file && test_set_editor : && - test_write_lines y g3 y g3 n g3 j g3 e q | git add -p >out && - test_write_lines 1 2 3 2 3 2 3 2 3 2 >expect && + test_write_lines y g3 y g3 n g3 j g3 e g1 k q | git add -p >out && + test_write_lines 1 2 3 2 3 2 3 2 3 2 1 2 >expect && sed -n -e "s-/.*--" -e "s/^(//p" <out >actual && test_cmp expect actual ' -- 2.51.0 ^ permalink raw reply related [flat|nested] 37+ messages in thread
* [PATCH v3 5/6] add-patch: let options a and d roll over like y and n 2025-10-06 17:18 ` [PATCH v3 0/6] add-patch: roll over to next undecided hunk René Scharfe ` (3 preceding siblings ...) 2025-10-06 17:22 ` [PATCH v3 4/6] add-patch: let options k and K roll over like j and J René Scharfe @ 2025-10-06 17:23 ` René Scharfe 2025-10-06 17:24 ` [PATCH v3 6/6] add-patch: reset "permitted" at loop start René Scharfe 2025-10-06 18:00 ` [PATCH v3 0/6] add-patch: roll over to next undecided hunk Junio C Hamano 6 siblings, 0 replies; 37+ messages in thread From: René Scharfe @ 2025-10-06 17:23 UTC (permalink / raw) To: git@vger.kernel.org; +Cc: Windl, Ulrich, Junio C Hamano, Phillip Wood Options a and d stage and unstage all undecided hunks towards the bottom of the array of hunks, respectively, and then roll over to the very first hunk. The first part is similar to y and n if the current hunk is the last one in the array, but they roll over to the next undecided hunk if there is any. That's more useful; do it for a and d as well. Signed-off-by: René Scharfe <l.s.r@web.de> --- add-patch.c | 15 +++++++++++++++ t/t3701-add-interactive.sh | 12 ++++++------ 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/add-patch.c b/add-patch.c index 4f314c16ec..6da13a78b5 100644 --- a/add-patch.c +++ b/add-patch.c @@ -1418,6 +1418,17 @@ static size_t inc_mod(size_t a, size_t m) return a < m - 1 ? a + 1 : 0; } +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++) { + if (file_diff->hunk[i].use == UNDECIDED_HUNK) { + *idx = i; + return true; + } + } + return false; +} + static int patch_update_file(struct add_p_state *s, struct file_diff *file_diff) { @@ -1572,6 +1583,8 @@ static int patch_update_file(struct add_p_state *s, if (hunk->use == UNDECIDED_HUNK) hunk->use = USE_HUNK; } + if (!get_first_undecided(file_diff, &hunk_index)) + hunk_index = 0; } else if (hunk->use == UNDECIDED_HUNK) { hunk->use = USE_HUNK; } @@ -1582,6 +1595,8 @@ static int patch_update_file(struct add_p_state *s, if (hunk->use == UNDECIDED_HUNK) hunk->use = SKIP_HUNK; } + if (!get_first_undecided(file_diff, &hunk_index)) + hunk_index = 0; } else if (hunk->use == UNDECIDED_HUNK) { hunk->use = SKIP_HUNK; } diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh index 385e55c783..9d81b0542e 100755 --- a/t/t3701-add-interactive.sh +++ b/t/t3701-add-interactive.sh @@ -1364,24 +1364,24 @@ test_expect_success 'options J, K roll over' ' test_cmp expect actual ' -test_expect_success 'options y, n, j, k, e roll over to next undecided (1)' ' +test_expect_success 'options y, n, a, d, j, k, e roll over to next undecided (1)' ' test_write_lines a b c d e f g h i j k l m n o p q >file && git add file && test_write_lines X b c d e f g h X j k l m n o p X >file && test_set_editor : && - test_write_lines g3 y g3 n g3 j g3 e k q | git add -p >out && - test_write_lines 1 3 1 3 1 3 1 3 1 2 >expect && + test_write_lines g3 y g3 n g3 a g3 d g3 j g3 e k q | git add -p >out && + test_write_lines 1 3 1 3 1 3 1 3 1 3 1 3 1 2 >expect && sed -n -e "s-/.*--" -e "s/^(//p" <out >actual && test_cmp expect actual ' -test_expect_success 'options y, n, j, k, e roll over to next undecided (2)' ' +test_expect_success 'options y, n, a, d, j, k, e roll over to next undecided (2)' ' test_write_lines a b c d e f g h i j k l m n o p q >file && git add file && test_write_lines X b c d e f g h X j k l m n o p X >file && test_set_editor : && - test_write_lines y g3 y g3 n g3 j g3 e g1 k q | git add -p >out && - test_write_lines 1 2 3 2 3 2 3 2 3 2 1 2 >expect && + test_write_lines y g3 y g3 n g3 a g3 d g3 j g3 e g1 k q | git add -p >out && + test_write_lines 1 2 3 2 3 2 3 2 3 2 3 2 3 2 1 2 >expect && sed -n -e "s-/.*--" -e "s/^(//p" <out >actual && test_cmp expect actual ' -- 2.51.0 ^ permalink raw reply related [flat|nested] 37+ messages in thread
* [PATCH v3 6/6] add-patch: reset "permitted" at loop start 2025-10-06 17:18 ` [PATCH v3 0/6] add-patch: roll over to next undecided hunk René Scharfe ` (4 preceding siblings ...) 2025-10-06 17:23 ` [PATCH v3 5/6] add-patch: let options a and d roll over like y and n René Scharfe @ 2025-10-06 17:24 ` René Scharfe 2025-10-31 10:28 ` [EXT] " Windl, Ulrich 2025-10-06 18:00 ` [PATCH v3 0/6] add-patch: roll over to next undecided hunk Junio C Hamano 6 siblings, 1 reply; 37+ messages in thread From: René Scharfe @ 2025-10-06 17:24 UTC (permalink / raw) To: git@vger.kernel.org; +Cc: Windl, Ulrich, Junio C Hamano, Phillip Wood Don't accumulate allowed options from any visited hunks, start fresh at the top of the loop instead and only record the allowed options for the current hunk. Reported-by: Junio C Hamano <gitster@pobox.com> Signed-off-by: René Scharfe <l.s.r@web.de> --- add-patch.c | 19 ++++++++++--------- t/t3701-add-interactive.sh | 14 ++++++++++++++ 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/add-patch.c b/add-patch.c index 6da13a78b5..45839ceac5 100644 --- a/add-patch.c +++ b/add-patch.c @@ -1439,15 +1439,6 @@ static int patch_update_file(struct add_p_state *s, struct child_process cp = CHILD_PROCESS_INIT; int colored = !!s->colored.len, quit = 0, use_pager = 0; enum prompt_mode_type prompt_mode_type; - enum { - ALLOW_GOTO_PREVIOUS_HUNK = 1 << 0, - ALLOW_GOTO_PREVIOUS_UNDECIDED_HUNK = 1 << 1, - ALLOW_GOTO_NEXT_HUNK = 1 << 2, - ALLOW_GOTO_NEXT_UNDECIDED_HUNK = 1 << 3, - ALLOW_SEARCH_AND_GOTO = 1 << 4, - ALLOW_SPLIT = 1 << 5, - ALLOW_EDIT = 1 << 6 - } permitted = 0; /* Empty added files have no hunks */ if (!file_diff->hunk_nr && !file_diff->added) @@ -1457,6 +1448,16 @@ static int patch_update_file(struct add_p_state *s, render_diff_header(s, file_diff, colored, &s->buf); fputs(s->buf.buf, stdout); for (;;) { + enum { + ALLOW_GOTO_PREVIOUS_HUNK = 1 << 0, + ALLOW_GOTO_PREVIOUS_UNDECIDED_HUNK = 1 << 1, + ALLOW_GOTO_NEXT_HUNK = 1 << 2, + ALLOW_GOTO_NEXT_UNDECIDED_HUNK = 1 << 3, + ALLOW_SEARCH_AND_GOTO = 1 << 4, + ALLOW_SPLIT = 1 << 5, + ALLOW_EDIT = 1 << 6 + } permitted = 0; + if (hunk_index >= file_diff->hunk_nr) hunk_index = 0; hunk = file_diff->hunk_nr diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh index 9d81b0542e..403aaee356 100755 --- a/t/t3701-add-interactive.sh +++ b/t/t3701-add-interactive.sh @@ -1386,4 +1386,18 @@ test_expect_success 'options y, n, a, d, j, k, e roll over to next undecided (2) test_cmp expect actual ' +test_expect_success 'invalid option s is rejected' ' + test_write_lines a b c d e f g h i j k >file && + git add file && + test_write_lines X b X d e f g h i j X >file && + test_write_lines j s q | git add -p >out && + sed -ne "s/ @@.*//" -e "s/ \$//" -e "/^(/p" <out >actual && + cat >expect <<-EOF && + (1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,s,e,p,?]? + (2/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,?]? Sorry, cannot split this hunk + (2/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,?]? + EOF + test_cmp expect actual +' + test_done -- 2.51.0 ^ permalink raw reply related [flat|nested] 37+ messages in thread
* RE: [EXT] [PATCH v3 6/6] add-patch: reset "permitted" at loop start 2025-10-06 17:24 ` [PATCH v3 6/6] add-patch: reset "permitted" at loop start René Scharfe @ 2025-10-31 10:28 ` Windl, Ulrich 2025-10-31 15:16 ` Junio C Hamano 0 siblings, 1 reply; 37+ messages in thread From: Windl, Ulrich @ 2025-10-31 10:28 UTC (permalink / raw) To: René Scharfe, git@vger.kernel.org; +Cc: Junio C Hamano, Phillip Wood Just a comment of personal taste: I think declaring an anonymous enum inside a loop is just bad style. I think that gcc is smart enough to optimize if "permitted" is declared outside the loop, or make the "permitted" use a typedef for a "named enum" (declared outside the loop while the variable may be inside the loop). > -----Original Message----- > From: René Scharfe <l.s.r@web.de> > Sent: Monday, October 6, 2025 7:24 PM > To: git@vger.kernel.org > Cc: Windl, Ulrich <u.windl@ukr.de>; Junio C Hamano <gitster@pobox.com>; > Phillip Wood <phillip.wood@dunelm.org.uk> > Subject: [EXT] [PATCH v3 6/6] add-patch: reset "permitted" at loop start > [...] > for (;;) { > + enum { > + ALLOW_GOTO_PREVIOUS_HUNK = 1 << 0, > + ALLOW_GOTO_PREVIOUS_UNDECIDED_HUNK = 1 << > 1, > + ALLOW_GOTO_NEXT_HUNK = 1 << 2, > + ALLOW_GOTO_NEXT_UNDECIDED_HUNK = 1 << 3, > + ALLOW_SEARCH_AND_GOTO = 1 << 4, > + ALLOW_SPLIT = 1 << 5, > + ALLOW_EDIT = 1 << 6 > + } permitted = 0; > + > if (hunk_index >= file_diff->hunk_nr) > hunk_index = 0; > hunk = file_diff->hunk_nr ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [EXT] [PATCH v3 6/6] add-patch: reset "permitted" at loop start 2025-10-31 10:28 ` [EXT] " Windl, Ulrich @ 2025-10-31 15:16 ` Junio C Hamano 0 siblings, 0 replies; 37+ messages in thread From: Junio C Hamano @ 2025-10-31 15:16 UTC (permalink / raw) To: Windl, Ulrich; +Cc: René Scharfe, git@vger.kernel.org, Phillip Wood "Windl, Ulrich" <u.windl@ukr.de> writes: > Just a comment of personal taste: I think declaring an anonymous > enum inside a loop is just bad style. I think that gcc is smart > enough to optimize if "permitted" is declared outside the loop, or > make the "permitted" use a typedef for a "named enum" (declared > outside the loop while the variable may be inside the loop). If this is more than just a personal preference (which to me does sound like), a patch to improve it on top is very much welcomed. The change itself would be just reverting the code movement, drop the 0 initialization and resetting the ariable at the top of the loop every iteration. But the rationale being that it would give compilers a chance to do a better job, I'd prefer to see a compiler person write the proposed log message, possibly backed by data (perhaps "generated assembly is objectively better---compare this and that" in this case? I dunno). Thanks. >> -----Original Message----- >> From: René Scharfe <l.s.r@web.de> >> Sent: Monday, October 6, 2025 7:24 PM >> To: git@vger.kernel.org >> Cc: Windl, Ulrich <u.windl@ukr.de>; Junio C Hamano <gitster@pobox.com>; >> Phillip Wood <phillip.wood@dunelm.org.uk> >> Subject: [EXT] [PATCH v3 6/6] add-patch: reset "permitted" at loop start >> > [...] >> for (;;) { >> + enum { >> + ALLOW_GOTO_PREVIOUS_HUNK = 1 << 0, >> + ALLOW_GOTO_PREVIOUS_UNDECIDED_HUNK = 1 << >> 1, >> + ALLOW_GOTO_NEXT_HUNK = 1 << 2, >> + ALLOW_GOTO_NEXT_UNDECIDED_HUNK = 1 << 3, >> + ALLOW_SEARCH_AND_GOTO = 1 << 4, >> + ALLOW_SPLIT = 1 << 5, >> + ALLOW_EDIT = 1 << 6 >> + } permitted = 0; >> + >> if (hunk_index >= file_diff->hunk_nr) >> hunk_index = 0; >> hunk = file_diff->hunk_nr ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH v3 0/6] add-patch: roll over to next undecided hunk 2025-10-06 17:18 ` [PATCH v3 0/6] add-patch: roll over to next undecided hunk René Scharfe ` (5 preceding siblings ...) 2025-10-06 17:24 ` [PATCH v3 6/6] add-patch: reset "permitted" at loop start René Scharfe @ 2025-10-06 18:00 ` Junio C Hamano 2025-10-06 20:05 ` René Scharfe 6 siblings, 1 reply; 37+ messages in thread From: Junio C Hamano @ 2025-10-06 18:00 UTC (permalink / raw) To: René Scharfe; +Cc: git@vger.kernel.org, Windl, Ulrich, Phillip Wood René Scharfe <l.s.r@web.de> writes: > Changes since v1: > - added patch 5 for a and d > - made error messages direction-neutral > - removed stray "only" from commit message of patch 2 > > add-patch: improve help for options j, J, k, and K > add-patch: document that option J rolls over > add-patch: let options y, n, j, and e roll over to next undecided > add-patch: let options k and K roll over like j and J > add-patch: let options a and d roll over like y and n > add-patch: reset "permitted" at loop start Will queue. Should we mark it for 'next'? Thanks. ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH v3 0/6] add-patch: roll over to next undecided hunk 2025-10-06 18:00 ` [PATCH v3 0/6] add-patch: roll over to next undecided hunk Junio C Hamano @ 2025-10-06 20:05 ` René Scharfe 2025-10-06 22:01 ` Junio C Hamano 0 siblings, 1 reply; 37+ messages in thread From: René Scharfe @ 2025-10-06 20:05 UTC (permalink / raw) To: Junio C Hamano; +Cc: git@vger.kernel.org, Windl, Ulrich, Phillip Wood On 10/6/25 8:00 PM, Junio C Hamano wrote: > René Scharfe <l.s.r@web.de> writes: > >> Changes since v1: >> - added patch 5 for a and d >> - made error messages direction-neutral >> - removed stray "only" from commit message of patch 2 >> >> add-patch: improve help for options j, J, k, and K >> add-patch: document that option J rolls over >> add-patch: let options y, n, j, and e roll over to next undecided >> add-patch: let options k and K roll over like j and J >> add-patch: let options a and d roll over like y and n >> add-patch: reset "permitted" at loop start > > Will queue. Should we mark it for 'next'? Oh, already? Fine with me. René ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH v3 0/6] add-patch: roll over to next undecided hunk 2025-10-06 20:05 ` René Scharfe @ 2025-10-06 22:01 ` Junio C Hamano 0 siblings, 0 replies; 37+ messages in thread From: Junio C Hamano @ 2025-10-06 22:01 UTC (permalink / raw) To: René Scharfe; +Cc: git@vger.kernel.org, Windl, Ulrich, Phillip Wood René Scharfe <l.s.r@web.de> writes: > On 10/6/25 8:00 PM, Junio C Hamano wrote: >> René Scharfe <l.s.r@web.de> writes: >> >>> Changes since v1: >>> - added patch 5 for a and d >>> - made error messages direction-neutral >>> - removed stray "only" from commit message of patch 2 >>> >>> add-patch: improve help for options j, J, k, and K >>> add-patch: document that option J rolls over >>> add-patch: let options y, n, j, and e roll over to next undecided >>> add-patch: let options k and K roll over like j and J >>> add-patch: let options a and d roll over like y and n >>> add-patch: reset "permitted" at loop start >> >> Will queue. Should we mark it for 'next'? > > Oh, already? Fine with me. Was just double-checking if there are things you wanted to imrpove. Thanks. ^ permalink raw reply [flat|nested] 37+ messages in thread
end of thread, other threads:[~2025-11-03 12:43 UTC | newest] Thread overview: 37+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2025-10-02 9:23 Broken handling of "J" hunks for "add --interactive"? Windl, Ulrich 2025-10-03 12:16 ` [PATCH] add-patch: roll over to next undecided hunk René Scharfe 2025-10-03 13:41 ` Phillip Wood 2025-10-03 14:10 ` René Scharfe 2025-10-08 13:47 ` Phillip Wood 2025-10-03 16:11 ` Junio C Hamano 2025-10-03 19:53 ` René Scharfe 2025-10-03 20:39 ` Junio C Hamano 2025-10-03 20:42 ` Junio C Hamano 2025-10-03 21:18 ` Junio C Hamano 2025-10-05 15:45 ` [PATCH v2 0/5] " René Scharfe 2025-10-05 15:55 ` [PATCH v2 1/5] add-patch: improve help for options j, J, k, and K René Scharfe 2025-10-05 21:30 ` Junio C Hamano 2025-10-06 17:17 ` René Scharfe 2025-10-06 17:58 ` Junio C Hamano 2025-10-31 10:08 ` [EXT] " Windl, Ulrich 2025-11-01 8:18 ` Junio C Hamano 2025-11-03 12:43 ` [EXT] " Windl, Ulrich 2025-10-05 15:55 ` [PATCH v2 2/5] add-patch: document that option J rolls over René Scharfe 2025-10-05 21:30 ` Junio C Hamano 2025-10-05 15:55 ` [PATCH v2 3/5] add-patch: let options y, n, j, and e roll over to next undecided René Scharfe 2025-10-05 15:55 ` [PATCH v2 4/5] add-patch: let options k and K roll over like j and J René Scharfe 2025-10-05 20:55 ` Junio C Hamano 2025-10-06 17:18 ` René Scharfe 2025-10-05 15:55 ` [PATCH v2 5/5] add-patch: reset "permitted" at loop start René Scharfe 2025-10-06 17:18 ` [PATCH v3 0/6] add-patch: roll over to next undecided hunk René Scharfe 2025-10-06 17:19 ` [PATCH v3 1/6] add-patch: improve help for options j, J, k, and K René Scharfe 2025-10-06 17:20 ` [PATCH v3 2/6] add-patch: document that option J rolls over René Scharfe 2025-10-06 17:21 ` [PATCH v3 3/6] add-patch: let options y, n, j, and e roll over to next undecided René Scharfe 2025-10-06 17:22 ` [PATCH v3 4/6] add-patch: let options k and K roll over like j and J René Scharfe 2025-10-06 17:23 ` [PATCH v3 5/6] add-patch: let options a and d roll over like y and n René Scharfe 2025-10-06 17:24 ` [PATCH v3 6/6] add-patch: reset "permitted" at loop start René Scharfe 2025-10-31 10:28 ` [EXT] " Windl, Ulrich 2025-10-31 15:16 ` Junio C Hamano 2025-10-06 18:00 ` [PATCH v3 0/6] add-patch: roll over to next undecided hunk Junio C Hamano 2025-10-06 20:05 ` René Scharfe 2025-10-06 22:01 ` Junio C Hamano
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).