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>
main
Ted Nyman 2026-07-26 17:28:40 -07:00 committed by Junio C Hamano
parent f0d866a2ea
commit 85f4f04f82
4 changed files with 25 additions and 3 deletions

View File

@ -70,7 +70,8 @@ static void fetch_single_packfile(struct object_id *packfile_hash,


if (start_active_slot(preq->slot)) { if (start_active_slot(preq->slot)) {
run_active_slot(preq->slot); run_active_slot(preq->slot);
if (results.curl_result != CURLE_OK) { if (results.curl_result != CURLE_OK &&
results.http_code != 416) {
struct url_info url; struct url_info url;
char *nurl = url_normalize(preq->url, &url); char *nurl = url_normalize(preq->url, &url);
if (!nurl || !git_env_bool("GIT_TRACE_REDACT", 1)) { if (!nurl || !git_env_bool("GIT_TRACE_REDACT", 1)) {

View File

@ -595,7 +595,8 @@ static void finish_request(struct transfer_request *request)


} else if (request->state == RUN_FETCH_PACKED) { } else if (request->state == RUN_FETCH_PACKED) {
int fail = 1; int fail = 1;
if (request->curl_result != CURLE_OK) { if (request->curl_result != CURLE_OK &&
request->http_code != 416) {
fprintf(stderr, "Unable to get pack file %s\n%s", fprintf(stderr, "Unable to get pack file %s\n%s",
request->url, curl_errorstr); request->url, curl_errorstr);
} else { } else {

View File

@ -451,7 +451,8 @@ static int http_fetch_pack(struct walker *walker, struct alt_base *repo,


if (start_active_slot(preq->slot)) { if (start_active_slot(preq->slot)) {
run_active_slot(preq->slot); run_active_slot(preq->slot);
if (results.curl_result != CURLE_OK) { if (results.curl_result != CURLE_OK &&
results.http_code != 416) {
error("Unable to get pack file %s\n%s", preq->url, error("Unable to get pack file %s\n%s", preq->url,
curl_errorstr); curl_errorstr);
goto abort; goto abort;

View File

@ -293,6 +293,25 @@ test_expect_success 'http-fetch --packfile' '
git -C packfileclient cat-file -e "$HASH" git -C packfileclient cat-file -e "$HASH"
' '


test_expect_success 'http-fetch --packfile accepts an already complete partial' '
git init packfileclient-complete &&
p=$(cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git &&
ls objects/pack/pack-*.pack) &&
packhash=$(basename "$p" .pack) &&
packhash=${packhash#pack-} &&
tmpfile="packfileclient-complete/.git/objects/pack/pack-$packhash.pack.temp" &&
cp "$HTTPD_DOCUMENT_ROOT_PATH/repo_pack.git/$p" "$tmpfile" &&
chmod u+w "$tmpfile" &&
GIT_TRACE_CURL="$TRASH_DIRECTORY/complete.trace" \
git -C packfileclient-complete http-fetch --packfile="$packhash" \
--index-pack-arg=index-pack \
--index-pack-arg=--stdin --index-pack-arg=--keep \
"$HTTPD_URL/dumb/repo_pack.git/$p" >out &&
test_grep "416 Requested Range Not Satisfiable" complete.trace &&
test_path_is_missing "$tmpfile" &&
git -C packfileclient-complete cat-file -e "$HASH"
'

test_expect_success 'fetch notices corrupt pack' ' test_expect_success 'fetch notices corrupt pack' '
cp -R "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git "$HTTPD_DOCUMENT_ROOT_PATH"/repo_bad1.git && cp -R "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git "$HTTPD_DOCUMENT_ROOT_PATH"/repo_bad1.git &&
(cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_bad1.git && (cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_bad1.git &&