Skip to content

Commit 32b1bdb

Browse files
committed
Merge branch 'rs/add-patch-options-fix' into seen
The code in "git add -p" and friends to iterate over hunks was riddled with bugs, which has been corrected. * rs/add-patch-options-fix: add-patch: reset "permitted" at loop start add-patch: let options a and d roll over like y and n add-patch: let options k and K roll over like j and J add-patch: let options y, n, j, and e roll over to next undecided add-patch: document that option J rolls over add-patch: improve help for options j, J, k, and K
2 parents 89d2f8c + 208e23e commit 32b1bdb

File tree

3 files changed

+118
-41
lines changed

3 files changed

+118
-41
lines changed

Documentation/git-add.adoc

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -342,10 +342,10 @@ patch::
342342
d - do not stage this hunk or any of the later hunks in the file
343343
g - select a hunk to go to
344344
/ - search for a hunk matching the given regex
345-
j - leave this hunk undecided, see next undecided hunk
346-
J - leave this hunk undecided, see next hunk
347-
k - leave this hunk undecided, see previous undecided hunk
348-
K - leave this hunk undecided, see previous hunk
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, roll over at the top
348+
K - go to the previous hunk, roll over at the top
349349
s - split the current hunk into smaller hunks
350350
e - manually edit the current hunk
351351
p - print the current hunk

add-patch.c

Lines changed: 53 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1531,17 +1531,38 @@ static size_t display_hunks(struct add_p_state *s,
15311531
}
15321532

15331533
static const char help_patch_remainder[] =
1534-
N_("j - leave this hunk undecided, see next undecided hunk\n"
1535-
"J - leave this hunk undecided, see next hunk\n"
1536-
"k - leave this hunk undecided, see previous undecided hunk\n"
1537-
"K - leave this hunk undecided, see previous hunk\n"
1534+
N_("j - go to the next undecided hunk, roll over at the bottom\n"
1535+
"J - go to the next hunk, roll over at the bottom\n"
1536+
"k - go to the previous undecided hunk, roll over at the top\n"
1537+
"K - go to the previous hunk, roll over at the top\n"
15381538
"g - select a hunk to go to\n"
15391539
"/ - search for a hunk matching the given regex\n"
15401540
"s - split the current hunk into smaller hunks\n"
15411541
"e - manually edit the current hunk\n"
15421542
"p - print the current hunk, 'P' to use the pager\n"
15431543
"? - print help\n");
15441544

1545+
static size_t dec_mod(size_t a, size_t m)
1546+
{
1547+
return a > 0 ? a - 1 : m - 1;
1548+
}
1549+
1550+
static size_t inc_mod(size_t a, size_t m)
1551+
{
1552+
return a < m - 1 ? a + 1 : 0;
1553+
}
1554+
1555+
static bool get_first_undecided(const struct file_diff *file_diff, size_t *idx)
1556+
{
1557+
for (size_t i = 0; i < file_diff->hunk_nr; i++) {
1558+
if (file_diff->hunk[i].use == UNDECIDED_HUNK) {
1559+
*idx = i;
1560+
return true;
1561+
}
1562+
}
1563+
return false;
1564+
}
1565+
15451566
static int patch_update_file(struct add_p_state *s,
15461567
struct file_diff *file_diff)
15471568
{
@@ -1552,15 +1573,6 @@ static int patch_update_file(struct add_p_state *s,
15521573
struct child_process cp = CHILD_PROCESS_INIT;
15531574
int colored = !!s->colored.len, quit = 0, use_pager = 0;
15541575
enum prompt_mode_type prompt_mode_type;
1555-
enum {
1556-
ALLOW_GOTO_PREVIOUS_HUNK = 1 << 0,
1557-
ALLOW_GOTO_PREVIOUS_UNDECIDED_HUNK = 1 << 1,
1558-
ALLOW_GOTO_NEXT_HUNK = 1 << 2,
1559-
ALLOW_GOTO_NEXT_UNDECIDED_HUNK = 1 << 3,
1560-
ALLOW_SEARCH_AND_GOTO = 1 << 4,
1561-
ALLOW_SPLIT = 1 << 5,
1562-
ALLOW_EDIT = 1 << 6
1563-
} permitted = 0;
15641576

15651577
/* Empty added files have no hunks */
15661578
if (!file_diff->hunk_nr && !file_diff->added)
@@ -1570,6 +1582,16 @@ static int patch_update_file(struct add_p_state *s,
15701582
render_diff_header(s, file_diff, colored, &s->buf);
15711583
fputs(s->buf.buf, stdout);
15721584
for (;;) {
1585+
enum {
1586+
ALLOW_GOTO_PREVIOUS_HUNK = 1 << 0,
1587+
ALLOW_GOTO_PREVIOUS_UNDECIDED_HUNK = 1 << 1,
1588+
ALLOW_GOTO_NEXT_HUNK = 1 << 2,
1589+
ALLOW_GOTO_NEXT_UNDECIDED_HUNK = 1 << 3,
1590+
ALLOW_SEARCH_AND_GOTO = 1 << 4,
1591+
ALLOW_SPLIT = 1 << 5,
1592+
ALLOW_EDIT = 1 << 6
1593+
} permitted = 0;
1594+
15731595
if (hunk_index >= file_diff->hunk_nr)
15741596
hunk_index = 0;
15751597
hunk = file_diff->hunk_nr
@@ -1579,13 +1601,17 @@ static int patch_update_file(struct add_p_state *s,
15791601
undecided_next = -1;
15801602

15811603
if (file_diff->hunk_nr) {
1582-
for (i = hunk_index - 1; i >= 0; i--)
1604+
for (i = dec_mod(hunk_index, file_diff->hunk_nr);
1605+
i != hunk_index;
1606+
i = dec_mod(i, file_diff->hunk_nr))
15831607
if (file_diff->hunk[i].use == UNDECIDED_HUNK) {
15841608
undecided_previous = i;
15851609
break;
15861610
}
15871611

1588-
for (i = hunk_index + 1; i < file_diff->hunk_nr; i++)
1612+
for (i = inc_mod(hunk_index, file_diff->hunk_nr);
1613+
i != hunk_index;
1614+
i = inc_mod(i, file_diff->hunk_nr))
15891615
if (file_diff->hunk[i].use == UNDECIDED_HUNK) {
15901616
undecided_next = i;
15911617
break;
@@ -1619,15 +1645,15 @@ static int patch_update_file(struct add_p_state *s,
16191645
permitted |= ALLOW_GOTO_PREVIOUS_UNDECIDED_HUNK;
16201646
strbuf_addstr(&s->buf, ",k");
16211647
}
1622-
if (hunk_index) {
1648+
if (file_diff->hunk_nr > 1) {
16231649
permitted |= ALLOW_GOTO_PREVIOUS_HUNK;
16241650
strbuf_addstr(&s->buf, ",K");
16251651
}
16261652
if (undecided_next >= 0) {
16271653
permitted |= ALLOW_GOTO_NEXT_UNDECIDED_HUNK;
16281654
strbuf_addstr(&s->buf, ",j");
16291655
}
1630-
if (hunk_index + 1 < file_diff->hunk_nr) {
1656+
if (file_diff->hunk_nr > 1) {
16311657
permitted |= ALLOW_GOTO_NEXT_HUNK;
16321658
strbuf_addstr(&s->buf, ",J");
16331659
}
@@ -1692,6 +1718,8 @@ static int patch_update_file(struct add_p_state *s,
16921718
if (hunk->use == UNDECIDED_HUNK)
16931719
hunk->use = USE_HUNK;
16941720
}
1721+
if (!get_first_undecided(file_diff, &hunk_index))
1722+
hunk_index = 0;
16951723
} else if (hunk->use == UNDECIDED_HUNK) {
16961724
hunk->use = USE_HUNK;
16971725
}
@@ -1702,6 +1730,8 @@ static int patch_update_file(struct add_p_state *s,
17021730
if (hunk->use == UNDECIDED_HUNK)
17031731
hunk->use = SKIP_HUNK;
17041732
}
1733+
if (!get_first_undecided(file_diff, &hunk_index))
1734+
hunk_index = 0;
17051735
} else if (hunk->use == UNDECIDED_HUNK) {
17061736
hunk->use = SKIP_HUNK;
17071737
}
@@ -1711,24 +1741,25 @@ static int patch_update_file(struct add_p_state *s,
17111741
}
17121742
} else if (s->answer.buf[0] == 'K') {
17131743
if (permitted & ALLOW_GOTO_PREVIOUS_HUNK)
1714-
hunk_index--;
1744+
hunk_index = dec_mod(hunk_index,
1745+
file_diff->hunk_nr);
17151746
else
1716-
err(s, _("No previous hunk"));
1747+
err(s, _("No other hunk"));
17171748
} else if (s->answer.buf[0] == 'J') {
17181749
if (permitted & ALLOW_GOTO_NEXT_HUNK)
17191750
hunk_index++;
17201751
else
1721-
err(s, _("No next hunk"));
1752+
err(s, _("No other hunk"));
17221753
} else if (s->answer.buf[0] == 'k') {
17231754
if (permitted & ALLOW_GOTO_PREVIOUS_UNDECIDED_HUNK)
17241755
hunk_index = undecided_previous;
17251756
else
1726-
err(s, _("No previous hunk"));
1757+
err(s, _("No other undecided hunk"));
17271758
} else if (s->answer.buf[0] == 'j') {
17281759
if (permitted & ALLOW_GOTO_NEXT_UNDECIDED_HUNK)
17291760
hunk_index = undecided_next;
17301761
else
1731-
err(s, _("No next hunk"));
1762+
err(s, _("No other undecided hunk"));
17321763
} else if (s->answer.buf[0] == 'g') {
17331764
char *pend;
17341765
unsigned long response;

t/t3701-add-interactive.sh

Lines changed: 61 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -333,8 +333,8 @@ test_expect_success 'different prompts for mode change/deleted' '
333333
sed -n "s/^\(([0-9/]*) Stage .*?\).*/\1/p" actual >actual.filtered &&
334334
cat >expect <<-\EOF &&
335335
(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,?]?
337-
(2/2) Stage this hunk [y,n,q,a,d,K,g,/,e,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,?]?
338338
EOF
339339
test_cmp expect actual.filtered
340340
'
@@ -521,13 +521,13 @@ test_expect_success 'split hunk setup' '
521521
test_expect_success 'goto hunk 1 with "g 1"' '
522522
test_when_finished "git reset" &&
523523
tr _ " " >expect <<-EOF &&
524-
(2/2) Stage this hunk [y,n,q,a,d,K,g,/,e,p,?]? + 1: -1,2 +1,3 +15
524+
(2/2) Stage this hunk [y,n,q,a,d,K,J,g,/,e,p,?]? + 1: -1,2 +1,3 +15
525525
_ 2: -2,4 +3,8 +21
526526
go to which hunk? @@ -1,2 +1,3 @@
527527
_10
528528
+15
529529
_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,?]?_
531531
EOF
532532
test_write_lines s y g 1 | git add -p >actual &&
533533
tail -n 7 <actual >actual.trimmed &&
@@ -540,7 +540,7 @@ test_expect_success 'goto hunk 1 with "g1"' '
540540
_10
541541
+15
542542
_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,?]?_
544544
EOF
545545
test_write_lines s y g1 | git add -p >actual &&
546546
tail -n 4 <actual >actual.trimmed &&
@@ -550,11 +550,11 @@ test_expect_success 'goto hunk 1 with "g1"' '
550550
test_expect_success 'navigate to hunk via regex /pattern' '
551551
test_when_finished "git reset" &&
552552
tr _ " " >expect <<-EOF &&
553-
(2/2) Stage this hunk [y,n,q,a,d,K,g,/,e,p,?]? @@ -1,2 +1,3 @@
553+
(2/2) Stage this hunk [y,n,q,a,d,K,J,g,/,e,p,?]? @@ -1,2 +1,3 @@
554554
_10
555555
+15
556556
_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,?]?_
558558
EOF
559559
test_write_lines s y /1,2 | git add -p >actual &&
560560
tail -n 5 <actual >actual.trimmed &&
@@ -567,7 +567,7 @@ test_expect_success 'navigate to hunk via regex / pattern' '
567567
_10
568568
+15
569569
_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,?]?_
571571
EOF
572572
test_write_lines s y / 1,2 | git add -p >actual &&
573573
tail -n 4 <actual >actual.trimmed &&
@@ -579,11 +579,11 @@ test_expect_success 'print again the hunk' '
579579
tr _ " " >expect <<-EOF &&
580580
+15
581581
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 @@
583583
10
584584
+15
585585
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,?]?_
587587
EOF
588588
test_write_lines s y g 1 p | git add -p >actual &&
589589
tail -n 7 <actual >actual.trimmed &&
@@ -595,11 +595,11 @@ test_expect_success TTY 'print again the hunk (PAGER)' '
595595
cat >expect <<-EOF &&
596596
<GREEN>+<RESET><GREEN>15<RESET>
597597
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>
599599
PAGER 10<RESET>
600600
PAGER <GREEN>+<RESET><GREEN>15<RESET>
601601
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>
603603
EOF
604604
test_write_lines s y g 1 P |
605605
(
@@ -802,15 +802,15 @@ test_expect_success 'colors can be overridden' '
802802
<BOLD>-old<RESET>
803803
<BLUE>+<RESET><BLUE>new<RESET>
804804
<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>
806806
<CYAN> more-context<RESET>
807807
<BLUE>+<RESET><BLUE>another-one<RESET>
808-
<YELLOW>(2/2) Stage this hunk [y,n,q,a,d,K,g,/,e,p,?]? <RESET><MAGENTA>@@ -1,3 +1,3 @@<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>
809809
<CYAN> context<RESET>
810810
<BOLD>-old<RESET>
811811
<BLUE>+new<RESET>
812812
<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>
814814
EOF
815815
test_cmp expect actual
816816
'
@@ -1385,4 +1385,50 @@ test_expect_success 'splitting edited hunk' '
13851385
test_cmp expect actual
13861386
'
13871387

1388+
test_expect_success 'options J, K roll over' '
1389+
test_write_lines a b c d e f g h i >file &&
1390+
git add file &&
1391+
test_write_lines X b c d e f g h X >file &&
1392+
test_write_lines J J K q | git add -p >out &&
1393+
test_write_lines 1 2 1 2 >expect &&
1394+
sed -n -e "s-/.*--" -e "s/^(//p" <out >actual &&
1395+
test_cmp expect actual
1396+
'
1397+
1398+
test_expect_success 'options y, n, a, d, j, k, e roll over to next undecided (1)' '
1399+
test_write_lines a b c d e f g h i j k l m n o p q >file &&
1400+
git add file &&
1401+
test_write_lines X b c d e f g h X j k l m n o p X >file &&
1402+
test_set_editor : &&
1403+
test_write_lines g3 y g3 n g3 a g3 d g3 j g3 e k q | git add -p >out &&
1404+
test_write_lines 1 3 1 3 1 3 1 3 1 3 1 3 1 2 >expect &&
1405+
sed -n -e "s-/.*--" -e "s/^(//p" <out >actual &&
1406+
test_cmp expect actual
1407+
'
1408+
1409+
test_expect_success 'options y, n, a, d, j, k, e roll over to next undecided (2)' '
1410+
test_write_lines a b c d e f g h i j k l m n o p q >file &&
1411+
git add file &&
1412+
test_write_lines X b c d e f g h X j k l m n o p X >file &&
1413+
test_set_editor : &&
1414+
test_write_lines y g3 y g3 n g3 a g3 d g3 j g3 e g1 k q | git add -p >out &&
1415+
test_write_lines 1 2 3 2 3 2 3 2 3 2 3 2 3 2 1 2 >expect &&
1416+
sed -n -e "s-/.*--" -e "s/^(//p" <out >actual &&
1417+
test_cmp expect actual
1418+
'
1419+
1420+
test_expect_success 'invalid option s is rejected' '
1421+
test_write_lines a b c d e f g h i j k >file &&
1422+
git add file &&
1423+
test_write_lines X b X d e f g h i j X >file &&
1424+
test_write_lines j s q | git add -p >out &&
1425+
sed -ne "s/ @@.*//" -e "s/ \$//" -e "/^(/p" <out >actual &&
1426+
cat >expect <<-EOF &&
1427+
(1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,s,e,p,?]?
1428+
(2/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,?]? Sorry, cannot split this hunk
1429+
(2/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,?]?
1430+
EOF
1431+
test_cmp expect actual
1432+
'
1433+
13881434
test_done

0 commit comments

Comments
 (0)