@matheusbsbs / matheus-bagliano / commits / 9254bdfb4f
Fork 0

built-in add -p: implement the 'g' ("goto") command

With this patch, it is now possible to see a summary of the available hunks and to navigate between them (by number). A test is added to verify that this behavior matches the one of the Perl version of `git add -p`. Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Johannes Schindelin committed Dec 13, 2019 at 08:08 UTC 9254bdfb4f971be4be5364341a0387e8a1f3734a
2 files changed +104
add-patch.c
+88
@@ -955,6 +955,54 @@ static int edit_hunk_loop(struct add_p_state *s,
955 }
956 }
957
958 +#define SUMMARY_HEADER_WIDTH 20
959 +#define SUMMARY_LINE_WIDTH 80
960 +static void summarize_hunk(struct add_p_state *s, struct hunk *hunk,
961 + struct strbuf *out)
962 +{
963 + struct hunk_header *header = &hunk->header;
964 + struct strbuf *plain = &s->plain;
965 + size_t len = out->len, i;
966 +
967 + strbuf_addf(out, " -%lu,%lu +%lu,%lu ",
968 + header->old_offset, header->old_count,
969 + header->new_offset, header->new_count);
970 + if (out->len - len < SUMMARY_HEADER_WIDTH)
971 + strbuf_addchars(out, ' ',
972 + SUMMARY_HEADER_WIDTH + len - out->len);
973 + for (i = hunk->start; i < hunk->end; i = find_next_line(plain, i))
974 + if (plain->buf[i] != ' ')
975 + break;
976 + if (i < hunk->end)
977 + strbuf_add(out, plain->buf + i, find_next_line(plain, i) - i);
978 + if (out->len - len > SUMMARY_LINE_WIDTH)
979 + strbuf_setlen(out, len + SUMMARY_LINE_WIDTH);
980 + strbuf_complete_line(out);
981 +}
982 +
983 +#define DISPLAY_HUNKS_LINES 20
984 +static size_t display_hunks(struct add_p_state *s,
985 + struct file_diff *file_diff, size_t start_index)
986 +{
987 + size_t end_index = start_index + DISPLAY_HUNKS_LINES;
988 +
989 + if (end_index > file_diff->hunk_nr)
990 + end_index = file_diff->hunk_nr;
991 +
992 + while (start_index < end_index) {
993 + struct hunk *hunk = file_diff->hunk + start_index++;
994 +
995 + strbuf_reset(&s->buf);
996 + strbuf_addf(&s->buf, "%c%2d: ", hunk->use == USE_HUNK ? '+'
997 + : hunk->use == SKIP_HUNK ? '-' : ' ',
998 + (int)start_index);
999 + summarize_hunk(s, hunk, &s->buf);
1000 + fputs(s->buf.buf, stdout);
1001 + }
1002 +
1003 + return end_index;
1004 +}
1005 +
1006 static const char help_patch_text[] =
1007 N_("y - stage this hunk\n"
1008 "n - do not stage this hunk\n"
@@ -964,6 +1012,7 @@ N_("y - stage this hunk\n"
1012 "J - leave this hunk undecided, see next hunk\n"
1013 "k - leave this hunk undecided, see previous undecided hunk\n"
1014 "K - leave this hunk undecided, see previous hunk\n"
1015 + "g - select a hunk to go to\n"
1016 "s - split the current hunk into smaller hunks\n"
1017 "e - manually edit the current hunk\n"
1018 "? - print help\n");
@@ -1022,6 +1071,8 @@ static int patch_update_file(struct add_p_state *s,
1071 strbuf_addstr(&s->buf, ",j");
1072 if (hunk_index + 1 < file_diff->hunk_nr)
1073 strbuf_addstr(&s->buf, ",J");
1074 + if (file_diff->hunk_nr > 1)
1075 + strbuf_addstr(&s->buf, ",g");
1076 if (hunk->splittable_into > 1)
1077 strbuf_addstr(&s->buf, ",s");
1078 if (hunk_index + 1 > file_diff->mode_change &&
@@ -1089,6 +1140,43 @@ soft_increment:
1140 hunk_index = undecided_next;
1141 else
1142 err(s, _("No next hunk"));
1143 + } else if (s->answer.buf[0] == 'g') {
1144 + char *pend;
1145 + unsigned long response;
1146 +
1147 + if (file_diff->hunk_nr < 2) {
1148 + err(s, _("No other hunks to goto"));
1149 + continue;
1150 + }
1151 + strbuf_remove(&s->answer, 0, 1);
1152 + strbuf_trim(&s->answer);
1153 + i = hunk_index - DISPLAY_HUNKS_LINES / 2;
1154 + if (i < file_diff->mode_change)
1155 + i = file_diff->mode_change;
1156 + while (s->answer.len == 0) {
1157 + i = display_hunks(s, file_diff, i);
1158 + printf("%s", i < file_diff->hunk_nr ?
1159 + _("go to which hunk (<ret> to see "
1160 + "more)? ") : _("go to which hunk? "));
1161 + fflush(stdout);
1162 + if (strbuf_getline(&s->answer,
1163 + stdin) == EOF)
1164 + break;
1165 + strbuf_trim_trailing_newline(&s->answer);
1166 + }
1167 +
1168 + strbuf_trim(&s->answer);
1169 + response = strtoul(s->answer.buf, &pend, 10);
1170 + if (*pend || pend == s->answer.buf)
1171 + err(s, _("Invalid number: '%s'"),
1172 + s->answer.buf);
1173 + else if (0 < response && response <= file_diff->hunk_nr)
1174 + hunk_index = response - 1;
1175 + else
1176 + err(s, Q_("Sorry, only %d hunk available.",
1177 + "Sorry, only %d hunks available.",
1178 + file_diff->hunk_nr),
1179 + (int)file_diff->hunk_nr);
1180 } else if (s->answer.buf[0] == 's') {
1181 size_t splittable_into = hunk->splittable_into;
1182 if (splittable_into < 2)
t/t3701-add-interactive.sh
+16
@@ -413,6 +413,22 @@ test_expect_success 'split hunk setup' '
413 test_write_lines 10 15 20 21 22 23 24 30 40 50 60 >test
414 '
415
416 +test_expect_success 'goto hunk' '
417 + test_when_finished "git reset" &&
418 + tr _ " " >expect <<-EOF &&
419 + (2/2) Stage this hunk [y,n,q,a,d,K,g,/,e,?]? + 1: -1,2 +1,3 +15
420 + _ 2: -2,4 +3,8 +21
421 + go to which hunk? @@ -1,2 +1,3 @@
422 + _10
423 + +15
424 + _20
425 + (1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,?]?_
426 + EOF
427 + test_write_lines s y g 1 | git add -p >actual &&
428 + tail -n 7 <actual >actual.trimmed &&
429 + test_cmp expect actual.trimmed
430 +'
431 +
432 test_expect_success 'split hunk "add -p (edit)"' '
433 # Split, say Edit and do nothing. Then:
434 #