add-patch: let options k and K roll over like j and J

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> Signed-off-by: Junio C Hamano <gitster@pobox.com>

René Scharfe committed Oct 6, 2025 at 19:22 UTC 1967b60681256ed452ed70dedf381b5380697901
3 files changed +37 -29
Documentation/git-add.adoc
+2 -2
@@ -344,8 +344,8 @@ patch::
344 / - search for a hunk matching the given regex
345 j - go to the next undecided hunk, roll over at the bottom
346 J - go to the next hunk, roll over at the bottom
347 - k - go to the previous undecided hunk
348 - K - go to the previous hunk
347 + k - go to the previous undecided hunk, roll over at the top
348 + K - go to the previous hunk, roll over at the top
349 s - split the current hunk into smaller hunks
350 e - manually edit the current hunk
351 p - print the current hunk
add-patch.c
+15 -7
@@ -1399,8 +1399,8 @@ static size_t display_hunks(struct add_p_state *s,
1399 static const char help_patch_remainder[] =
1400 N_("j - go to the next undecided hunk, roll over at the bottom\n"
1401 "J - go to the next hunk, roll over at the bottom\n"
1402 - "k - go to the previous undecided hunk\n"
1403 - "K - go to the previous hunk\n"
1402 + "k - go to the previous undecided hunk, roll over at the top\n"
1403 + "K - go to the previous hunk, roll over at the top\n"
1404 "g - select a hunk to go to\n"
1405 "/ - search for a hunk matching the given regex\n"
1406 "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"
1408 "p - print the current hunk, 'P' to use the pager\n"
1409 "? - print help\n");
1410
1411 +static size_t dec_mod(size_t a, size_t m)
1412 +{
1413 + return a > 0 ? a - 1 : m - 1;
1414 +}
1415 +
1416 static size_t inc_mod(size_t a, size_t m)
1417 {
1418 return a < m - 1 ? a + 1 : 0;
@@ -1450,7 +1455,9 @@ static int patch_update_file(struct add_p_state *s,
1455 undecided_next = -1;
1456
1457 if (file_diff->hunk_nr) {
1453 - for (i = hunk_index - 1; i >= 0; i--)
1458 + for (i = dec_mod(hunk_index, file_diff->hunk_nr);
1459 + i != hunk_index;
1460 + i = dec_mod(i, file_diff->hunk_nr))
1461 if (file_diff->hunk[i].use == UNDECIDED_HUNK) {
1462 undecided_previous = i;
1463 break;
@@ -1492,7 +1499,7 @@ static int patch_update_file(struct add_p_state *s,
1499 permitted |= ALLOW_GOTO_PREVIOUS_UNDECIDED_HUNK;
1500 strbuf_addstr(&s->buf, ",k");
1501 }
1495 - if (hunk_index) {
1502 + if (file_diff->hunk_nr > 1) {
1503 permitted |= ALLOW_GOTO_PREVIOUS_HUNK;
1504 strbuf_addstr(&s->buf, ",K");
1505 }
@@ -1584,9 +1591,10 @@ soft_increment:
1591 }
1592 } else if (s->answer.buf[0] == 'K') {
1593 if (permitted & ALLOW_GOTO_PREVIOUS_HUNK)
1587 - hunk_index--;
1594 + hunk_index = dec_mod(hunk_index,
1595 + file_diff->hunk_nr);
1596 else
1589 - err(s, _("No previous hunk"));
1597 + err(s, _("No other hunk"));
1598 } else if (s->answer.buf[0] == 'J') {
1599 if (permitted & ALLOW_GOTO_NEXT_HUNK)
1600 hunk_index++;
@@ -1596,7 +1604,7 @@ soft_increment:
1604 if (permitted & ALLOW_GOTO_PREVIOUS_UNDECIDED_HUNK)
1605 hunk_index = undecided_previous;
1606 else
1599 - err(s, _("No previous hunk"));
1607 + err(s, _("No other undecided hunk"));
1608 } else if (s->answer.buf[0] == 'j') {
1609 if (permitted & ALLOW_GOTO_NEXT_UNDECIDED_HUNK)
1610 hunk_index = undecided_next;
t/t3701-add-interactive.sh
+20 -20
@@ -333,7 +333,7 @@ test_expect_success 'different prompts for mode change/deleted' '
333 sed -n "s/^\(([0-9/]*) Stage .*?\).*/\1/p" actual >actual.filtered &&
334 cat >expect <<-\EOF &&
335 (1/1) Stage deletion [y,n,q,a,d,p,?]?
336 - (1/2) Stage mode change [y,n,q,a,d,j,J,g,/,p,?]?
336 + (1/2) Stage mode change [y,n,q,a,d,k,K,j,J,g,/,p,?]?
337 (2/2) Stage this hunk [y,n,q,a,d,K,J,g,/,e,p,?]?
338 EOF
339 test_cmp expect actual.filtered
@@ -527,7 +527,7 @@ test_expect_success 'goto hunk 1 with "g 1"' '
527 _10
528 +15
529 _20
530 - (1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,p,?]?_
530 + (1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,?]?_
531 EOF
532 test_write_lines s y g 1 | git add -p >actual &&
533 tail -n 7 <actual >actual.trimmed &&
@@ -540,7 +540,7 @@ test_expect_success 'goto hunk 1 with "g1"' '
540 _10
541 +15
542 _20
543 - (1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,p,?]?_
543 + (1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,?]?_
544 EOF
545 test_write_lines s y g1 | git add -p >actual &&
546 tail -n 4 <actual >actual.trimmed &&
@@ -554,7 +554,7 @@ test_expect_success 'navigate to hunk via regex /pattern' '
554 _10
555 +15
556 _20
557 - (1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,p,?]?_
557 + (1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,?]?_
558 EOF
559 test_write_lines s y /1,2 | git add -p >actual &&
560 tail -n 5 <actual >actual.trimmed &&
@@ -567,7 +567,7 @@ test_expect_success 'navigate to hunk via regex / pattern' '
567 _10
568 +15
569 _20
570 - (1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,p,?]?_
570 + (1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,?]?_
571 EOF
572 test_write_lines s y / 1,2 | git add -p >actual &&
573 tail -n 4 <actual >actual.trimmed &&
@@ -579,11 +579,11 @@ test_expect_success 'print again the hunk' '
579 tr _ " " >expect <<-EOF &&
580 +15
581 20
582 - (1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,p,?]? @@ -1,2 +1,3 @@
582 + (1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,?]? @@ -1,2 +1,3 @@
583 10
584 +15
585 20
586 - (1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,p,?]?_
586 + (1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,?]?_
587 EOF
588 test_write_lines s y g 1 p | git add -p >actual &&
589 tail -n 7 <actual >actual.trimmed &&
@@ -595,11 +595,11 @@ test_expect_success TTY 'print again the hunk (PAGER)' '
595 cat >expect <<-EOF &&
596 <GREEN>+<RESET><GREEN>15<RESET>
597 20<RESET>
598 - <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>
598 + <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>
599 PAGER 10<RESET>
600 PAGER <GREEN>+<RESET><GREEN>15<RESET>
601 PAGER 20<RESET>
602 - <BOLD;BLUE>(1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,p,?]? <RESET>
602 + <BOLD;BLUE>(1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,?]? <RESET>
603 EOF
604 test_write_lines s y g 1 P |
605 (
@@ -802,7 +802,7 @@ test_expect_success 'colors can be overridden' '
802 <BOLD>-old<RESET>
803 <BLUE>+<RESET><BLUE>new<RESET>
804 <CYAN> more-context<RESET>
805 - <YELLOW>(1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,p,?]? <RESET><MAGENTA>@@ -3 +3,2 @@<RESET>
805 + <YELLOW>(1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,?]? <RESET><MAGENTA>@@ -3 +3,2 @@<RESET>
806 <CYAN> more-context<RESET>
807 <BLUE>+<RESET><BLUE>another-one<RESET>
808 <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' '
810 <BOLD>-old<RESET>
811 <BLUE>+new<RESET>
812 <CYAN> more-context<RESET>
813 - <YELLOW>(1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,p,?]? <RESET>
813 + <YELLOW>(1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,?]? <RESET>
814 EOF
815 test_cmp expect actual
816 '
@@ -1354,34 +1354,34 @@ do
1354 '
1355 done
1356
1357 -test_expect_success 'option J rolls over' '
1357 +test_expect_success 'options J, K roll over' '
1358 test_write_lines a b c d e f g h i >file &&
1359 git add file &&
1360 test_write_lines X b c d e f g h X >file &&
1361 - test_write_lines J J q | git add -p >out &&
1362 - test_write_lines 1 2 1 >expect &&
1361 + test_write_lines J J K q | git add -p >out &&
1362 + test_write_lines 1 2 1 2 >expect &&
1363 sed -n -e "s-/.*--" -e "s/^(//p" <out >actual &&
1364 test_cmp expect actual
1365 '
1366
1367 -test_expect_success 'options y, n, j, e roll over to next undecided (1)' '
1367 +test_expect_success 'options y, n, j, k, e roll over to next undecided (1)' '
1368 test_write_lines a b c d e f g h i j k l m n o p q >file &&
1369 git add file &&
1370 test_write_lines X b c d e f g h X j k l m n o p X >file &&
1371 test_set_editor : &&
1372 - test_write_lines g3 y g3 n g3 j g3 e q | git add -p >out &&
1373 - test_write_lines 1 3 1 3 1 3 1 3 1 >expect &&
1372 + test_write_lines g3 y g3 n g3 j g3 e k q | git add -p >out &&
1373 + test_write_lines 1 3 1 3 1 3 1 3 1 2 >expect &&
1374 sed -n -e "s-/.*--" -e "s/^(//p" <out >actual &&
1375 test_cmp expect actual
1376 '
1377
1378 -test_expect_success 'options y, n, j, e roll over to next undecided (2)' '
1378 +test_expect_success 'options y, n, j, k, e roll over to next undecided (2)' '
1379 test_write_lines a b c d e f g h i j k l m n o p q >file &&
1380 git add file &&
1381 test_write_lines X b c d e f g h X j k l m n o p X >file &&
1382 test_set_editor : &&
1383 - test_write_lines y g3 y g3 n g3 j g3 e q | git add -p >out &&
1384 - test_write_lines 1 2 3 2 3 2 3 2 3 2 >expect &&
1383 + test_write_lines y g3 y g3 n g3 j g3 e g1 k q | git add -p >out &&
1384 + test_write_lines 1 2 3 2 3 2 3 2 3 2 1 2 >expect &&
1385 sed -n -e "s-/.*--" -e "s/^(//p" <out >actual &&
1386 test_cmp expect actual
1387 '