diff mbox series

[v4] add-patch: response to unknown command

Message ID b209a2b8-f98f-4f14-a687-9022d30968dd@gmail.com (mailing list archive)
State Superseded
Commit ea7d103d395c07404861999ceca51e3267bf7e74
Headers show
Series [v4] add-patch: response to unknown command | expand

Commit Message

Rubén Justo April 21, 2024, 9:52 p.m. UTC
When the user gives an unknown command to the "add -p" prompt, the list
of accepted commands with their explanation is given.  This is the same
output they get when they say '?'.

However, the unknown command may be due to a user input error rather
than the user not knowing the valid command.

To reduce the likelihood of user confusion and error repetition, instead
of displaying the list of accepted commands, display a short error
message with the unknown command received, as feedback to the user.

Include a reminder about the current command '?' in the new message, to
guide the user if they want help.

Signed-off-by: Rubén Justo <rjusto@gmail.com>
---

The test now pass with GIT_TEST_DEFAULT_HASH=sha256.

Thanks.

Range-diff against v3:
1:  0317594bce ! 1:  b418b03f15 add-patch: response to unknown command
    @@ t/t3701-add-interactive.sh: test_expect_success 'warn about add.interactive.useB
     +	test_when_finished "git reset --hard; rm -f command" &&
     +	echo W >command &&
     +	git add -N command &&
    -+	cat >expect <<-EOF &&
    -+	diff --git a/command b/command
    -+	new file mode 100644
    -+	index 0000000..a42d8ff
    -+	--- /dev/null
    -+	+++ b/command
    -+	@@ -0,0 +1 @@
    -+	+W
    ++	git diff command >expect &&
    ++	cat >>expect <<-EOF &&
     +	(1/1) Stage addition [y,n,q,a,d,e,p,?]? Unknown command ${SQ}W${SQ} (use ${SQ}?${SQ} for help)
     +	(1/1) Stage addition [y,n,q,a,d,e,p,?]?$SP
     +	EOF

 add-patch.c                |  5 ++++-
 t/t3701-add-interactive.sh | 16 +++++++++++++++-
 2 files changed, 19 insertions(+), 2 deletions(-)

Comments

Junio C Hamano April 22, 2024, 3:50 p.m. UTC | #1
Rubén Justo <rjusto@gmail.com> writes:

> 1:  0317594bce ! 1:  b418b03f15 add-patch: response to unknown command
>     @@ t/t3701-add-interactive.sh: test_expect_success 'warn about add.interactive.useB
>      +	test_when_finished "git reset --hard; rm -f command" &&
>      +	echo W >command &&
>      +	git add -N command &&
>     -+	cat >expect <<-EOF &&
>     -+	diff --git a/command b/command
>     -+	new file mode 100644
>     -+	index 0000000..a42d8ff
>     -+	--- /dev/null
>     -+	+++ b/command
>     -+	@@ -0,0 +1 @@
>     -+	+W
>     ++	git diff command >expect &&
>     ++	cat >>expect <<-EOF &&
>      +	(1/1) Stage addition [y,n,q,a,d,e,p,?]? Unknown command ${SQ}W${SQ} (use ${SQ}?${SQ} for help)
>      +	(1/1) Stage addition [y,n,q,a,d,e,p,?]?$SP
>      +	EOF

Interesting.

My first reaction was "how is this different from checking just the
last line of the actual output?  The early part of expect and actual
both come from an internal invocation of 'git diff', and they must
match by definition".

But that may really be the point of this test.

That is, we may later decide to tweak the way "git diff" hunks are
presented, and we expect that the way "git add -p" presents the
hunks would change together with it automatically.
Phillip Wood April 24, 2024, 10:15 a.m. UTC | #2
Hi Junio

On 22/04/2024 16:50, Junio C Hamano wrote:
> Rubén Justo <rjusto@gmail.com> writes:
> 
>> 1:  0317594bce ! 1:  b418b03f15 add-patch: response to unknown command
>>      @@ t/t3701-add-interactive.sh: test_expect_success 'warn about add.interactive.useB
>>       +	test_when_finished "git reset --hard; rm -f command" &&
>>       +	echo W >command &&
>>       +	git add -N command &&
>>      -+	cat >expect <<-EOF &&
>>      -+	diff --git a/command b/command
>>      -+	new file mode 100644
>>      -+	index 0000000..a42d8ff
>>      -+	--- /dev/null
>>      -+	+++ b/command
>>      -+	@@ -0,0 +1 @@
>>      -+	+W
>>      ++	git diff command >expect &&
>>      ++	cat >>expect <<-EOF &&
>>       +	(1/1) Stage addition [y,n,q,a,d,e,p,?]? Unknown command ${SQ}W${SQ} (use ${SQ}?${SQ} for help)
>>       +	(1/1) Stage addition [y,n,q,a,d,e,p,?]?$SP
>>       +	EOF
> 
> Interesting.
> 
> My first reaction was "how is this different from checking just the
> last line of the actual output?  The early part of expect and actual
> both come from an internal invocation of 'git diff', and they must
> match by definition".
> 
> But that may really be the point of this test.

Yes - we want to make sure that we are not printing the help and the 
only way to do that is to check the whole output

Best Wishes

Phillip

> That is, we may later decide to tweak the way "git diff" hunks are
> presented, and we expect that the way "git add -p" presents the
> hunks would change together with it automatically.
Junio C Hamano April 24, 2024, 3:35 p.m. UTC | #3
phillip.wood123@gmail.com writes:

> On 22/04/2024 16:50, Junio C Hamano wrote:
>> Rubén Justo <rjusto@gmail.com> writes:
>> 
>>> 1:  0317594bce ! 1:  b418b03f15 add-patch: response to unknown command
>>>      @@ t/t3701-add-interactive.sh: test_expect_success 'warn about add.interactive.useB
>>>       +	test_when_finished "git reset --hard; rm -f command" &&
>>>       +	echo W >command &&
>>>       +	git add -N command &&
>>>      -+	cat >expect <<-EOF &&
>>>      -+	diff --git a/command b/command
>>>      -+	new file mode 100644
>>>      -+	index 0000000..a42d8ff
>>>      -+	--- /dev/null
>>>      -+	+++ b/command
>>>      -+	@@ -0,0 +1 @@
>>>      -+	+W
>>>      ++	git diff command >expect &&
>>>      ++	cat >>expect <<-EOF &&
>>>       +	(1/1) Stage addition [y,n,q,a,d,e,p,?]? Unknown command ${SQ}W${SQ} (use ${SQ}?${SQ} for help)
>>>       +	(1/1) Stage addition [y,n,q,a,d,e,p,?]?$SP
>>>       +	EOF
>> Interesting.
>> My first reaction was "how is this different from checking just the
>> last line of the actual output?  The early part of expect and actual
>> both come from an internal invocation of 'git diff', and they must
>> match by definition".
>> But that may really be the point of this test.
>
> Yes - we want to make sure that we are not printing the help and the
> only way to do that is to check the whole output

I was not questioning that part of the patch.  "My first reaction"
was solely about use of "git diff" to replace the golden copy of
expected result in the test itself, only to allow for use of
different hash functions.  As you (or somebody else?) mentioned in
an earlier review, diff_cmp is there for exactly that purpose.

>> That is, we may later decide to tweak the way "git diff" hunks are
>> presented, and we expect that the way "git add -p" presents the
>> hunks would change together with it automatically.

This argument cuts both ways, by the way.

Insisting that the output match the explicit expectation (not what
"git diff" of the day produces) has a few advantages.  It makes the
test more explicit and easy to see what output we are expecting, and
more importantly, it forces us to update the test when we decide to
tweak the output from the command being tested (i.e. hunk selection
UI) and/or the output from "git diff" command.
Jeff King April 25, 2024, 1:44 a.m. UTC | #4
On Sun, Apr 21, 2024 at 11:52:33PM +0200, Rubén Justo wrote:

> +test_expect_success 'unknown command' '
> +	test_when_finished "git reset --hard; rm -f command" &&
> +	echo W >command &&
> +	git add -N command &&
> +	git diff command >expect &&
> +	cat >>expect <<-EOF &&
> +	(1/1) Stage addition [y,n,q,a,d,e,p,?]? Unknown command ${SQ}W${SQ} (use ${SQ}?${SQ} for help)
> +	(1/1) Stage addition [y,n,q,a,d,e,p,?]?$SP
> +	EOF
> +	git add -p -- command <command >actual 2>&1 &&
> +	test_cmp expect actual
> +'

I got a test failure on Windows CI from this. The test_cmp output looks
like this:

  -(1/1) Stage addition [y,n,q,a,d,e,p,?]? Unknown command 'W' (use '?' for help)
  -(1/1) Stage addition [y,n,q,a,d,e,p,?]?
  +(1/1) Stage addition [y,n,q,a,d,e,p,?]? (1/1) Stage addition [y,n,q,a,d,e,p,?]?
  +Unknown command 'W' (use '?' for help)

which makes me suspect a race. Perhaps because the prompt is going to
stdout, but the "Unknown command" message goes to stderr? Maybe we
should keep stdout and stderr separate and check them independently.

-Peff
Eric Sunshine April 25, 2024, 2:15 a.m. UTC | #5
On Wed, Apr 24, 2024 at 9:44 PM Jeff King <peff@peff.net> wrote:
> On Sun, Apr 21, 2024 at 11:52:33PM +0200, Rubén Justo wrote:
> > +test_expect_success 'unknown command' '
> > +     test_when_finished "git reset --hard; rm -f command" &&
> > +     echo W >command &&
> > +     git add -N command &&
> > +     git diff command >expect &&
> > +     cat >>expect <<-EOF &&
> > +     (1/1) Stage addition [y,n,q,a,d,e,p,?]? Unknown command ${SQ}W${SQ} (use ${SQ}?${SQ} for help)
> > +     (1/1) Stage addition [y,n,q,a,d,e,p,?]?$SP
> > +     EOF
> > +     git add -p -- command <command >actual 2>&1 &&
> > +     test_cmp expect actual
> > +'
>
> I got a test failure on Windows CI from this. The test_cmp output looks
> like this:
>
>   -(1/1) Stage addition [y,n,q,a,d,e,p,?]? Unknown command 'W' (use '?' for help)
>   -(1/1) Stage addition [y,n,q,a,d,e,p,?]?
>   +(1/1) Stage addition [y,n,q,a,d,e,p,?]? (1/1) Stage addition [y,n,q,a,d,e,p,?]?
>   +Unknown command 'W' (use '?' for help)
>
> which makes me suspect a race. Perhaps because the prompt is going to
> stdout, but the "Unknown command" message goes to stderr? Maybe we
> should keep stdout and stderr separate and check them independently.

That's very reminiscent of [1]. Although, unlike [1], the output
presented to the user in this case is (I suppose) less likely to be
messed up; only the combined captured output is probably affected. So,
capturing stdout and stderr separately would indeed be a good idea.

[1]: https://lore.kernel.org/git/CAPig+cTGq-10ZTBts2LXRVdPMf2vNMX8HTuhg_+ZHSiLX-brOQ@mail.gmail.com/
Rubén Justo April 25, 2024, 3:04 a.m. UTC | #6
On Wed, Apr 24, 2024 at 09:44:32PM -0400, Jeff King wrote:
> On Sun, Apr 21, 2024 at 11:52:33PM +0200, Rubén Justo wrote:
> 
> > +test_expect_success 'unknown command' '
> > +	test_when_finished "git reset --hard; rm -f command" &&
> > +	echo W >command &&
> > +	git add -N command &&
> > +	git diff command >expect &&
> > +	cat >>expect <<-EOF &&
> > +	(1/1) Stage addition [y,n,q,a,d,e,p,?]? Unknown command ${SQ}W${SQ} (use ${SQ}?${SQ} for help)
> > +	(1/1) Stage addition [y,n,q,a,d,e,p,?]?$SP
> > +	EOF
> > +	git add -p -- command <command >actual 2>&1 &&
> > +	test_cmp expect actual
> > +'
> 
> I got a test failure on Windows CI from this.

Thank you for testing thoroughly.

> The test_cmp output looks
> like this:
> 
>   -(1/1) Stage addition [y,n,q,a,d,e,p,?]? Unknown command 'W' (use '?' for help)
>   -(1/1) Stage addition [y,n,q,a,d,e,p,?]?
>   +(1/1) Stage addition [y,n,q,a,d,e,p,?]? (1/1) Stage addition [y,n,q,a,d,e,p,?]?
>   +Unknown command 'W' (use '?' for help)
> 
> which makes me suspect a race. Perhaps because the prompt is going to
> stdout, but the "Unknown command" message goes to stderr?

I have to read the thread pointed by Eric, but my knee-jerk reaction has
been to think in something like:

diff --git a/add-patch.c b/add-patch.c
index 447e8166d2..0090543f89 100644
--- a/add-patch.c
+++ b/add-patch.c
@@ -292,6 +292,7 @@ static void err(struct add_p_state *s, const char *fmt, ...)
 {
        va_list args;

        va_start(args, fmt);
+       fflush(stdout);
        fputs(s->s.error_color, stderr);
        vfprintf(stderr, fmt, args);
Phillip Wood April 25, 2024, 8:53 a.m. UTC | #7
Hi Peff

On 25/04/2024 02:44, Jeff King wrote:
> On Sun, Apr 21, 2024 at 11:52:33PM +0200, Rubén Justo wrote:
> 
>> +test_expect_success 'unknown command' '
>> +	test_when_finished "git reset --hard; rm -f command" &&
>> +	echo W >command &&
>> +	git add -N command &&
>> +	git diff command >expect &&
>> +	cat >>expect <<-EOF &&
>> +	(1/1) Stage addition [y,n,q,a,d,e,p,?]? Unknown command ${SQ}W${SQ} (use ${SQ}?${SQ} for help)
>> +	(1/1) Stage addition [y,n,q,a,d,e,p,?]?$SP
>> +	EOF
>> +	git add -p -- command <command >actual 2>&1 &&
>> +	test_cmp expect actual
>> +'
> 
> I got a test failure on Windows CI from this. The test_cmp output looks
> like this:
> 
>    -(1/1) Stage addition [y,n,q,a,d,e,p,?]? Unknown command 'W' (use '?' for help)
>    -(1/1) Stage addition [y,n,q,a,d,e,p,?]?
>    +(1/1) Stage addition [y,n,q,a,d,e,p,?]? (1/1) Stage addition [y,n,q,a,d,e,p,?]?
>    +Unknown command 'W' (use '?' for help)
> 
> which makes me suspect a race. Perhaps because the prompt is going to
> stdout, but the "Unknown command" message goes to stderr? Maybe we
> should keep stdout and stderr separate and check them independently.

Can we change err() to print to stdout? I can't really see the advantage 
of separating "normal output" and "error messages" for an interactive 
program like this.

Best Wishes

Phillip
Junio C Hamano April 25, 2024, 8:23 p.m. UTC | #8
Eric Sunshine <sunshine@sunshineco.com> writes:

> That's very reminiscent of [1]. Although, unlike [1], the output
> presented to the user in this case is (I suppose) less likely to be
> messed up; only the combined captured output is probably affected. So,
> capturing stdout and stderr separately would indeed be a good idea.

Hmph, something along this line?

It loses to capture how the output should be intermixed, which is
essential to validate what the end-user should see.  As we can see
in the attached patch, we cannot express that "Unknown ..." should
come in between two "Stage addition?" questions, which is a downside.

Between adding fflush() before err() writes, and updating err() to
write to the standard output stream, I am in favor of the latter for
its simplicity (of the mental model of the resulting code, not of
the patch that is required to do so).

diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh
index ed7e414649..a8dfebd8d7 100755
--- a/t/t3701-add-interactive.sh
+++ b/t/t3701-add-interactive.sh
@@ -64,11 +64,12 @@ test_expect_success 'unknown command' '
 	git add -N command &&
 	git diff command >expect &&
 	cat >>expect <<-EOF &&
-	(1/1) Stage addition [y,n,q,a,d,e,p,?]? Unknown command ${SQ}W${SQ} (use ${SQ}?${SQ} for help)
-	(1/1) Stage addition [y,n,q,a,d,e,p,?]?$SP
+	(1/1) Stage addition [y,n,q,a,d,e,p,?]? (1/1) Stage addition [y,n,q,a,d,e,p,?]?$SP
 	EOF
-	git add -p -- command <command >actual 2>&1 &&
-	test_cmp expect actual
+	echo "Unknown command ${SQ}W${SQ} (use ${SQ}?${SQ} for help)" >expect.error &&
+	git add -p -- command <command >actual 2>actual.error &&
+	test_cmp expect actual &&
+	test_cmp expect.error actual.error
 '
 
 test_expect_success 'setup (initial)' '
Eric Sunshine April 25, 2024, 9 p.m. UTC | #9
On Thu, Apr 25, 2024 at 4:23 PM Junio C Hamano <gitster@pobox.com> wrote:
> Eric Sunshine <sunshine@sunshineco.com> writes:
> > That's very reminiscent of [1]. Although, unlike [1], the output
> > presented to the user in this case is (I suppose) less likely to be
> > messed up; only the combined captured output is probably affected. So,
> > capturing stdout and stderr separately would indeed be a good idea.
>
> Between adding fflush() before err() writes, and updating err() to
> write to the standard output stream, I am in favor of the latter for
> its simplicity (of the mental model of the resulting code, not of
> the patch that is required to do so).

Writing to a common stream (stdout, in this case) for this sort of
interactive session is indeed probably the way to go, as Phillip
suggested.

That was also the adopted solution to the cited similar example[1];
git-worktree was changed to send all its chatty output to stderr[2],
which was appropriate for that (non-interactive) case.

[1]: https://lore.kernel.org/git/CAPig+cTGq-10ZTBts2LXRVdPMf2vNMX8HTuhg_+ZHSiLX-brOQ@mail.gmail.com/
[2]: https://lore.kernel.org/git/20211203034420.47447-2-sunshine@sunshineco.com/
Junio C Hamano April 25, 2024, 9:05 p.m. UTC | #10
Junio C Hamano <gitster@pobox.com> writes:

> Eric Sunshine <sunshine@sunshineco.com> writes:
>
>> That's very reminiscent of [1]. Although, unlike [1], the output
>> presented to the user in this case is (I suppose) less likely to be
>> messed up; only the combined captured output is probably affected. So,
>> capturing stdout and stderr separately would indeed be a good idea.
>
> Hmph, something along this line?
>
> It loses to capture how the output should be intermixed, which is
> essential to validate what the end-user should see.  As we can see
> in the attached patch, we cannot express that "Unknown ..." should
> come in between two "Stage addition?" questions, which is a downside.
>
> Between adding fflush() before err() writes, and updating err() to
> write to the standard output stream, I am in favor of the latter for
> its simplicity (of the mental model of the resulting code, not of
> the patch that is required to do so).

The latter, which I claimed to prefer, would look like this.

The idea is to perform the interactive session over the standard
output (and the standard input).  For that, we teach err() to use
the standard output and have a few fprintf() to also call err().

A few tests expect certain messages to appear on the standard error
stream, which needed adjusting.

I know the previous one "fixes" the CI job at Windows, but I haven't
tried this yet.

diff --git c/add-patch.c w/add-patch.c
index 7be142d448..c28ad380ed 100644
--- c/add-patch.c
+++ w/add-patch.c
@@ -293,10 +293,10 @@ static void err(struct add_p_state *s, const char *fmt, ...)
 	va_list args;
 
 	va_start(args, fmt);
-	fputs(s->s.error_color, stderr);
-	vfprintf(stderr, fmt, args);
-	fputs(s->s.reset_color, stderr);
-	fputc('\n', stderr);
+	fputs(s->s.error_color, stdout);
+	vfprintf(stdout, fmt, args);
+	fputs(s->s.reset_color, stdout);
+	fputc('\n', stdout);
 	va_end(args);
 }
 
@@ -1326,7 +1326,7 @@ static int apply_for_checkout(struct add_p_state *s, struct strbuf *diff,
 		err(s, _("Nothing was applied.\n"));
 	} else
 		/* As a last resort, show the diff to the user */
-		fwrite(diff->buf, diff->len, 1, stderr);
+		fwrite(diff->buf, diff->len, 1, stdout);
 
 	return 0;
 }
@@ -1780,9 +1780,9 @@ int run_add_p(struct repository *r, enum add_p_mode mode,
 			break;
 
 	if (s.file_diff_nr == 0)
-		fprintf(stderr, _("No changes.\n"));
+		err(&s, _("No changes."));
 	else if (binary_count == s.file_diff_nr)
-		fprintf(stderr, _("Only binary files changed.\n"));
+		err(&s, _("Only binary files changed."));
 
 	add_p_state_clear(&s);
 	return 0;
diff --git c/t/t3701-add-interactive.sh w/t/t3701-add-interactive.sh
index 482d5c117e..a315ec99a3 100755
--- c/t/t3701-add-interactive.sh
+++ w/t/t3701-add-interactive.sh
@@ -43,17 +43,17 @@ force_color () {
 }
 
 test_expect_success 'warn about add.interactive.useBuiltin' '
-	cat >expect <<-\EOF &&
+	cat >expect.error <<-\EOF &&
 	warning: the add.interactive.useBuiltin setting has been removed!
 	See its entry in '\''git help config'\'' for details.
-	No changes.
 	EOF
 
 	for v in = =true =false
 	do
-		git -c "add.interactive.useBuiltin$v" add -p >out 2>actual &&
-		test_must_be_empty out &&
-		test_cmp expect actual || return 1
+		git -c "add.interactive.useBuiltin$v" add -p >actual 2>error &&
+		echo "No changes." >expect &&
+		test_cmp expect actual &&
+		test_cmp expect.error error || return 1
 	done
 '
 
@@ -348,13 +348,13 @@ test_expect_success 'different prompts for mode change/deleted' '
 
 test_expect_success 'correct message when there is nothing to do' '
 	git reset --hard &&
-	git add -p 2>err &&
-	test_grep "No changes" err &&
+	git add -p >out &&
+	test_grep "No changes" out &&
 	printf "\\0123" >binary &&
 	git add binary &&
 	printf "\\0abc" >binary &&
-	git add -p 2>err &&
-	test_grep "Only binary files changed" err
+	git add -p >out &&
+	test_grep "Only binary files changed" out
 '
 
 test_expect_success 'setup again' '
Junio C Hamano April 25, 2024, 9:13 p.m. UTC | #11
Eric Sunshine <sunshine@sunshineco.com> writes:

> That was also the adopted solution to the cited similar example[1];
> git-worktree was changed to send all its chatty output to stderr[2],
> which was appropriate for that (non-interactive) case.
>
> [1]: https://lore.kernel.org/git/CAPig+cTGq-10ZTBts2LXRVdPMf2vNMX8HTuhg_+ZHSiLX-brOQ@mail.gmail.com/
> [2]: https://lore.kernel.org/git/20211203034420.47447-2-sunshine@sunshineco.com/

Hmph, what I just sent out tries to send everything to the standard
output stream.  For an interactive sesion, whether the interaction
is done over stdout or stderr does not really make much difference,
but I have a slight preference to use the stderr (because I view
prompts etc. in the same "chatty" class as the progress bar), but I
do not care enough to go back and redo the patch ;-)
Rubén Justo April 25, 2024, 10:09 p.m. UTC | #12
On Thu, Apr 25, 2024 at 02:05:33PM -0700, Junio C Hamano wrote:

I am assuming that this change will precede my series.

I leave some nits below.

> diff --git c/add-patch.c w/add-patch.c
> index 7be142d448..c28ad380ed 100644
> --- c/add-patch.c
> +++ w/add-patch.c
> @@ -293,10 +293,10 @@ static void err(struct add_p_state *s, const char *fmt, ...)
>  	va_list args;
>  
>  	va_start(args, fmt);
> -	fputs(s->s.error_color, stderr);
> -	vfprintf(stderr, fmt, args);
> -	fputs(s->s.reset_color, stderr);
> -	fputc('\n', stderr);
> +	fputs(s->s.error_color, stdout);
> +	vfprintf(stdout, fmt, args);
> +	fputs(s->s.reset_color, stdout);
> +	fputc('\n', stdout);

If we're going to use stdout, perhaps we can be less explicit?

diff --git a/add-patch.c b/add-patch.c
index 447e8166d2..a204224dae 100644
--- a/add-patch.c
+++ b/add-patch.c
@@ -293,10 +293,10 @@ static void err(struct add_p_state *s, const char *fmt, ...)
        va_list args;

        va_start(args, fmt);
-       fputs(s->s.error_color, stderr);
-       vfprintf(stderr, fmt, args);
-       fputs(s->s.reset_color, stderr);
-       fputc('\n', stderr);
+       puts(s->s.error_color);
+       vprintf(fmt, args);
+       puts(s->s.reset_color);
+       putchar('\n');
        va_end(args);
 }

> @@ -1780,9 +1780,9 @@ int run_add_p(struct repository *r, enum add_p_mode mode,
>  			break;
>  
>  	if (s.file_diff_nr == 0)
> -		fprintf(stderr, _("No changes.\n"));
> +		err(&s, _("No changes."));

Nice.

>  	else if (binary_count == s.file_diff_nr)
> -		fprintf(stderr, _("Only binary files changed.\n"));
> +		err(&s, _("Only binary files changed."));

Nice.

> diff --git c/t/t3701-add-interactive.sh w/t/t3701-add-interactive.sh
> index 482d5c117e..a315ec99a3 100755
> --- c/t/t3701-add-interactive.sh
> +++ w/t/t3701-add-interactive.sh
> @@ -43,17 +43,17 @@ force_color () {
>  }
>  
>  test_expect_success 'warn about add.interactive.useBuiltin' '
> -	cat >expect <<-\EOF &&
> +	cat >expect.error <<-\EOF &&

This expect.error is what we are going to test with the output on
stderr ... 

>  	warning: the add.interactive.useBuiltin setting has been removed!
>  	See its entry in '\''git help config'\'' for details.
> -	No changes.

however, this line no longer goes to stderr.  OK.

>  	EOF
>  
>  	for v in = =true =false
>  	do
> -		git -c "add.interactive.useBuiltin$v" add -p >out 2>actual &&
> -		test_must_be_empty out &&
> -		test_cmp expect actual || return 1
> +		git -c "add.interactive.useBuiltin$v" add -p >actual 2>error &&
> +		echo "No changes." >expect &&

Why not prepare this file above, out of the loop?

> +		test_cmp expect actual &&
> +		test_cmp expect.error error || return 1
>  	done
>  '
>  
> @@ -348,13 +348,13 @@ test_expect_success 'different prompts for mode change/deleted' '
>  
>  test_expect_success 'correct message when there is nothing to do' '
>  	git reset --hard &&
> -	git add -p 2>err &&
> -	test_grep "No changes" err &&
> +	git add -p >out &&
> +	test_grep "No changes" out &&
>  	printf "\\0123" >binary &&
>  	git add binary &&
>  	printf "\\0abc" >binary &&
> -	git add -p 2>err &&
> -	test_grep "Only binary files changed" err
> +	git add -p >out &&
> +	test_grep "Only binary files changed" out
>  '
>  
>  test_expect_success 'setup again' '
Junio C Hamano April 25, 2024, 10:16 p.m. UTC | #13
Rubén Justo <rjusto@gmail.com> writes:

> On Thu, Apr 25, 2024 at 02:05:33PM -0700, Junio C Hamano wrote:
>
> I am assuming that this change will precede my series.

No, this was merely "if we were to update the series I queued to my
tree, the squashable fix may look like this", which you can use to
update _your_ series if you want to.
Rubén Justo April 25, 2024, 11:46 p.m. UTC | #14
> Rubén Justo <rjusto@gmail.com> writes:
> 
> > On Thu, Apr 25, 2024 at 02:05:33PM -0700, Junio C Hamano wrote:
> >
> > I am assuming that this change will precede my series.
> 
> No, this was merely "if we were to update the series I queued to my
> tree, the squashable fix may look like this", which you can use to
> update _your_ series if you want to.
> 

I was not sure what was the expectation.  In fact, I'm still not quite
sure.

I am not sure about the change either.

The current options are:

	a.- make the test check for stderr and stdout, separatedly

	b.- fflush(stdout) in err

	c.- make err print to stdout

I suspect that similar tests for other commands would produce similar
errors, so (a) seems like an easy fix but feels like kicking the can
forward.

I'm not sure of the implications of (c).  Perhaps moving current
messages to stdout breaks some workflow out there?  The other thread
about disabling all hints has made me think.

The (b) option seems to me the less disturbing change, but it has not
attracted attention.

There is even a (d) that is to go back to test just the new error
message, not the whole output.

I will give it some thought.
Junio C Hamano April 26, 2024, 5:39 a.m. UTC | #15
Rubén Justo <rjusto@gmail.com> writes:

> The current options are:
>
> 	a.- make the test check for stderr and stdout, separatedly

There is a downside that we do not check what the users would see,
as I mentioned in an earlier message.

> 	b.- fflush(stdout) in err
>
> 	c.- make err print to stdout

Yup.  The last one is what I showed in the thread.

> I suspect that similar tests for other commands would produce similar
> errors, so (a) seems like an easy fix but feels like kicking the can
> forward.
>
> I'm not sure of the implications of (c).  Perhaps moving current
> messages to stdout breaks some workflow out there?  The other thread
> about disabling all hints has made me think.

That depends on "other" commands.  As Phillip said, "git add -p" and
the like that interacts with the end user via terminal, using a
single stream, whether it is the standard error stream or the
standard output stream, should not affect any end-user experience.
It is very unlikely people capture only one stream and feed it to a
program, and with i18n, these messages are not even designed for
machine consumption in the first place.

> The (b) option seems to me the less disturbing change

Sticking close to the status quo would certainly _feel_ the safest
at least in the shorter term.

But given that there is not really a reasonable justification why
some output goes to the standard output stream while others go to
the standard error stream in this program (note that I am
specifically talking about the terminal interactive session with
"add -p"), the approach will force us to worry about similar gotchas
and we need to decide which stream the message needs to go every
time we add a new one.

> but it has not attracted attention.

I think the same reasoning from the old thread that made us avoid
the "flush() and keep writing to two streams" in the worktree code
would apply here, too.
Junio C Hamano April 26, 2024, 4:26 p.m. UTC | #16
Rubén Justo <rjusto@gmail.com> writes:

> I will give it some thought.

In the meantime, I'll revert the merge of the topic into 'next',
squash in the "send everything to the standard output stream" fix,
and queue the "tentative" result to 'seen', to make the CI happy.

I am OK enough with the "tentative" one, but if you and others feel
differently, I am perfectly happy to see it replaced with [PATCH v5].

Thanks.
Jeff King April 26, 2024, 8:21 p.m. UTC | #17
On Wed, Apr 24, 2024 at 10:15:25PM -0400, Eric Sunshine wrote:

> > I got a test failure on Windows CI from this. The test_cmp output looks
> > like this:
> >
> >   -(1/1) Stage addition [y,n,q,a,d,e,p,?]? Unknown command 'W' (use '?' for help)
> >   -(1/1) Stage addition [y,n,q,a,d,e,p,?]?
> >   +(1/1) Stage addition [y,n,q,a,d,e,p,?]? (1/1) Stage addition [y,n,q,a,d,e,p,?]?
> >   +Unknown command 'W' (use '?' for help)
> >
> > which makes me suspect a race. Perhaps because the prompt is going to
> > stdout, but the "Unknown command" message goes to stderr? Maybe we
> > should keep stdout and stderr separate and check them independently.
> 
> That's very reminiscent of [1]. Although, unlike [1], the output
> presented to the user in this case is (I suppose) less likely to be
> messed up; only the combined captured output is probably affected. So,
> capturing stdout and stderr separately would indeed be a good idea.

Ah, yeah, that is almost certainly the same issue. I could not reproduce
it on my local system with --stress, so I think it may be unique to
Windows. What it looks like is that stderr is buffered when output to a
file (whereas on most systems it would still be line buffered at most).
But I didn't actually run it through a debugger to see.

So I _suspect_ that yet another possible solution here is to do an
explicit:

  setvbuf(stderr, NULL, _IOLBF, BUFSIZ);

in common-main. Though grepping for setvbuf() in our code base, it looks
like compat/winansi.c already does:

          if (fd == 2)
                setvbuf(stderr, NULL, _IONBF, BUFSIZ);

which would imply that output should happen even more immediately than
line-buffering. So maybe there is some other layer at work. There are
probably dragons, and definitely pursuing that line would involve some
experimentation.

The "if you want them in order, make sure they are on the same stream"
approach suggested elsewhere is by comparison much simpler. :)

-Peff
Jeff King April 26, 2024, 8:23 p.m. UTC | #18
On Thu, Apr 25, 2024 at 05:04:45AM +0200, Rubén Justo wrote:

> > The test_cmp output looks
> > like this:
> > 
> >   -(1/1) Stage addition [y,n,q,a,d,e,p,?]? Unknown command 'W' (use '?' for help)
> >   -(1/1) Stage addition [y,n,q,a,d,e,p,?]?
> >   +(1/1) Stage addition [y,n,q,a,d,e,p,?]? (1/1) Stage addition [y,n,q,a,d,e,p,?]?
> >   +Unknown command 'W' (use '?' for help)
> > 
> > which makes me suspect a race. Perhaps because the prompt is going to
> > stdout, but the "Unknown command" message goes to stderr?
> 
> I have to read the thread pointed by Eric, but my knee-jerk reaction has
> been to think in something like:
> 
> diff --git a/add-patch.c b/add-patch.c
> index 447e8166d2..0090543f89 100644
> --- a/add-patch.c
> +++ b/add-patch.c
> @@ -292,6 +292,7 @@ static void err(struct add_p_state *s, const char *fmt, ...)
>  {
>         va_list args;
> 
>         va_start(args, fmt);
> +       fflush(stdout);
>         fputs(s->s.error_color, stderr);
>         vfprintf(stderr, fmt, args);

I think the "just send it all to stdout" approach makes the most sense
here, but in case we don't do that: I don't think this will fix it. In
the output above it is the "Unknown command" output which is delayed,
which is sent to stderr via err(). So flushing stdout again won't help.
Flushing stderr after the vfprintf _might_ help (though I'm confused why
stderr would be fully buffered in the first place).

-Peff
Rubén Justo April 26, 2024, 8:41 p.m. UTC | #19
On Fri, Apr 26, 2024 at 04:23:33PM -0400, Jeff King wrote:
> On Thu, Apr 25, 2024 at 05:04:45AM +0200, Rubén Justo wrote:
> 
> > > The test_cmp output looks
> > > like this:
> > > 
> > >   -(1/1) Stage addition [y,n,q,a,d,e,p,?]? Unknown command 'W' (use '?' for help)
> > >   -(1/1) Stage addition [y,n,q,a,d,e,p,?]?
> > >   +(1/1) Stage addition [y,n,q,a,d,e,p,?]? (1/1) Stage addition [y,n,q,a,d,e,p,?]?
> > >   +Unknown command 'W' (use '?' for help)
> > > 
> > > which makes me suspect a race. Perhaps because the prompt is going to
> > > stdout, but the "Unknown command" message goes to stderr?
> > 
> > I have to read the thread pointed by Eric, but my knee-jerk reaction has
> > been to think in something like:
> > 
> > diff --git a/add-patch.c b/add-patch.c
> > index 447e8166d2..0090543f89 100644
> > --- a/add-patch.c
> > +++ b/add-patch.c
> > @@ -292,6 +292,7 @@ static void err(struct add_p_state *s, const char *fmt, ...)
> >  {
> >         va_list args;
> > 
> >         va_start(args, fmt);
> > +       fflush(stdout);
> >         fputs(s->s.error_color, stderr);
> >         vfprintf(stderr, fmt, args);
> 
> I think the "just send it all to stdout" approach makes the most sense
> here, but in case we don't do that: I don't think this will fix it. In
> the output above it is the "Unknown command" output which is delayed,
> which is sent to stderr via err().

Very true.

I'm happy with what Junio has just queued.  I do not plan to send a new
iteration, unless a new test break appears :-)

Thanks, all.
Phillip Wood April 29, 2024, 9:48 a.m. UTC | #20
Hi Junio

On 24/04/2024 16:35, Junio C Hamano wrote:
> phillip.wood123@gmail.com writes:
> 
>> On 22/04/2024 16:50, Junio C Hamano wrote:
>>> Rubén Justo <rjusto@gmail.com> writes:
>>>
>>>> 1:  0317594bce ! 1:  b418b03f15 add-patch: response to unknown command
>>>>       @@ t/t3701-add-interactive.sh: test_expect_success 'warn about add.interactive.useB
>>>>        +	test_when_finished "git reset --hard; rm -f command" &&
>>>>        +	echo W >command &&
>>>>        +	git add -N command &&
>>>>       -+	cat >expect <<-EOF &&
>>>>       -+	diff --git a/command b/command
>>>>       -+	new file mode 100644
>>>>       -+	index 0000000..a42d8ff
>>>>       -+	--- /dev/null
>>>>       -+	+++ b/command
>>>>       -+	@@ -0,0 +1 @@
>>>>       -+	+W
>>>>       ++	git diff command >expect &&
>>>>       ++	cat >>expect <<-EOF &&
>>>>        +	(1/1) Stage addition [y,n,q,a,d,e,p,?]? Unknown command ${SQ}W${SQ} (use ${SQ}?${SQ} for help)
>>>>        +	(1/1) Stage addition [y,n,q,a,d,e,p,?]?$SP
>>>>        +	EOF
>>> Interesting.
>>> My first reaction was "how is this different from checking just the
>>> last line of the actual output?  The early part of expect and actual
>>> both come from an internal invocation of 'git diff', and they must
>>> match by definition".
>>> But that may really be the point of this test.
>>
>> Yes - we want to make sure that we are not printing the help and the
>> only way to do that is to check the whole output
> 
> I was not questioning that part of the patch.  "My first reaction"
> was solely about use of "git diff" to replace the golden copy of
> expected result in the test itself, only to allow for use of
> different hash functions.  As you (or somebody else?) mentioned in
> an earlier review, diff_cmp is there for exactly that purpose.

Oh sorry I'd misunderstood

>>> That is, we may later decide to tweak the way "git diff" hunks are
>>> presented, and we expect that the way "git add -p" presents the
>>> hunks would change together with it automatically.
> 
> This argument cuts both ways, by the way.
> 
> Insisting that the output match the explicit expectation (not what
> "git diff" of the day produces) has a few advantages.  It makes the
> test more explicit and easy to see what output we are expecting, and
> more importantly, it forces us to update the test when we decide to
> tweak the output from the command being tested (i.e. hunk selection
> UI) and/or the output from "git diff" command.

There is also a practical argument against using "git diff" to generate 
the expected output as it only works if the diff contains a single hunk. 
If the diff contains more than one hunk "add -p" displays them separately.

Best Wishes

Phillip
Junio C Hamano April 29, 2024, 4:09 p.m. UTC | #21
Phillip Wood <phillip.wood123@gmail.com> writes:

> There is also a practical argument against using "git diff" to
> generate the expected output as it only works if the diff contains a
> single hunk. If the diff contains more than one hunk "add -p" displays
> them separately.

True, but spliting the output from "git diff" at @@ hunk boundary
lines would allow you to hardcode the assumption that "add -p" shows
each hunk exactly as "git diff" would.  So if we really wanted to
have the convenience that the tests' expectation automatically
follow what "git diff" of the day happens to produce, it is still a
viable approach to use "git diff" output with some post-processing.

Thanks.
diff mbox series

Patch

diff --git a/add-patch.c b/add-patch.c
index a06dd18985..7be142d448 100644
--- a/add-patch.c
+++ b/add-patch.c
@@ -1667,7 +1667,7 @@  static int patch_update_file(struct add_p_state *s,
 			}
 		} else if (s->answer.buf[0] == 'p') {
 			rendered_hunk_index = -1;
-		} else {
+		} else if (s->answer.buf[0] == '?') {
 			const char *p = _(help_patch_remainder), *eol = p;
 
 			color_fprintf(stdout, s->s.help_color, "%s",
@@ -1691,6 +1691,9 @@  static int patch_update_file(struct add_p_state *s,
 				color_fprintf_ln(stdout, s->s.help_color,
 						 "%.*s", (int)(eol - p), p);
 			}
+		} else {
+			err(s, _("Unknown command '%s' (use '?' for help)"),
+			    s->answer.buf);
 		}
 	}
 
diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh
index bc55255b0a..482d5c117e 100755
--- a/t/t3701-add-interactive.sh
+++ b/t/t3701-add-interactive.sh
@@ -7,6 +7,8 @@  export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 . ./test-lib.sh
 . "$TEST_DIRECTORY"/lib-terminal.sh
 
+SP=" "
+
 diff_cmp () {
 	for x
 	do
@@ -55,6 +57,19 @@  test_expect_success 'warn about add.interactive.useBuiltin' '
 	done
 '
 
+test_expect_success 'unknown command' '
+	test_when_finished "git reset --hard; rm -f command" &&
+	echo W >command &&
+	git add -N command &&
+	git diff command >expect &&
+	cat >>expect <<-EOF &&
+	(1/1) Stage addition [y,n,q,a,d,e,p,?]? Unknown command ${SQ}W${SQ} (use ${SQ}?${SQ} for help)
+	(1/1) Stage addition [y,n,q,a,d,e,p,?]?$SP
+	EOF
+	git add -p -- command <command >actual 2>&1 &&
+	test_cmp expect actual
+'
+
 test_expect_success 'setup (initial)' '
 	echo content >file &&
 	git add file &&
@@ -231,7 +246,6 @@  test_expect_success 'setup file' '
 '
 
 test_expect_success 'setup patch' '
-	SP=" " &&
 	NULL="" &&
 	cat >patch <<-EOF
 	@@ -1,4 +1,4 @@