http: accept HTTP 416 for complete partial packs

A resumed pack request may already have all bytes of the remote pack. A server can respond to the resulting Range request with HTTP 416 instead of returning an empty response. Accept that response in each pack-download caller and let index-pack validate the completed staging file. This can happen without concurrent downloads when a previous attempt completed the transfer but failed before indexing it. Add a regression test that seeds a complete partial pack and checks that http-fetch indexes it after the server returns HTTP 416. Signed-off-by: Ted Nyman <tnyman@openai.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Ted Nyman committed Jul 26, 2026 at 17:28 UTC 85f4f04f820c9aa6cfdfb4594ceb9b41515a8245
4 files changed +25 -3
http-fetch.c
+2 -1
@@ -70,7 +70,8 @@ static void fetch_single_packfile(struct object_id *packfile_hash,
70
71 if (start_active_slot(preq->slot)) {
72 run_active_slot(preq->slot);
73 - if (results.curl_result != CURLE_OK) {
73 + if (results.curl_result != CURLE_OK &&
74 + results.http_code != 416) {
75 struct url_info url;
76 char *nurl = url_normalize(preq->url, &url);
77 if (!nurl || !git_env_bool("GIT_TRACE_REDACT", 1)) {
http-push.c
+2 -1
@@ -595,7 +595,8 @@ static void finish_request(struct transfer_request *request)
595
596 } else if (request->state == RUN_FETCH_PACKED) {
597 int fail = 1;
598 - if (request->curl_result != CURLE_OK) {
598 + if (request->curl_result != CURLE_OK &&
599 + request->http_code != 416) {
600 fprintf(stderr, "Unable to get pack file %s\n%s",
601 request->url, curl_errorstr);
602 } else {
http-walker.c
+2 -1
@@ -451,7 +451,8 @@ static int http_fetch_pack(struct walker *walker, struct alt_base *repo,
451
452 if (start_active_slot(preq->slot)) {
453 run_active_slot(preq->slot);
454 - if (results.curl_result != CURLE_OK) {
454 + if (results.curl_result != CURLE_OK &&
455 + results.http_code != 416) {
456 error("Unable to get pack file %s\n%s", preq->url,
457 curl_errorstr);
458 goto abort;
t/t5550-http-fetch-dumb.sh
+19
@@ -293,6 +293,25 @@ test_expect_success 'http-fetch --packfile' '
293 git -C packfileclient cat-file -e "$HASH"
294 '
295
296 +test_expect_success 'http-fetch --packfile accepts an already complete partial' '
297 + git init packfileclient-complete &&
298 + p=$(cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git &&
299 + ls objects/pack/pack-*.pack) &&
300 + packhash=$(basename "$p" .pack) &&
301 + packhash=${packhash#pack-} &&
302 + tmpfile="packfileclient-complete/.git/objects/pack/pack-$packhash.pack.temp" &&
303 + cp "$HTTPD_DOCUMENT_ROOT_PATH/repo_pack.git/$p" "$tmpfile" &&
304 + chmod u+w "$tmpfile" &&
305 + GIT_TRACE_CURL="$TRASH_DIRECTORY/complete.trace" \
306 + git -C packfileclient-complete http-fetch --packfile="$packhash" \
307 + --index-pack-arg=index-pack \
308 + --index-pack-arg=--stdin --index-pack-arg=--keep \
309 + "$HTTPD_URL/dumb/repo_pack.git/$p" >out &&
310 + test_grep "416 Requested Range Not Satisfiable" complete.trace &&
311 + test_path_is_missing "$tmpfile" &&
312 + git -C packfileclient-complete cat-file -e "$HASH"
313 +'
314 +
315 test_expect_success 'fetch notices corrupt pack' '
316 cp -R "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git "$HTTPD_DOCUMENT_ROOT_PATH"/repo_bad1.git &&
317 (cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_bad1.git &&