git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH 2/2] add-patch: render hunks through the pager

From
Rubén Justo <rjusto@gmail.com>
Date
Jul 25, 2024, 16:43 UTC
Message-ID
<38e190de-cbe4-4f75-acdb-fe566e541179@gmail.com>
In-Reply-To
<24e83a0f-b0c8-4cd5-b321-1d7702b844ce@gmail.com>

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.

For the tests, avoid the common construct to set and export one-shot variables within the scope of a command:

    VAR=VAL command args

It happens that when "command" is a shell function that in turn executes a "command", the behavior with "VAR" varies depending on the shell:

 ** Bash 5.2.21 **
    $ f () { bash -c 'echo A=$A'; }
    $ A=1 f
    A=1
 ** dash 0.5.12-9 **
    $ f () { bash -c 'echo A=$A'; }
    $ A=1 f
    A=1
 ** dash 0.5.10.2-6 **
    $ f () { bash -c 'echo A=$A'; }
    $ A=1 f
    A=
POSIX explicitly says the effect of this construct is unspecified.

One of our CI jobs on GitHub Actions uses Ubuntu 20.04 running dash 0.5.10.2-6, so avoid using the construct and use a subshell instead.

Signed-off-by: Rubén Justo <rjusto@gmail.com>
---
 add-patch.c                | 18 +++++++++++++++---
 t/t3701-add-interactive.sh | 32 ++++++++++++++++++++++++++++++++
 2 files changed, 47 insertions(+), 3 deletions(-)
diff --git a/add-patch.c b/add-patch.c
index 6e176cd21a..f2c76b7d83 100644
--- a/add-patch.c
+++ b/add-patch.c
@@ -7,9 +7,11 @@
 #include "environment.h"
 #include "gettext.h"
 #include "object-name.h"
+#include "pager.h"
 #include "read-cache-ll.h"
 #include "repository.h"
 #include "strbuf.h"
+#include "sigchain.h"
 #include "run-command.h"
 #include "strvec.h"
 #include "pathspec.h"
@@ -1391,7 +1393,7 @@ N_("j - leave this hunk undecided, see next undecided hunk\n"
    "/ - search for a hunk matching the given regex\n"
    "s - split the current hunk into smaller hunks\n"
    "e - manually edit the current hunk\n"
-   "p - print the current hunk\n"
+   "p - print the current hunk, 'P' to use the pager\n"
    "? - print help\n");
 
 static int patch_update_file(struct add_p_state *s,
@@ -1402,7 +1404,7 @@ static int patch_update_file(struct add_p_state *s,
 	struct hunk *hunk;
 	char ch;
 	struct child_process cp = CHILD_PROCESS_INIT;
-	int colored = !!s->colored.len, quit = 0;
+	int colored = !!s->colored.len, quit = 0, use_pager = 0;
 	enum prompt_mode_type prompt_mode_type;
 	enum {
 		ALLOW_GOTO_PREVIOUS_HUNK = 1 << 0,
@@ -1452,9 +1454,18 @@ static int patch_update_file(struct add_p_state *s,
 		strbuf_reset(&s->buf);
 		if (file_diff->hunk_nr) {
 			if (rendered_hunk_index != hunk_index) {
+				if (use_pager) {
+					setup_pager();
+					sigchain_push(SIGPIPE, SIG_IGN);
+				}
 				render_hunk(s, hunk, 0, colored, &s->buf);
 				fputs(s->buf.buf, stdout);
 				rendered_hunk_index = hunk_index;
+				if (use_pager) {
+					sigchain_pop(SIGPIPE);
+					wait_for_pager();
+					use_pager = 0;
+				}
 			}
 
 			strbuf_reset(&s->buf);
@@ -1675,8 +1686,9 @@ static int patch_update_file(struct add_p_state *s,
 				hunk->use = USE_HUNK;
 				goto soft_increment;
 			}
-		} else if (s->answer.buf[0] == 'p') {
+		} else if (ch == 'p') {
 			rendered_hunk_index = -1;
+			use_pager = (s->answer.buf[0] == 'P') ? 1 : 0;
 		} else if (s->answer.buf[0] == '?') {
 			const char *p = _(help_patch_remainder), *eol = p;
 
diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh
index 6daf3a6be0..1b8617e0c1 100755
--- a/t/t3701-add-interactive.sh
+++ b/t/t3701-add-interactive.sh
@@ -591,6 +591,38 @@ test_expect_success 'print again the hunk' '
 	test_cmp expect actual.trimmed
 '
 
+test_expect_success TTY 'print again the hunk (PAGER)' '
+	test_when_finished "git reset" &&
+	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>
+	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>
+	EOF
+	test_write_lines s y g 1 P |
+	(
+		GIT_PAGER="sed s/^/PAGER\ /" &&
+		export GIT_PAGER &&
+		test_terminal git add -p >actual
+	) &&
+	tail -n 7 <actual | test_decode_color >actual.trimmed &&
+	test_cmp expect actual.trimmed
+'
+
+test_expect_success TTY 'P handles SIGPIPE when writing to pager' '
+	test_when_finished "rm -f huge_file; git reset" &&
+	printf "\n%2500000s" Y >huge_file &&
+	git add -N huge_file &&
+	test_write_lines P q | (
+		GIT_PAGER="head -n 1" &&
+		export GIT_PAGER &&
+		test_terminal git add -p
+	)
+'
+
 test_expect_success 'split hunk "add -p (edit)"' '
 	# Split, say Edit and do nothing.  Then:
 	#
-- 
2.46.0.rc0.4.g6f4990c0d4
Previous: Rubén JustoNext: Junio C Hamano
Message 20 of 29 in “add-p P fixups”
  1. 0/2 add-p P fixupsRubén Justo, Jul 23, 2024
  2. 1/2 t3701: avoid one-shot export for shell functionsRubén Justo, Jul 23, 2024
  3. 2/2 pager: make wait_for_pager a no-op for "cat"Rubén Justo, Jul 23, 2024
  4. Junio C HamanoJul 23, 2024
  5. Phillip WoodJul 23, 2024
  6. Junio C HamanoJul 23, 2024
  7. Rubén JustoJul 23, 2024
  8. phillip.wood123@gmail.comJul 24, 2024
  9. Rubén JustoJul 24, 2024
  10. Phillip WoodJul 25, 2024
  11. Rubén JustoJul 25, 2024
  12. 0/4 squash fixups in rj/add-p-pagerRubén Justo, Jul 25, 2024
  13. 1/4 add-patch: test for 'p' commandRubén Justo, Jul 25, 2024
  14. 2/4 pager: do not close fd 2 unnecessarilyRubén Justo, Jul 25, 2024
  15. 3/4 pager: introduce wait_for_pagerRubén Justo, Jul 25, 2024
  16. 4/4 add-patch: render hunks through the pagerRubén Justo, Jul 25, 2024
  17. Junio C HamanoJul 25, 2024
  18. Re* [PATCH v2 0/2] add-p P fixupsRubén Justo, Jul 25, 2024
  19. 1/2 pager: introduce wait_for_pagerRubén Justo, Jul 25, 2024
  20. 2/2 add-patch: render hunks through the pagerRubén Justo, Jul 25, 2024
  21. Junio C HamanoJul 26, 2024
  22. Junio C HamanoJul 27, 2024
  23. Rubén JustoJul 27, 2024
  24. Junio C HamanoJul 26, 2024
  25. Rubén JustoJul 26, 2024
  26. Junio C HamanoJul 26, 2024
  27. Rubén JustoJul 26, 2024
  28. Rubén JustoJul 28, 2024
  29. Junio C HamanoJul 29, 2024

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.