diff mbox series

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

Message ID 5effca4d-536c-4e51-a024-5f1e90583176@gmail.com (mailing list archive)
State Superseded
Headers show
Series use the pager in 'add -p' | expand

Commit Message

Rubén Justo July 12, 2024, 1 a.m. UTC
Make the print command to trigger the pager when invoked using a capital
'P', to make it easier for the user to review long hunks.

Signed-off-by: Rubén Justo <rjusto@gmail.com>
---
 add-patch.c                | 18 +++++++++++++---
 t/t3701-add-interactive.sh | 44 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 59 insertions(+), 3 deletions(-)

Comments

Dragan Simic July 12, 2024, 8:58 a.m. UTC | #1
On 2024-07-12 03:00, Rubén Justo wrote:
> @@ -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");

I'm fine with this compact form, even though it diverges from
the "one command per help line" rule.
Phillip Wood July 12, 2024, 1:26 p.m. UTC | #2
Hi Rubén

On 12/07/2024 02:00, Rubén Justo wrote:
> Make the print command to trigger the pager when invoked using a capital

s/to//

> 'P', to make it easier for the user to review long hunks.
> 
> Signed-off-by: Rubén Justo <rjusto@gmail.com>

Thanks for working on this. The code changes all look good, I'm a bit 
confused by this test though

> +test_expect_success TTY 'P does not break if pager ends unexpectly' '
> +	test_when_finished "rm -f huge_file; git reset" &&
> +	printf "%2500000s" Y >huge_file &&
> +	git add -N huge_file &&
> +	cat >expect <<-EOF &&
> +	<GREEN>+<RESET><GREEN>22<RESET>
> +	<GREEN>+<RESET><GREEN>23<RESET>
> +	<GREEN>+<RESET><GREEN>24<RESET>
> +	 30<RESET>
> +	 40<RESET>
> +	 50<RESET>
> +	<BOLD;BLUE>(1/1) Stage this hunk [y,n,q,a,d,s,e,p,?]? <RESET>
> +	EOF
> +	test_write_lines P |
> +	(
> +		GIT_PAGER="head -1" &&
> +		export GIT_PAGER &&
> +		test_terminal git add -p >actual
> +	) &&
> +	tail -n 7 <actual | test_decode_color >actual.trimmed &&
> +	test_cmp expect actual.trimmed
> +'

What is huge_file doing and what happens to the single line of pager output?

Thanks

Phillip

>   test_expect_success 'split hunk "add -p (edit)"' '
>   	# Split, say Edit and do nothing.  Then:
>   	#
Rubén Justo July 12, 2024, 4:24 p.m. UTC | #3
On Fri, Jul 12, 2024 at 02:26:22PM +0100, Phillip Wood wrote:

> > +test_expect_success TTY 'P does not break if pager ends unexpectly' '
> > +	test_when_finished "rm -f huge_file; git reset" &&
> > +	printf "%2500000s" Y >huge_file &&
> > +	git add -N huge_file &&
> > +	cat >expect <<-EOF &&
> > +	<GREEN>+<RESET><GREEN>22<RESET>
> > +	<GREEN>+<RESET><GREEN>23<RESET>
> > +	<GREEN>+<RESET><GREEN>24<RESET>
> > +	 30<RESET>
> > +	 40<RESET>
> > +	 50<RESET>
> > +	<BOLD;BLUE>(1/1) Stage this hunk [y,n,q,a,d,s,e,p,?]? <RESET>
> > +	EOF
> > +	test_write_lines P |
> > +	(
> > +		GIT_PAGER="head -1" &&
> > +		export GIT_PAGER &&
> > +		test_terminal git add -p >actual
> > +	) &&
> > +	tail -n 7 <actual | test_decode_color >actual.trimmed &&
> > +	test_cmp expect actual.trimmed
> > +'
> 
> What is huge_file doing and what happens to the single line of pager output?

The huge file is to make sure we are receiving a SIGPIPE.  We don't
really care about the line "head -1" produces, only that we don't
break due to the SIGPIPE that occurs.

Maybe a test like this would be clearer?

test_expect_success TTY 'P does not break if pager ends unexpectedly' '
	test_when_finished "rm -f huge_file; git reset" &&
	printf "%2500000s\nfrotz\n" Y >huge_file &&
	git add -N huge_file &&
	cat >expect <<-EOF &&
	<GREEN>+<RESET><GREEN>frotz<RESET>
	<BOLD;BLUE>(1/1) Stage addition [y,n,q,a,d,e,p,?]? <RESET><CYAN>@@ -0,0 +1,2 @@<RESET>
	<BOLD;BLUE>(1/1) Stage addition [y,n,q,a,d,e,p,?]? <RESET>
	EOF
	test_write_lines P q |
	(
		GIT_PAGER="head -1" &&
		export GIT_PAGER &&
		test_terminal git add -p >actual
	) &&
	tail -n 3 <actual | test_decode_color >actual.trimmed &&
	test_cmp expect actual.trimmed
'

> 
> Thanks

Thank you.
Rubén Justo July 13, 2024, 3:23 a.m. UTC | #4
On 13/7/24 1:24, Rubén Justo wrote:
 
> Maybe a test like this would be clearer?

test_expect_success TTY 'P does not break if pager ends unexpectedly' '
       test_when_finished "rm -f huge_file; git reset" &&
       printf "%2500000s" Y >huge_file &&
       git add -N huge_file &&
       test_write_lines P q | GIT_PAGER="head -c 1" test_terminal git add -p
'
Junio C Hamano July 13, 2024, 9:12 a.m. UTC | #5
Rubén Justo <rjusto@gmail.com> writes:

>> What is huge_file doing and what happens to the single line of pager output?
>
> The huge file is to make sure we are receiving a SIGPIPE.  We don't
> really care about the line "head -1" produces, only that we don't
> break due to the SIGPIPE that occurs.

That deserves to be explained in the proposed log message, I think.

Thanks.
Phillip Wood July 13, 2024, 1:17 p.m. UTC | #6
On 12/07/2024 17:24, Rubén Justo wrote:
> On Fri, Jul 12, 2024 at 02:26:22PM +0100, Phillip Wood wrote:
> 
>>> +test_expect_success TTY 'P does not break if pager ends unexpectly' '
>>> +	test_when_finished "rm -f huge_file; git reset" &&
>>> +	printf "%2500000s" Y >huge_file &&
>>> +	git add -N huge_file &&
>>> +	cat >expect <<-EOF &&
>>> +	<GREEN>+<RESET><GREEN>22<RESET>
>>> +	<GREEN>+<RESET><GREEN>23<RESET>
>>> +	<GREEN>+<RESET><GREEN>24<RESET>
>>> +	 30<RESET>
>>> +	 40<RESET>
>>> +	 50<RESET>
>>> +	<BOLD;BLUE>(1/1) Stage this hunk [y,n,q,a,d,s,e,p,?]? <RESET>
>>> +	EOF
>>> +	test_write_lines P |
>>> +	(
>>> +		GIT_PAGER="head -1" &&
>>> +		export GIT_PAGER &&
>>> +		test_terminal git add -p >actual
>>> +	) &&
>>> +	tail -n 7 <actual | test_decode_color >actual.trimmed &&
>>> +	test_cmp expect actual.trimmed
>>> +'
>>
>> What is huge_file doing and what happens to the single line of pager output?
> 
> The huge file is to make sure we are receiving a SIGPIPE.  We don't
> really care about the line "head -1" produces, only that we don't
> break due to the SIGPIPE that occurs.

As Junio said it would help to explain that. I'm still confused why we 
don't see any output from the pager - shouldn't the pager print the hunk 
header as it does in the example below?

> Maybe a test like this would be clearer?

I think explaining in the commit message would be best.

Thanks

Phillip

> test_expect_success TTY 'P does not break if pager ends unexpectedly' '
> 	test_when_finished "rm -f huge_file; git reset" &&
> 	printf "%2500000s\nfrotz\n" Y >huge_file &&
> 	git add -N huge_file &&
> 	cat >expect <<-EOF &&
> 	<GREEN>+<RESET><GREEN>frotz<RESET>
> 	<BOLD;BLUE>(1/1) Stage addition [y,n,q,a,d,e,p,?]? <RESET><CYAN>@@ -0,0 +1,2 @@<RESET>
> 	<BOLD;BLUE>(1/1) Stage addition [y,n,q,a,d,e,p,?]? <RESET>
> 	EOF
> 	test_write_lines P q |
> 	(
> 		GIT_PAGER="head -1" &&
> 		export GIT_PAGER &&
> 		test_terminal git add -p >actual
> 	) &&
> 	tail -n 3 <actual | test_decode_color >actual.trimmed &&
> 	test_cmp expect actual.trimmed
> '
> 
>>
>> Thanks
> 
> Thank you.
Rubén Justo July 13, 2024, 11:13 p.m. UTC | #7
On Sat, Jul 13, 2024 at 02:17:49PM +0100, phillip.wood123@gmail.com wrote:
> On 12/07/2024 17:24, Rubén Justo wrote:
> > On Fri, Jul 12, 2024 at 02:26:22PM +0100, Phillip Wood wrote:
> > 
> > > > +test_expect_success TTY 'P does not break if pager ends unexpectly' '
> > > > +	test_when_finished "rm -f huge_file; git reset" &&
> > > > +	printf "%2500000s" Y >huge_file &&
> > > > +	git add -N huge_file &&
> > > > +	cat >expect <<-EOF &&
> > > > +	<GREEN>+<RESET><GREEN>22<RESET>
> > > > +	<GREEN>+<RESET><GREEN>23<RESET>
> > > > +	<GREEN>+<RESET><GREEN>24<RESET>
> > > > +	 30<RESET>
> > > > +	 40<RESET>
> > > > +	 50<RESET>
> > > > +	<BOLD;BLUE>(1/1) Stage this hunk [y,n,q,a,d,s,e,p,?]? <RESET>
> > > > +	EOF
> > > > +	test_write_lines P |
> > > > +	(
> > > > +		GIT_PAGER="head -1" &&
> > > > +		export GIT_PAGER &&
> > > > +		test_terminal git add -p >actual
> > > > +	) &&
> > > > +	tail -n 7 <actual | test_decode_color >actual.trimmed &&
> > > > +	test_cmp expect actual.trimmed
> > > > +'
> > > 
> > > What is huge_file doing and what happens to the single line of pager output?
> > 
> [...]
> 
> I'm still confused why we don't
> see any output from the pager - shouldn't the pager print the hunk header as
> it does in the example below?

In the test above, we would need to use "tail -n 18" to capture the
header of the hunk.

In the test below, the "q" allows "tail -n 3" to capture it. 

> > test_expect_success TTY 'P does not break if pager ends unexpectedly' '
> > 	test_when_finished "rm -f huge_file; git reset" &&
> > 	printf "%2500000s\nfrotz\n" Y >huge_file &&
> > 	git add -N huge_file &&
> > 	cat >expect <<-EOF &&
> > 	<GREEN>+<RESET><GREEN>frotz<RESET>
> > 	<BOLD;BLUE>(1/1) Stage addition [y,n,q,a,d,e,p,?]? <RESET><CYAN>@@ -0,0 +1,2 @@<RESET>
> > 	<BOLD;BLUE>(1/1) Stage addition [y,n,q,a,d,e,p,?]? <RESET>
> > 	EOF
> > 	test_write_lines P q |
> > 	(
> > 		GIT_PAGER="head -1" &&
> > 		export GIT_PAGER &&
> > 		test_terminal git add -p >actual
> > 	) &&
> > 	tail -n 3 <actual | test_decode_color >actual.trimmed &&
> > 	test_cmp expect actual.trimmed
> > '

I wonder if the following would be desirable: 

--- a/add-patch.c
+++ b/add-patch.c
@@@ -1519,8 -1519,8 +1519,10 @@@ static int patch_update_file(struct add
  		if (*s->s.reset_color)
  			fputs(s->s.reset_color, stdout);
  		fflush(stdout);
--		if (read_single_character(s) == EOF)
++		if (read_single_character(s) == EOF) {
++			quit = 1;
  			break;
++		}
  
  		if (!s->answer.len)
  			continue;
diff mbox series

Patch

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..bf82a9dc35 100755
--- a/t/t3701-add-interactive.sh
+++ b/t/t3701-add-interactive.sh
@@ -591,6 +591,50 @@  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 does not break if pager ends unexpectly' '
+	test_when_finished "rm -f huge_file; git reset" &&
+	printf "%2500000s" Y >huge_file &&
+	git add -N huge_file &&
+	cat >expect <<-EOF &&
+	<GREEN>+<RESET><GREEN>22<RESET>
+	<GREEN>+<RESET><GREEN>23<RESET>
+	<GREEN>+<RESET><GREEN>24<RESET>
+	 30<RESET>
+	 40<RESET>
+	 50<RESET>
+	<BOLD;BLUE>(1/1) Stage this hunk [y,n,q,a,d,s,e,p,?]? <RESET>
+	EOF
+	test_write_lines P |
+	(
+		GIT_PAGER="head -1" &&
+		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 'split hunk "add -p (edit)"' '
 	# Split, say Edit and do nothing.  Then:
 	#