remote-curl: fix parsing of detached SHA256 heads

The dumb HTTP transport tries to read the remote HEAD reference by downloading the "HEAD" file and then parsing it via `http_fetch_ref()`. This function will either parse the file as an object ID in case it is exactly `the_hash_algo->hexsz` long, or otherwise it will check whether the reference starts with "ref :" and parse it as a symbolic ref. This is broken when parsing detached HEADs of a remote SHA256 repository because we never update `the_hash_algo` to the discovered remote object hash. Consequently, `the_hash_algo` will always be the fallback SHA1 hash algorithm, which will cause us to fail parsing HEAD altogteher when it contains a SHA256 object ID. Fix this issue by setting up `the_hash_algo` via `repo_set_hash_algo()`. While at it, let's make the expected SHA1 fallback explicit in our code, which also addresses an upcoming issue where we are going to remove the SHA1 fallback for `the_hash_algo`. Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Patrick Steinhardt committed May 7, 2024 at 06:53 UTC bd455cec37551557e9122102606299c4bdc93505
2 files changed +33 -1
remote-curl.c
+18 -1
@@ -266,12 +266,23 @@ static struct ref *parse_git_refs(struct discovery *heads, int for_push)
266 return list;
267 }
268
269 +/*
270 + * Try to detect the hash algorithm used by the remote repository when using
271 + * the dumb HTTP transport. As dumb transports cannot tell us the object hash
272 + * directly have to derive it from the advertised ref lengths.
273 + */
274 static const struct git_hash_algo *detect_hash_algo(struct discovery *heads)
275 {
276 const char *p = memchr(heads->buf, '\t', heads->len);
277 int algo;
278 +
279 + /*
280 + * In case the remote has no refs we have no way to reliably determine
281 + * the object hash used by that repository. In that case we simply fall
282 + * back to SHA1, which may or may not be correct.
283 + */
284 if (!p)
274 - return the_hash_algo;
285 + return &hash_algos[GIT_HASH_SHA1];
286
287 algo = hash_algo_by_length((p - heads->buf) / 2);
288 if (algo == GIT_HASH_UNKNOWN)
@@ -295,6 +306,12 @@ static struct ref *parse_info_refs(struct discovery *heads)
306 "is this a git repository?",
307 transport_anonymize_url(url.buf));
308
309 + /*
310 + * Set the repository's hash algo to whatever we have just detected.
311 + * This ensures that we can correctly parse the remote references.
312 + */
313 + repo_set_hash_algo(the_repository, hash_algo_by_ptr(options.hash_algo));
314 +
315 data = heads->buf;
316 start = NULL;
317 mid = data;
t/t5550-http-fetch-dumb.sh
+15
@@ -55,6 +55,21 @@ test_expect_success 'list refs from outside any repository' '
55 test_cmp expect actual
56 '
57
58 +
59 +test_expect_success 'list detached HEAD from outside any repository' '
60 + git clone --mirror "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" \
61 + "$HTTPD_DOCUMENT_ROOT_PATH/repo-detached.git" &&
62 + git -C "$HTTPD_DOCUMENT_ROOT_PATH/repo-detached.git" \
63 + update-ref --no-deref HEAD refs/heads/main &&
64 + git -C "$HTTPD_DOCUMENT_ROOT_PATH/repo-detached.git" update-server-info &&
65 + cat >expect <<-EOF &&
66 + $(git rev-parse main) HEAD
67 + $(git rev-parse main) refs/heads/main
68 + EOF
69 + nongit git ls-remote "$HTTPD_URL/dumb/repo-detached.git" >actual &&
70 + test_cmp expect actual
71 +'
72 +
73 test_expect_success 'create password-protected repository' '
74 mkdir -p "$HTTPD_DOCUMENT_ROOT_PATH/auth/dumb/" &&
75 cp -Rf "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" \