add-patch: render hunks through the pager

Make the print command trigger the pager when invoked using a capital 'P', to make it easier for the user to review long hunks. Note that if the PAGER ends unexpectedly before we've been able to send the payload, perhaps because the user is not interested in the whole thing, we might receive a SIGPIPE, which would abruptly and unexpectedly terminate the interactive session for the user. Therefore, we need to ignore a possible SIGPIPE signal. Add a test for this, in addition to the test for normal operation. For the SIGPIPE test, we need to make sure that we completely fill the operating system's buffer, otherwise we might not trigger the SIGPIPE signal. The normal size of this buffer in different OSs varies from a few KBs to 1MB. Use a payload large enough to guarantee that we exceed this limit. Signed-off-by: Rubén Justo <rjusto@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Rubén Justo committed Jul 25, 2024 at 15:44 UTC fc87b2f7c18d0a097c80e4d9ef1f84da687636ab
2 files changed +47 -3
add-patch.c
+15 -3
@@ -7,9 +7,11 @@
7 #include "environment.h"
8 #include "gettext.h"
9 #include "object-name.h"
10 +#include "pager.h"
11 #include "read-cache-ll.h"
12 #include "repository.h"
13 #include "strbuf.h"
14 +#include "sigchain.h"
15 #include "run-command.h"
16 #include "strvec.h"
17 #include "pathspec.h"
@@ -1391,7 +1393,7 @@ N_("j - leave this hunk undecided, see next undecided hunk\n"
1393 "/ - search for a hunk matching the given regex\n"
1394 "s - split the current hunk into smaller hunks\n"
1395 "e - manually edit the current hunk\n"
1394 - "p - print the current hunk\n"
1396 + "p - print the current hunk, 'P' to use the pager\n"
1397 "? - print help\n");
1398
1399 static int patch_update_file(struct add_p_state *s,
@@ -1402,7 +1404,7 @@ static int patch_update_file(struct add_p_state *s,
1404 struct hunk *hunk;
1405 char ch;
1406 struct child_process cp = CHILD_PROCESS_INIT;
1405 - int colored = !!s->colored.len, quit = 0;
1407 + int colored = !!s->colored.len, quit = 0, use_pager = 0;
1408 enum prompt_mode_type prompt_mode_type;
1409 enum {
1410 ALLOW_GOTO_PREVIOUS_HUNK = 1 << 0,
@@ -1452,9 +1454,18 @@ static int patch_update_file(struct add_p_state *s,
1454 strbuf_reset(&s->buf);
1455 if (file_diff->hunk_nr) {
1456 if (rendered_hunk_index != hunk_index) {
1457 + if (use_pager) {
1458 + setup_pager();
1459 + sigchain_push(SIGPIPE, SIG_IGN);
1460 + }
1461 render_hunk(s, hunk, 0, colored, &s->buf);
1462 fputs(s->buf.buf, stdout);
1463 rendered_hunk_index = hunk_index;
1464 + if (use_pager) {
1465 + sigchain_pop(SIGPIPE);
1466 + wait_for_pager();
1467 + use_pager = 0;
1468 + }
1469 }
1470
1471 strbuf_reset(&s->buf);
@@ -1675,8 +1686,9 @@ soft_increment:
1686 hunk->use = USE_HUNK;
1687 goto soft_increment;
1688 }
1678 - } else if (s->answer.buf[0] == 'p') {
1689 + } else if (ch == 'p') {
1690 rendered_hunk_index = -1;
1691 + use_pager = (s->answer.buf[0] == 'P') ? 1 : 0;
1692 } else if (s->answer.buf[0] == '?') {
1693 const char *p = _(help_patch_remainder), *eol = p;
1694
t/t3701-add-interactive.sh
+32
@@ -591,6 +591,38 @@ test_expect_success 'print again the hunk' '
591 test_cmp expect actual.trimmed
592 '
593
594 +test_expect_success TTY 'print again the hunk (PAGER)' '
595 + test_when_finished "git reset" &&
596 + cat >expect <<-EOF &&
597 + <GREEN>+<RESET><GREEN>15<RESET>
598 + 20<RESET>
599 + <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>
600 + PAGER 10<RESET>
601 + PAGER <GREEN>+<RESET><GREEN>15<RESET>
602 + PAGER 20<RESET>
603 + <BOLD;BLUE>(1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,p,?]? <RESET>
604 + EOF
605 + test_write_lines s y g 1 P |
606 + (
607 + GIT_PAGER="sed s/^/PAGER\ /" &&
608 + export GIT_PAGER &&
609 + test_terminal git add -p >actual
610 + ) &&
611 + tail -n 7 <actual | test_decode_color >actual.trimmed &&
612 + test_cmp expect actual.trimmed
613 +'
614 +
615 +test_expect_success TTY 'P handles SIGPIPE when writing to pager' '
616 + test_when_finished "rm -f huge_file; git reset" &&
617 + printf "\n%2500000s" Y >huge_file &&
618 + git add -N huge_file &&
619 + test_write_lines P q | (
620 + GIT_PAGER="head -n 1" &&
621 + export GIT_PAGER &&
622 + test_terminal git add -p
623 + )
624 +'
625 +
626 test_expect_success 'split hunk "add -p (edit)"' '
627 # Split, say Edit and do nothing. Then:
628 #