fetch-pack: accept "pack" output for packfile URIs

When index-pack finds an existing keep file it reports pack rather than keep. Accept either result from http-fetch, and only register a keep lockfile when this fetch created it. Read the pack/keep prefix and hash without consuming any following fsck output, validate the reported pack hash against the advertised hash, and exercise a packfile URI fetch with a pre-existing keep file. Signed-off-by: Ted Nyman <tnyman@openai.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Ted Nyman committed Jul 21, 2026 at 16:29 UTC 1c26a0b3076bcdb308f01a86e894d74dfe874ea4
2 files changed +49 -15
fetch-pack.c
+18 -15
@@ -1887,9 +1887,10 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
1887 }
1888
1889 for (i = 0; i < packfile_uris.nr; i++) {
1890 + bool created_keep;
1891 int j;
1892 struct child_process cmd = CHILD_PROCESS_INIT;
1892 - char packname[GIT_MAX_HEXSZ + 1];
1893 + char packhash[GIT_MAX_HEXSZ + 1];
1894 const char *uri = packfile_uris.items[i].string +
1895 the_hash_algo->hexsz + 1;
1896
@@ -1907,16 +1908,17 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
1908 if (start_command(&cmd))
1909 die("fetch-pack: unable to spawn http-fetch");
1910
1910 - if (read_in_full(cmd.out, packname, 5) < 0 ||
1911 - memcmp(packname, "keep\t", 5))
1912 - die("fetch-pack: expected keep then TAB at start of http-fetch output");
1911 + if (read_in_full(cmd.out, packhash, 5) != 5 ||
1912 + (memcmp(packhash, "keep\t", 5) &&
1913 + memcmp(packhash, "pack\t", 5)))
1914 + die("fetch-pack: expected pack or keep then TAB at start of http-fetch output");
1915 + created_keep = !memcmp(packhash, "keep\t", 5);
1916
1914 - if (read_in_full(cmd.out, packname,
1915 - the_hash_algo->hexsz + 1) < 0 ||
1916 - packname[the_hash_algo->hexsz] != '\n')
1917 - die("fetch-pack: expected hash then LF at end of http-fetch output");
1918 -
1919 - packname[the_hash_algo->hexsz] = '\0';
1917 + if (read_in_full(cmd.out, packhash,
1918 + the_hash_algo->hexsz + 1) != the_hash_algo->hexsz + 1 ||
1919 + packhash[the_hash_algo->hexsz] != '\n')
1920 + die("fetch-pack: expected hash then LF in http-fetch output");
1921 + packhash[the_hash_algo->hexsz] = '\0';
1922
1923 parse_gitmodules_oids(cmd.out, &fsck_options.gitmodules_found);
1924
@@ -1925,16 +1927,17 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
1927 if (finish_command(&cmd))
1928 die("fetch-pack: unable to finish http-fetch");
1929
1928 - if (memcmp(packfile_uris.items[i].string, packname,
1930 + if (memcmp(packfile_uris.items[i].string, packhash,
1931 the_hash_algo->hexsz))
1932 die("fetch-pack: pack downloaded from %s does not match expected hash %.*s",
1933 uri, (int) the_hash_algo->hexsz,
1934 packfile_uris.items[i].string);
1935
1934 - string_list_append_nodup(pack_lockfiles,
1935 - xstrfmt("%s/pack/pack-%s.keep",
1936 - repo_get_object_directory(the_repository),
1937 - packname));
1936 + if (created_keep)
1937 + string_list_append_nodup(pack_lockfiles,
1938 + xstrfmt("%s/pack/pack-%s.keep",
1939 + repo_get_object_directory(the_repository),
1940 + packhash));
1941 }
1942 string_list_clear(&packfile_uris, 0);
1943 strvec_clear(&index_pack_args);
t/t5702-protocol-v2.sh
+31
@@ -1291,6 +1291,37 @@ test_expect_success 'packfile URIs with fetch instead of clone' '
1291 fetch "$HTTPD_URL/smart/http_parent"
1292 '
1293
1294 +test_expect_success 'packfile URI preserves an existing keep file' '
1295 + P="$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
1296 + rm -rf "$P" http_child keep.expect &&
1297 +
1298 + git init "$P" &&
1299 + git -C "$P" config uploadpack.allowsidebandall true &&
1300 +
1301 + echo my-blob >"$P/my-blob" &&
1302 + git -C "$P" add my-blob &&
1303 + git -C "$P" commit -m x &&
1304 + configure_exclusion "$P" my-blob >h &&
1305 +
1306 + git init http_child &&
1307 + packhash=$(cat packh) &&
1308 + keep="http_child/.git/objects/pack/pack-$packhash.keep" &&
1309 + echo pre-existing >"$keep" &&
1310 + cp "$keep" keep.expect &&
1311 +
1312 + GIT_TEST_SIDEBAND_ALL=1 \
1313 + git -C http_child -c protocol.version=2 \
1314 + -c fetch.uriprotocols=http,https \
1315 + fetch "$HTTPD_URL/smart/http_parent" &&
1316 +
1317 + test_path_is_file \
1318 + "http_child/.git/objects/pack/pack-$packhash.pack" &&
1319 + test_path_is_file \
1320 + "http_child/.git/objects/pack/pack-$packhash.idx" &&
1321 + test_cmp keep.expect "$keep" &&
1322 + git -C http_child cat-file -e "$(cat h)"
1323 +'
1324 +
1325 test_expect_success 'fetching with valid packfile URI but invalid hash fails' '
1326 P="$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
1327 rm -rf "$P" http_child log &&