bundle-uri: copy all bundle references ino the refs/bundle space

When downloading bundles via the bundle-uri functionality, we only copy the references from refs/heads into the refs/bundle space. I'm not sure why this refspec is hardcoded to be so limited, but it makes the ref negotiation on the subsequent fetch suboptimal, since it won't use objects that are referenced outside of the current heads of the bundled repository. This change to copy everything in refs/ in the bundle to refs/bundles/ significantly helps the subsequent fetch, since nearly all the references are now included in the negotiation. The update to the bundle-uri unbundling refspec puts all the heads from a bundle file into refs/bundle/heads instead of directly into refs/bundle/ so the tests also need to be updated to look in the new heirarchy. Signed-off-by: Scott Chacon <schacon@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Scott Chacon committed Apr 25, 2025 at 19:33 UTC c858c6442b53fc9b7e79923546668fe38fe2c58d
3 files changed +94 -94
Documentation/technical/bundle-uri.adoc
+7 -7
@@ -232,13 +232,13 @@ will interact with bundle URIs according to the following flow:
232 are present in the client repository. If some are missing, then the
233 client delays unbundling until other bundles have been unbundled,
234 making those OIDs present. When all required OIDs are present, the
235 - client unbundles that data using a refspec. The default refspec is
236 - `+refs/heads/*:refs/bundles/*`, but this can be configured. These refs
237 - are stored so that later `git fetch` negotiations can communicate each
238 - bundled ref as a `have`, reducing the size of the fetch over the Git
239 - protocol. To allow pruning refs from this ref namespace, Git may
240 - introduce a numbered namespace (such as `refs/bundles/<i>/*`) such that
241 - stale bundle refs can be deleted.
235 + client unbundles that data using a refspec. The refspec used is
236 + `+refs/*:refs/bundles/*`. These refs are stored so that later
237 + `git fetch` negotiations can communicate each bundled ref as a `have`,
238 + reducing the size of the fetch over the Git protocol. To allow pruning
239 + refs from this ref namespace, Git may introduce a numbered namespace
240 + (such as `refs/bundles/<i>/*`) such that stale bundle refs can be
241 + deleted.
242
243 3. If the file is instead a bundle list, then the client inspects the
244 `bundle.mode` to see if the list is of the `all` or `any` form.
bundle-uri.c
+1 -1
@@ -403,7 +403,7 @@ static int unbundle_from_file(struct repository *r, const char *file)
403 const char *branch_name;
404 int has_old;
405
406 - if (!skip_prefix(refname->string, "refs/heads/", &branch_name))
406 + if (!skip_prefix(refname->string, "refs/", &branch_name))
407 continue;
408
409 strbuf_setlen(&bundle_ref, bundle_prefix_len);
t/t5558-clone-bundle-uri.sh
+86 -86
@@ -58,7 +58,7 @@ test_expect_success 'create bundle' '
58 test_expect_success 'clone with path bundle' '
59 git clone --bundle-uri="clone-from/B.bundle" \
60 clone-from clone-path &&
61 - git -C clone-path rev-parse refs/bundles/topic >actual &&
61 + git -C clone-path rev-parse refs/bundles/heads/topic >actual &&
62 git -C clone-from rev-parse topic >expect &&
63 test_cmp expect actual
64 '
@@ -68,9 +68,9 @@ test_expect_success 'clone with bundle that has bad header' '
68 git clone --bundle-uri="clone-from/bad-header.bundle" \
69 clone-from clone-bad-header 2>err &&
70 commit_b=$(git -C clone-from rev-parse B) &&
71 - test_grep "trying to write ref '\''refs/bundles/topic'\'' with nonexistent object $commit_b" err &&
71 + test_grep "trying to write ref '\''refs/bundles/heads/topic'\'' with nonexistent object $commit_b" err &&
72 git -C clone-bad-header for-each-ref --format="%(refname)" >refs &&
73 - test_grep ! "refs/bundles/" refs
73 + test_grep ! "refs/bundles/heads/" refs
74 '
75
76 test_expect_success 'clone with bundle that has bad object' '
@@ -78,8 +78,8 @@ test_expect_success 'clone with bundle that has bad object' '
78 git clone --bundle-uri="clone-from/bad-object.bundle" \
79 clone-from clone-bad-object-no-fsck &&
80 git -C clone-bad-object-no-fsck for-each-ref --format="%(refname)" >refs &&
81 - grep "refs/bundles/" refs >actual &&
82 - test_write_lines refs/bundles/bad >expect &&
81 + grep "refs/bundles/heads/" refs >actual &&
82 + test_write_lines refs/bundles/heads/bad >expect &&
83 test_cmp expect actual &&
84
85 # Unbundle fails with fsckObjects set true, but clone can still proceed.
@@ -87,14 +87,14 @@ test_expect_success 'clone with bundle that has bad object' '
87 clone-from clone-bad-object-fsck 2>err &&
88 test_grep "missingEmail" err &&
89 git -C clone-bad-object-fsck for-each-ref --format="%(refname)" >refs &&
90 - test_grep ! "refs/bundles/" refs
90 + test_grep ! "refs/bundles/heads/" refs
91 '
92
93 test_expect_success 'clone with path bundle and non-default hash' '
94 test_when_finished "rm -rf clone-path-non-default-hash" &&
95 GIT_DEFAULT_HASH=sha256 git clone --bundle-uri="clone-from/B.bundle" \
96 clone-from clone-path-non-default-hash &&
97 - git -C clone-path-non-default-hash rev-parse refs/bundles/topic >actual &&
97 + git -C clone-path-non-default-hash rev-parse refs/bundles/heads/topic >actual &&
98 git -C clone-from rev-parse topic >expect &&
99 test_cmp expect actual
100 '
@@ -102,7 +102,7 @@ test_expect_success 'clone with path bundle and non-default hash' '
102 test_expect_success 'clone with file:// bundle' '
103 git clone --bundle-uri="file://$(pwd)/clone-from/B.bundle" \
104 clone-from clone-file &&
105 - git -C clone-file rev-parse refs/bundles/topic >actual &&
105 + git -C clone-file rev-parse refs/bundles/heads/topic >actual &&
106 git -C clone-from rev-parse topic >expect &&
107 test_cmp expect actual
108 '
@@ -173,12 +173,12 @@ test_expect_success 'clone bundle list (file, no heuristic)' '
173 git -C clone-list-file cat-file --batch-check <oids &&
174
175 git -C clone-list-file for-each-ref --format="%(refname)" >refs &&
176 - grep "refs/bundles/" refs >actual &&
176 + grep "refs/bundles/heads/" refs >actual &&
177 cat >expect <<-\EOF &&
178 - refs/bundles/base
179 - refs/bundles/left
180 - refs/bundles/merge
181 - refs/bundles/right
178 + refs/bundles/heads/base
179 + refs/bundles/heads/left
180 + refs/bundles/heads/merge
181 + refs/bundles/heads/right
182 EOF
183 test_cmp expect actual
184 '
@@ -220,10 +220,10 @@ test_expect_success 'clone bundle list (file, all mode, some failures)' '
220 git -C clone-all-some cat-file --batch-check <oids &&
221
222 git -C clone-all-some for-each-ref --format="%(refname)" >refs &&
223 - grep "refs/bundles/" refs >actual &&
223 + grep "refs/bundles/heads/" refs >actual &&
224 cat >expect <<-\EOF &&
225 - refs/bundles/base
226 - refs/bundles/left
225 + refs/bundles/heads/base
226 + refs/bundles/heads/left
227 EOF
228 test_cmp expect actual
229 '
@@ -253,7 +253,7 @@ test_expect_success 'clone bundle list (file, all mode, all failures)' '
253 git -C clone-all-fail cat-file --batch-check <oids &&
254
255 git -C clone-all-fail for-each-ref --format="%(refname)" >refs &&
256 - ! grep "refs/bundles/" refs
256 + ! grep "refs/bundles/heads/" refs
257 '
258
259 test_expect_success 'clone bundle list (file, any mode)' '
@@ -282,9 +282,9 @@ test_expect_success 'clone bundle list (file, any mode)' '
282 git -C clone-any-file cat-file --batch-check <oids &&
283
284 git -C clone-any-file for-each-ref --format="%(refname)" >refs &&
285 - grep "refs/bundles/" refs >actual &&
285 + grep "refs/bundles/heads/" refs >actual &&
286 cat >expect <<-\EOF &&
287 - refs/bundles/base
287 + refs/bundles/heads/base
288 EOF
289 test_cmp expect actual
290 '
@@ -313,7 +313,7 @@ test_expect_success 'clone bundle list (file, any mode, all failures)' '
313 git -C clone-any-fail cat-file --batch-check <oids &&
314
315 git -C clone-any-fail for-each-ref --format="%(refname)" >refs &&
316 - ! grep "refs/bundles/" refs
316 + ! grep "refs/bundles/heads/" refs
317 '
318
319 test_expect_success 'negotiation: bundle with part of wanted commits' '
@@ -322,10 +322,10 @@ test_expect_success 'negotiation: bundle with part of wanted commits' '
322 git clone --no-local --bundle-uri="clone-from/A.bundle" \
323 clone-from nego-bundle-part &&
324 git -C nego-bundle-part for-each-ref --format="%(refname)" >refs &&
325 - grep "refs/bundles/" refs >actual &&
326 - test_write_lines refs/bundles/topic >expect &&
325 + grep "refs/bundles/heads/" refs >actual &&
326 + test_write_lines refs/bundles/heads/topic >expect &&
327 test_cmp expect actual &&
328 - # Ensure that refs/bundles/topic are sent as "have".
328 + # Ensure that refs/bundles/heads/topic are sent as "have".
329 tip=$(git -C clone-from rev-parse A) &&
330 test_grep "clone> have $tip" trace-packet.txt
331 '
@@ -337,8 +337,8 @@ test_expect_success 'negotiation: bundle with all wanted commits' '
337 --bundle-uri="clone-from/B.bundle" \
338 clone-from nego-bundle-all &&
339 git -C nego-bundle-all for-each-ref --format="%(refname)" >refs &&
340 - grep "refs/bundles/" refs >actual &&
341 - test_write_lines refs/bundles/topic >expect &&
340 + grep "refs/bundles/heads/" refs >actual &&
341 + test_write_lines refs/bundles/heads/topic >expect &&
342 test_cmp expect actual &&
343 # We already have all needed commits so no "want" needed.
344 test_grep ! "clone> want " trace-packet.txt
@@ -363,13 +363,13 @@ test_expect_success 'negotiation: bundle list (no heuristic)' '
363 clone-from nego-bundle-list-no-heuristic &&
364
365 git -C nego-bundle-list-no-heuristic for-each-ref --format="%(refname)" >refs &&
366 - grep "refs/bundles/" refs >actual &&
366 + grep "refs/bundles/heads/" refs >actual &&
367 cat >expect <<-\EOF &&
368 - refs/bundles/base
369 - refs/bundles/left
368 + refs/bundles/heads/base
369 + refs/bundles/heads/left
370 EOF
371 test_cmp expect actual &&
372 - tip=$(git -C nego-bundle-list-no-heuristic rev-parse refs/bundles/left) &&
372 + tip=$(git -C nego-bundle-list-no-heuristic rev-parse refs/bundles/heads/left) &&
373 test_grep "clone> have $tip" trace-packet.txt
374 '
375
@@ -395,13 +395,13 @@ test_expect_success 'negotiation: bundle list (creationToken)' '
395 clone-from nego-bundle-list-heuristic &&
396
397 git -C nego-bundle-list-heuristic for-each-ref --format="%(refname)" >refs &&
398 - grep "refs/bundles/" refs >actual &&
398 + grep "refs/bundles/heads/" refs >actual &&
399 cat >expect <<-\EOF &&
400 - refs/bundles/base
401 - refs/bundles/left
400 + refs/bundles/heads/base
401 + refs/bundles/heads/left
402 EOF
403 test_cmp expect actual &&
404 - tip=$(git -C nego-bundle-list-heuristic rev-parse refs/bundles/left) &&
404 + tip=$(git -C nego-bundle-list-heuristic rev-parse refs/bundles/heads/left) &&
405 test_grep "clone> have $tip" trace-packet.txt
406 '
407
@@ -428,10 +428,10 @@ test_expect_success 'negotiation: bundle list with all wanted commits' '
428 clone-from nego-bundle-list-all &&
429
430 git -C nego-bundle-list-all for-each-ref --format="%(refname)" >refs &&
431 - grep "refs/bundles/" refs >actual &&
431 + grep "refs/bundles/heads/" refs >actual &&
432 cat >expect <<-\EOF &&
433 - refs/bundles/base
434 - refs/bundles/left
433 + refs/bundles/heads/base
434 + refs/bundles/heads/left
435 EOF
436 test_cmp expect actual &&
437 # We already have all needed commits so no "want" needed.
@@ -465,7 +465,7 @@ test_expect_success 'clone HTTP bundle' '
465
466 git clone --bundle-uri="$HTTPD_URL/B.bundle" \
467 "$HTTPD_URL/smart/fetch.git" clone-http &&
468 - git -C clone-http rev-parse refs/bundles/topic >actual &&
468 + git -C clone-http rev-parse refs/bundles/heads/topic >actual &&
469 git -C clone-from rev-parse topic >expect &&
470 test_cmp expect actual &&
471
@@ -476,7 +476,7 @@ test_expect_success 'clone HTTP bundle with non-default hash' '
476 test_when_finished "rm -rf clone-http-non-default-hash" &&
477 GIT_DEFAULT_HASH=sha256 git clone --bundle-uri="$HTTPD_URL/B.bundle" \
478 "$HTTPD_URL/smart/fetch.git" clone-http-non-default-hash &&
479 - git -C clone-http-non-default-hash rev-parse refs/bundles/topic >actual &&
479 + git -C clone-http-non-default-hash rev-parse refs/bundles/heads/topic >actual &&
480 git -C clone-from rev-parse topic >expect &&
481 test_cmp expect actual
482 '
@@ -553,12 +553,12 @@ test_expect_success 'clone bundle list (HTTP, any mode)' '
553 git -C clone-any-http cat-file --batch-check <oids &&
554
555 git -C clone-list-file for-each-ref --format="%(refname)" >refs &&
556 - grep "refs/bundles/" refs >actual &&
556 + grep "refs/bundles/heads/" refs >actual &&
557 cat >expect <<-\EOF &&
558 - refs/bundles/base
559 - refs/bundles/left
560 - refs/bundles/merge
561 - refs/bundles/right
558 + refs/bundles/heads/base
559 + refs/bundles/heads/left
560 + refs/bundles/heads/merge
561 + refs/bundles/heads/right
562 EOF
563 test_cmp expect actual
564 '
@@ -641,9 +641,9 @@ test_expect_success 'clone incomplete bundle list (http, creationToken)' '
641 test_cmp expect actual &&
642
643 # We now have only one bundle ref.
644 - git -C clone-token-http for-each-ref --format="%(refname)" "refs/bundles/*" >refs &&
644 + git -C clone-token-http for-each-ref --format="%(refname)" "refs/bundles/heads/*" >refs &&
645 cat >expect <<-\EOF &&
646 - refs/bundles/base
646 + refs/bundles/heads/base
647 EOF
648 test_cmp expect refs &&
649
@@ -679,13 +679,13 @@ test_expect_success 'clone incomplete bundle list (http, creationToken)' '
679 test_cmp expect actual &&
680
681 # We now have all bundle refs.
682 - git -C clone-token-http for-each-ref --format="%(refname)" "refs/bundles/*" >refs &&
682 + git -C clone-token-http for-each-ref --format="%(refname)" "refs/bundles/heads/*" >refs &&
683
684 cat >expect <<-\EOF &&
685 - refs/bundles/base
686 - refs/bundles/left
687 - refs/bundles/merge
688 - refs/bundles/right
685 + refs/bundles/heads/base
686 + refs/bundles/heads/left
687 + refs/bundles/heads/merge
688 + refs/bundles/heads/right
689 EOF
690 test_cmp expect refs
691 '
@@ -721,9 +721,9 @@ test_expect_success 'http clone with bundle.heuristic creates fetch.bundleURI' '
721 test_cmp expect actual &&
722
723 # only received base ref from bundle-1
724 - git -C fetch-http-4 for-each-ref --format="%(refname)" "refs/bundles/*" >refs &&
724 + git -C fetch-http-4 for-each-ref --format="%(refname)" "refs/bundles/heads/*" >refs &&
725 cat >expect <<-\EOF &&
726 - refs/bundles/base
726 + refs/bundles/heads/base
727 EOF
728 test_cmp expect refs &&
729
@@ -749,10 +749,10 @@ test_expect_success 'http clone with bundle.heuristic creates fetch.bundleURI' '
749 test_cmp expect actual &&
750
751 # received left from bundle-2
752 - git -C fetch-http-4 for-each-ref --format="%(refname)" "refs/bundles/*" >refs &&
752 + git -C fetch-http-4 for-each-ref --format="%(refname)" "refs/bundles/heads/*" >refs &&
753 cat >expect <<-\EOF &&
754 - refs/bundles/base
755 - refs/bundles/left
754 + refs/bundles/heads/base
755 + refs/bundles/heads/left
756 EOF
757 test_cmp expect refs &&
758
@@ -795,12 +795,12 @@ test_expect_success 'http clone with bundle.heuristic creates fetch.bundleURI' '
795
796 # received merge ref from bundle-4, but right is missing
797 # because we did not download bundle-3.
798 - git -C fetch-http-4 for-each-ref --format="%(refname)" "refs/bundles/*" >refs &&
798 + git -C fetch-http-4 for-each-ref --format="%(refname)" "refs/bundles/heads/*" >refs &&
799
800 cat >expect <<-\EOF &&
801 - refs/bundles/base
802 - refs/bundles/left
803 - refs/bundles/merge
801 + refs/bundles/heads/base
802 + refs/bundles/heads/left
803 + refs/bundles/heads/merge
804 EOF
805 test_cmp expect refs &&
806
@@ -862,7 +862,7 @@ test_expect_success 'creationToken heuristic with failed downloads (clone)' '
862 test_cmp expect actual &&
863
864 # All bundles failed to unbundle
865 - git -C download-1 for-each-ref --format="%(refname)" "refs/bundles/*" >refs &&
865 + git -C download-1 for-each-ref --format="%(refname)" "refs/bundles/heads/*" >refs &&
866 test_must_be_empty refs &&
867
868 # Case 2: middle bundle does not exist, only two bundles can unbundle
@@ -909,10 +909,10 @@ test_expect_success 'creationToken heuristic with failed downloads (clone)' '
909 test_cmp expect actual &&
910
911 # bundle-1 and bundle-3 could unbundle, but bundle-4 could not
912 - git -C download-2 for-each-ref --format="%(refname)" "refs/bundles/*" >refs &&
912 + git -C download-2 for-each-ref --format="%(refname)" "refs/bundles/heads/*" >refs &&
913 cat >expect <<-EOF &&
914 - refs/bundles/base
915 - refs/bundles/right
914 + refs/bundles/heads/base
915 + refs/bundles/heads/right
916 EOF
917 test_cmp expect refs &&
918
@@ -961,11 +961,11 @@ test_expect_success 'creationToken heuristic with failed downloads (clone)' '
961 test_cmp expect actual &&
962
963 # fake.bundle did not unbundle, but the others did.
964 - git -C download-3 for-each-ref --format="%(refname)" "refs/bundles/*" >refs &&
964 + git -C download-3 for-each-ref --format="%(refname)" "refs/bundles/heads/*" >refs &&
965 cat >expect <<-EOF &&
966 - refs/bundles/base
967 - refs/bundles/left
968 - refs/bundles/right
966 + refs/bundles/heads/base
967 + refs/bundles/heads/left
968 + refs/bundles/heads/right
969 EOF
970 test_cmp expect refs
971 '
@@ -1083,15 +1083,15 @@ test_expect_success 'creationToken heuristic with failed downloads (fetch)' '
1083 test_cmp expect actual &&
1084
1085 # Check which bundles have unbundled by refs
1086 - git -C fetch-1 for-each-ref --format="%(refname)" "refs/bundles/*" >refs &&
1086 + git -C fetch-1 for-each-ref --format="%(refname)" "refs/bundles/heads/*" >refs &&
1087 cat >expect <<-EOF &&
1088 - refs/bundles/base
1089 - refs/bundles/left
1090 - refs/bundles/lefter
1091 - refs/bundles/merge
1092 - refs/bundles/right
1093 - refs/bundles/righter
1094 - refs/bundles/top
1088 + refs/bundles/heads/base
1089 + refs/bundles/heads/left
1090 + refs/bundles/heads/lefter
1091 + refs/bundles/heads/merge
1092 + refs/bundles/heads/right
1093 + refs/bundles/heads/righter
1094 + refs/bundles/heads/top
1095 EOF
1096 test_cmp expect refs &&
1097
@@ -1144,12 +1144,12 @@ test_expect_success 'creationToken heuristic with failed downloads (fetch)' '
1144 test_cmp expect actual &&
1145
1146 # Check which bundles have unbundled by refs
1147 - git -C fetch-2 for-each-ref --format="%(refname)" "refs/bundles/*" >refs &&
1147 + git -C fetch-2 for-each-ref --format="%(refname)" "refs/bundles/heads/*" >refs &&
1148 cat >expect <<-EOF &&
1149 - refs/bundles/base
1150 - refs/bundles/left
1151 - refs/bundles/merge
1152 - refs/bundles/right
1149 + refs/bundles/heads/base
1150 + refs/bundles/heads/left
1151 + refs/bundles/heads/merge
1152 + refs/bundles/heads/right
1153 EOF
1154 test_cmp expect refs &&
1155
@@ -1204,13 +1204,13 @@ test_expect_success 'creationToken heuristic with failed downloads (fetch)' '
1204 test_cmp expect actual &&
1205
1206 # Check which bundles have unbundled by refs
1207 - git -C fetch-3 for-each-ref --format="%(refname)" "refs/bundles/*" >refs &&
1207 + git -C fetch-3 for-each-ref --format="%(refname)" "refs/bundles/heads/*" >refs &&
1208 cat >expect <<-EOF &&
1209 - refs/bundles/base
1210 - refs/bundles/left
1211 - refs/bundles/lefter
1212 - refs/bundles/right
1213 - refs/bundles/righter
1209 + refs/bundles/heads/base
1210 + refs/bundles/heads/left
1211 + refs/bundles/heads/lefter
1212 + refs/bundles/heads/right
1213 + refs/bundles/heads/righter
1214 EOF
1215 test_cmp expect refs
1216 '