git@vger.kernel.org mailing list mirror (one of many)
 help / color / mirror / code / Atom feed
* [PATCH 0/2] handling REF_STATUS_EXPECTING_REPORT over http
@ 2021-10-18 19:34 Jeff King
  2021-10-18 19:43 ` [PATCH 1/2] send-pack: complain about "expecting report" with --helper-status Jeff King
                   ` (2 more replies)
  0 siblings, 3 replies; 5+ messages in thread
From: Jeff King @ 2021-10-18 19:34 UTC (permalink / raw)
  To: git

I recently saw a case where GitHub's server implementation failed to
correctly report ref statuses. In this case it was because it hit an
internal timeout, but the root cause doesn't really matter; the server
is doing the wrong thing.

But what's interesting on the Git client side is that we produce quite a
confusing message for http. For ssh, we correctly say something like (in
this example, I'm pushing an absurd number of tags that causes the
timeout):

   To ssh://github.com/some/repo
    ! [remote failure]  tag-1 -> tag-1 (remote failed to report status)
   [...etc...]
    ! [remote failure]  tag-999 -> tag-999 (remote failed to report status)
   error: failed to push some refs to 'ssh://github.com/some/repo'

but for http, I get just:

  Everything up-to-date

Which is misleading to say the least. The issue is communication between
send-pack and remote-curl on the client side.

The first patch does the minimal fix. The second one has some cosmetic
improvements. They arguably could be squashed together, but I think
keeping them split shows exactly what the second patch is actually
accomplishing (especially with the minor change to the expected output
in the test).

This bug has been around since smart http was added in 2009. I just
prepared it on master, but I expect it could also go to maint.

  [1/2]: send-pack: complain about "expecting report" with --helper-status
  [2/2]: transport-helper: recognize "expecting report" error from send-pack

 builtin/send-pack.c            |  4 ++++
 t/lib-httpd.sh                 |  1 +
 t/lib-httpd/apache.conf        |  4 ++++
 t/lib-httpd/error-no-report.sh |  6 ++++++
 t/t5541-http-push-smart.sh     | 16 ++++++++++++++++
 transport-helper.c             |  4 ++++
 6 files changed, 35 insertions(+)
 create mode 100644 t/lib-httpd/error-no-report.sh

-Peff

^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH 1/2] send-pack: complain about "expecting report" with --helper-status
  2021-10-18 19:34 [PATCH 0/2] handling REF_STATUS_EXPECTING_REPORT over http Jeff King
@ 2021-10-18 19:43 ` Jeff King
  2021-10-18 20:26   ` Junio C Hamano
  2021-10-18 19:45 ` [PATCH 2/2] transport-helper: recognize "expecting report" error from send-pack Jeff King
  2021-10-19 23:58 ` [PATCH 0/2] handling REF_STATUS_EXPECTING_REPORT over http brian m. carlson
  2 siblings, 1 reply; 5+ messages in thread
From: Jeff King @ 2021-10-18 19:43 UTC (permalink / raw)
  To: git

When pushing to a server which erroneously omits the final ref-status
report, the client side should complain about the refs for which we
didn't receive the status (because we can't just assume they were
updated). This works over most transports like ssh, but for http we'll
print a very misleading "Everything up-to-date".

It works for ssh because send-pack internally sets the status of each
ref to REF_STATUS_EXPECTING_REPORT, and then if the server doesn't tell
us about a particular ref, it will stay at that value. When we print the
final status table, we'll see that we're still on EXPECTING_REPORT and
complain then.

But for http, we go through remote-curl, which invokes send-pack with
"--stateless-rpc --helper-status". The latter option causes send-pack to
return a machine-readable list of ref statuses to the remote helper. But
ever since its inception in de1a2fdd38 (Smart push over HTTP: client
side, 2009-10-30), the send-pack code has simply omitted mention of any
ref which ended up in EXPECTING_REPORT.

In the remote helper, we then take the absence of any status report
from send-pack to mean that the ref was not even something we tried to
send, and thus it prints "Everything up-to-date". Fortunately it does
detect the eventual non-zero exit from send-pack, and propagates that in
its own non-zero exit code. So at least a careful script invoking "git
push" would notice the failure.  But sending the misleading message on
stderr is certainly confusing for humans (not to mention the
machine-readable "push --porcelain" output, though again, any careful
script should be checking the exit code from push, too).

Nobody seems to have noticed because the server in this instance has to
be misbehaving: it has promised to support the ref-status capability
(otherwise the client will not set EXPECTING_REPORT at all), but didn't
send us any. If the connection were simply cut, then send-pack would
complain about getting EOF while trying to read the status. But if the
server actually sends a flush packet (i.e., saying "now you have all of
the ref statuses" without actually sending any), then the client ends up
in this confused situation.

The fix is simple: we should return an error message from "send-pack
--helper-status", just like we would for any other error per-ref error
condition (in the test I included, the server simply omits all ref
status responses, but a more insidious version of this would skip only
some of them).

Signed-off-by: Jeff King <peff@peff.net>
---
 builtin/send-pack.c            |  4 ++++
 t/lib-httpd.sh                 |  1 +
 t/lib-httpd/apache.conf        |  4 ++++
 t/lib-httpd/error-no-report.sh |  6 ++++++
 t/t5541-http-push-smart.sh     | 16 ++++++++++++++++
 5 files changed, 31 insertions(+)
 create mode 100644 t/lib-httpd/error-no-report.sh

diff --git a/builtin/send-pack.c b/builtin/send-pack.c
index 8932142312..69c432ef1a 100644
--- a/builtin/send-pack.c
+++ b/builtin/send-pack.c
@@ -87,6 +87,10 @@ static void print_helper_status(struct ref *ref)
 			break;
 
 		case REF_STATUS_EXPECTING_REPORT:
+			res = "error";
+			msg = "expecting report";
+			break;
+
 		default:
 			continue;
 		}
diff --git a/t/lib-httpd.sh b/t/lib-httpd.sh
index d2edfa4c50..782891908d 100644
--- a/t/lib-httpd.sh
+++ b/t/lib-httpd.sh
@@ -131,6 +131,7 @@ prepare_httpd() {
 	cp "$TEST_PATH"/passwd "$HTTPD_ROOT_PATH"
 	install_script incomplete-length-upload-pack-v2-http.sh
 	install_script incomplete-body-upload-pack-v2-http.sh
+	install_script error-no-report.sh
 	install_script broken-smart-http.sh
 	install_script error-smart-http.sh
 	install_script error.sh
diff --git a/t/lib-httpd/apache.conf b/t/lib-httpd/apache.conf
index 180a41fe96..497b9b9d92 100644
--- a/t/lib-httpd/apache.conf
+++ b/t/lib-httpd/apache.conf
@@ -122,6 +122,7 @@ Alias /auth/dumb/ www/auth/dumb/
 </LocationMatch>
 ScriptAlias /smart/incomplete_length/git-upload-pack incomplete-length-upload-pack-v2-http.sh/
 ScriptAlias /smart/incomplete_body/git-upload-pack incomplete-body-upload-pack-v2-http.sh/
+ScriptAlias /smart/no_report/git-receive-pack error-no-report.sh/
 ScriptAliasMatch /error_git_upload_pack/(.*)/git-upload-pack error.sh/
 ScriptAliasMatch /smart_*[^/]*/(.*) ${GIT_EXEC_PATH}/git-http-backend/$1
 ScriptAlias /broken_smart/ broken-smart-http.sh/
@@ -137,6 +138,9 @@ ScriptAliasMatch /one_time_perl/(.*) apply-one-time-perl.sh/$1
 <Files incomplete-body-upload-pack-v2-http.sh>
 	Options ExecCGI
 </Files>
+<Files error-no-report.sh>
+	Options ExecCGI
+</Files>
 <Files broken-smart-http.sh>
 	Options ExecCGI
 </Files>
diff --git a/t/lib-httpd/error-no-report.sh b/t/lib-httpd/error-no-report.sh
new file mode 100644
index 0000000000..39ff75bbc4
--- /dev/null
+++ b/t/lib-httpd/error-no-report.sh
@@ -0,0 +1,6 @@
+echo "Content-Type: application/x-git-receive-pack-result"
+echo
+printf '0013\001000eunpack ok\n'
+printf '0015\002skipping report\n'
+printf '0009\0010000'
+printf '0000'
diff --git a/t/t5541-http-push-smart.sh b/t/t5541-http-push-smart.sh
index c024fa2818..9c61dccc24 100755
--- a/t/t5541-http-push-smart.sh
+++ b/t/t5541-http-push-smart.sh
@@ -509,4 +509,20 @@ test_expect_success 'colorize errors/hints' '
 	test_i18ngrep ! "^hint: " decoded
 '
 
+test_expect_success 'report error server does not provide ref status' '
+	git init "$HTTPD_DOCUMENT_ROOT_PATH/no_report" &&
+	git -C "$HTTPD_DOCUMENT_ROOT_PATH/no_report" config http.receivepack true &&
+	test_must_fail git push --porcelain \
+		$HTTPD_URL_USER_PASS/smart/no_report \
+		HEAD:refs/tags/will-fail >actual &&
+	test_must_fail git -C "$HTTPD_DOCUMENT_ROOT_PATH/no_report" \
+		rev-parse --verify refs/tags/will-fail &&
+	cat >expect <<-EOF &&
+	To $HTTPD_URL/smart/no_report
+	!	HEAD:refs/tags/will-fail	[remote rejected] (expecting report)
+	Done
+	EOF
+	test_cmp expect actual
+'
+
 test_done
-- 
2.33.1.1223.g80c1dbe6e5


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH 2/2] transport-helper: recognize "expecting report" error from send-pack
  2021-10-18 19:34 [PATCH 0/2] handling REF_STATUS_EXPECTING_REPORT over http Jeff King
  2021-10-18 19:43 ` [PATCH 1/2] send-pack: complain about "expecting report" with --helper-status Jeff King
@ 2021-10-18 19:45 ` Jeff King
  2021-10-19 23:58 ` [PATCH 0/2] handling REF_STATUS_EXPECTING_REPORT over http brian m. carlson
  2 siblings, 0 replies; 5+ messages in thread
From: Jeff King @ 2021-10-18 19:45 UTC (permalink / raw)
  To: git

When a transport helper pushes via send-pack, it passes --helper-status
to get a machine-readable status back for each ref. The previous commit
taught the send-pack code to hand back "error expecting report" if the
server did not send us the proper ref-status. And that's enough to cause
us to recognize that an error occurred for the ref and print something
sensible in our final status table.

But we do interpret these messages on the remote-helper side to turn
them back into REF_STATUS_* enum values.  Recognizing this token to turn
it back into REF_STATUS_EXPECTING_REPORT has two advantages:

  1. We now print exactly the same message in the human-readable (and
     machine-readable --porcelain) output for this situation whether the
     transport went through a helper (e.g., http) or not (e.g., ssh).

  2. If any code in the helper really cares about distinguishing
     EXPECT_REPORT from more generic error conditions, it could now do
     so. I didn't find any, so this is mostly future-proofing.

So this is mostly cosmetic for now, but it seems like the
least-surprising thing for the transport-helper code to be doing.

Signed-off-by: Jeff King <peff@peff.net>
---
Yes, the "else if" is not cuddled below. It matches the rest of the
existing if/else chain.

 t/t5541-http-push-smart.sh | 2 +-
 transport-helper.c         | 4 ++++
 2 files changed, 5 insertions(+), 1 deletion(-)

diff --git a/t/t5541-http-push-smart.sh b/t/t5541-http-push-smart.sh
index 9c61dccc24..8ca50f8b18 100755
--- a/t/t5541-http-push-smart.sh
+++ b/t/t5541-http-push-smart.sh
@@ -519,7 +519,7 @@ test_expect_success 'report error server does not provide ref status' '
 		rev-parse --verify refs/tags/will-fail &&
 	cat >expect <<-EOF &&
 	To $HTTPD_URL/smart/no_report
-	!	HEAD:refs/tags/will-fail	[remote rejected] (expecting report)
+	!	HEAD:refs/tags/will-fail	[remote failure] (remote failed to report status)
 	Done
 	EOF
 	test_cmp expect actual
diff --git a/transport-helper.c b/transport-helper.c
index e8dbdd1153..a0297b0986 100644
--- a/transport-helper.c
+++ b/transport-helper.c
@@ -845,6 +845,10 @@ static int push_update_ref_status(struct strbuf *buf,
 			forced = 1;
 			FREE_AND_NULL(msg);
 		}
+		else if (!strcmp(msg, "expecting report")) {
+			status = REF_STATUS_EXPECTING_REPORT;
+			FREE_AND_NULL(msg);
+		}
 	}
 
 	if (state->hint)
-- 
2.33.1.1223.g80c1dbe6e5

^ permalink raw reply related	[flat|nested] 5+ messages in thread

* Re: [PATCH 1/2] send-pack: complain about "expecting report" with --helper-status
  2021-10-18 19:43 ` [PATCH 1/2] send-pack: complain about "expecting report" with --helper-status Jeff King
@ 2021-10-18 20:26   ` Junio C Hamano
  0 siblings, 0 replies; 5+ messages in thread
From: Junio C Hamano @ 2021-10-18 20:26 UTC (permalink / raw)
  To: Jeff King; +Cc: git

Jeff King <peff@peff.net> writes:

> diff --git a/builtin/send-pack.c b/builtin/send-pack.c
> index 8932142312..69c432ef1a 100644
> --- a/builtin/send-pack.c
> +++ b/builtin/send-pack.c
> @@ -87,6 +87,10 @@ static void print_helper_status(struct ref *ref)
>  			break;
>  
>  		case REF_STATUS_EXPECTING_REPORT:
> +			res = "error";
> +			msg = "expecting report";
> +			break;
> +
>  		default:
>  			continue;
>  		}

That is obviously correct, minimum and surprisingly simple ;-)

Thanks.

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH 0/2] handling REF_STATUS_EXPECTING_REPORT over http
  2021-10-18 19:34 [PATCH 0/2] handling REF_STATUS_EXPECTING_REPORT over http Jeff King
  2021-10-18 19:43 ` [PATCH 1/2] send-pack: complain about "expecting report" with --helper-status Jeff King
  2021-10-18 19:45 ` [PATCH 2/2] transport-helper: recognize "expecting report" error from send-pack Jeff King
@ 2021-10-19 23:58 ` brian m. carlson
  2 siblings, 0 replies; 5+ messages in thread
From: brian m. carlson @ 2021-10-19 23:58 UTC (permalink / raw)
  To: Jeff King; +Cc: git

[-- Attachment #1: Type: text/plain, Size: 1653 bytes --]

On 2021-10-18 at 19:34:59, Jeff King wrote:
> I recently saw a case where GitHub's server implementation failed to
> correctly report ref statuses. In this case it was because it hit an
> internal timeout, but the root cause doesn't really matter; the server
> is doing the wrong thing.
> 
> But what's interesting on the Git client side is that we produce quite a
> confusing message for http. For ssh, we correctly say something like (in
> this example, I'm pushing an absurd number of tags that causes the
> timeout):
> 
>    To ssh://github.com/some/repo
>     ! [remote failure]  tag-1 -> tag-1 (remote failed to report status)
>    [...etc...]
>     ! [remote failure]  tag-999 -> tag-999 (remote failed to report status)
>    error: failed to push some refs to 'ssh://github.com/some/repo'
> 
> but for http, I get just:
> 
>   Everything up-to-date
> 
> Which is misleading to say the least. The issue is communication between
> send-pack and remote-curl on the client side.
> 
> The first patch does the minimal fix. The second one has some cosmetic
> improvements. They arguably could be squashed together, but I think
> keeping them split shows exactly what the second patch is actually
> accomplishing (especially with the minor change to the expected output
> in the test).
> 
> This bug has been around since smart http was added in 2009. I just
> prepared it on master, but I expect it could also go to maint.

This seems sane to me, and I'm delighted you managed to add a test for
this case (which I was not expecting to be possible).
-- 
brian m. carlson (he/him or they/them)
Toronto, Ontario, CA

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 262 bytes --]

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2021-10-19 23:59 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-10-18 19:34 [PATCH 0/2] handling REF_STATUS_EXPECTING_REPORT over http Jeff King
2021-10-18 19:43 ` [PATCH 1/2] send-pack: complain about "expecting report" with --helper-status Jeff King
2021-10-18 20:26   ` Junio C Hamano
2021-10-18 19:45 ` [PATCH 2/2] transport-helper: recognize "expecting report" error from send-pack Jeff King
2021-10-19 23:58 ` [PATCH 0/2] handling REF_STATUS_EXPECTING_REPORT over http brian m. carlson

Code repositories for project(s) associated with this public inbox

	https://80x24.org/mirrors/git.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).