-
Notifications
You must be signed in to change notification settings - Fork 79
/
Copy pathbundle-uri.html
1225 lines (1225 loc) · 62.4 KB
/
bundle-uri.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
<head>
<meta charset="UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<meta name="generator" content="Asciidoctor 2.0.23"/>
<title>Bundle URIs</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700"/>
<style>
/*! Asciidoctor default stylesheet | MIT License | https://asciidoctor.org */
/* Uncomment the following line when using as a custom stylesheet */
/* @import "https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700"; */
html{font-family:sans-serif;-webkit-text-size-adjust:100%}
a{background:none}
a:focus{outline:thin dotted}
a:active,a:hover{outline:0}
h1{font-size:2em;margin:.67em 0}
b,strong{font-weight:bold}
abbr{font-size:.9em}
abbr[title]{cursor:help;border-bottom:1px dotted #dddddf;text-decoration:none}
dfn{font-style:italic}
hr{height:0}
mark{background:#ff0;color:#000}
code,kbd,pre,samp{font-family:monospace;font-size:1em}
pre{white-space:pre-wrap}
q{quotes:"\201C" "\201D" "\2018" "\2019"}
small{font-size:80%}
sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}
sup{top:-.5em}
sub{bottom:-.25em}
img{border:0}
svg:not(:root){overflow:hidden}
figure{margin:0}
audio,video{display:inline-block}
audio:not([controls]){display:none;height:0}
fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}
legend{border:0;padding:0}
button,input,select,textarea{font-family:inherit;font-size:100%;margin:0}
button,input{line-height:normal}
button,select{text-transform:none}
button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}
button[disabled],html input[disabled]{cursor:default}
input[type=checkbox],input[type=radio]{padding:0}
button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}
textarea{overflow:auto;vertical-align:top}
table{border-collapse:collapse;border-spacing:0}
*,::before,::after{box-sizing:border-box}
html,body{font-size:100%}
body{background:#fff;color:rgba(0,0,0,.8);padding:0;margin:0;font-family:"Noto Serif","DejaVu Serif",serif;line-height:1;position:relative;cursor:auto;-moz-tab-size:4;-o-tab-size:4;tab-size:4;word-wrap:anywhere;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased}
a:hover{cursor:pointer}
img,object,embed{max-width:100%;height:auto}
object,embed{height:100%}
img{-ms-interpolation-mode:bicubic}
.left{float:left!important}
.right{float:right!important}
.text-left{text-align:left!important}
.text-right{text-align:right!important}
.text-center{text-align:center!important}
.text-justify{text-align:justify!important}
.hide{display:none}
img,object,svg{display:inline-block;vertical-align:middle}
textarea{height:auto;min-height:50px}
select{width:100%}
.subheader,.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{line-height:1.45;color:#7a2518;font-weight:400;margin-top:0;margin-bottom:.25em}
div,dl,dt,dd,ul,ol,li,h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6,pre,form,p,blockquote,th,td{margin:0;padding:0}
a{color:#2156a5;text-decoration:underline;line-height:inherit}
a:hover,a:focus{color:#1d4b8f}
a img{border:0}
p{line-height:1.6;margin-bottom:1.25em;text-rendering:optimizeLegibility}
p aside{font-size:.875em;line-height:1.35;font-style:italic}
h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{font-family:"Open Sans","DejaVu Sans",sans-serif;font-weight:300;font-style:normal;color:#ba3925;text-rendering:optimizeLegibility;margin-top:1em;margin-bottom:.5em;line-height:1.0125em}
h1 small,h2 small,h3 small,#toctitle small,.sidebarblock>.content>.title small,h4 small,h5 small,h6 small{font-size:60%;color:#e99b8f;line-height:0}
h1{font-size:2.125em}
h2{font-size:1.6875em}
h3,#toctitle,.sidebarblock>.content>.title{font-size:1.375em}
h4,h5{font-size:1.125em}
h6{font-size:1em}
hr{border:solid #dddddf;border-width:1px 0 0;clear:both;margin:1.25em 0 1.1875em}
em,i{font-style:italic;line-height:inherit}
strong,b{font-weight:bold;line-height:inherit}
small{font-size:60%;line-height:inherit}
code{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;color:rgba(0,0,0,.9)}
ul,ol,dl{line-height:1.6;margin-bottom:1.25em;list-style-position:outside;font-family:inherit}
ul,ol{margin-left:1.5em}
ul li ul,ul li ol{margin-left:1.25em;margin-bottom:0}
ul.circle{list-style-type:circle}
ul.disc{list-style-type:disc}
ul.square{list-style-type:square}
ul.circle ul:not([class]),ul.disc ul:not([class]),ul.square ul:not([class]){list-style:inherit}
ol li ul,ol li ol{margin-left:1.25em;margin-bottom:0}
dl dt{margin-bottom:.3125em;font-weight:bold}
dl dd{margin-bottom:1.25em}
blockquote{margin:0 0 1.25em;padding:.5625em 1.25em 0 1.1875em;border-left:1px solid #ddd}
blockquote,blockquote p{line-height:1.6;color:rgba(0,0,0,.85)}
@media screen and (min-width:768px){h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2}
h1{font-size:2.75em}
h2{font-size:2.3125em}
h3,#toctitle,.sidebarblock>.content>.title{font-size:1.6875em}
h4{font-size:1.4375em}}
table{background:#fff;margin-bottom:1.25em;border:1px solid #dedede;word-wrap:normal}
table thead,table tfoot{background:#f7f8f7}
table thead tr th,table thead tr td,table tfoot tr th,table tfoot tr td{padding:.5em .625em .625em;font-size:inherit;color:rgba(0,0,0,.8);text-align:left}
table tr th,table tr td{padding:.5625em .625em;font-size:inherit;color:rgba(0,0,0,.8)}
table tr.even,table tr.alt{background:#f8f8f7}
table thead tr th,table tfoot tr th,table tbody tr td,table tr td,table tfoot tr td{line-height:1.6}
h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2;word-spacing:-.05em}
h1 strong,h2 strong,h3 strong,#toctitle strong,.sidebarblock>.content>.title strong,h4 strong,h5 strong,h6 strong{font-weight:400}
.center{margin-left:auto;margin-right:auto}
.stretch{width:100%}
.clearfix::before,.clearfix::after,.float-group::before,.float-group::after{content:" ";display:table}
.clearfix::after,.float-group::after{clear:both}
:not(pre).nobreak{word-wrap:normal}
:not(pre).nowrap{white-space:nowrap}
:not(pre).pre-wrap{white-space:pre-wrap}
:not(pre):not([class^=L])>code{font-size:.9375em;font-style:normal!important;letter-spacing:0;padding:.1em .5ex;word-spacing:-.15em;background:#f7f7f8;border-radius:4px;line-height:1.45;text-rendering:optimizeSpeed}
pre{color:rgba(0,0,0,.9);font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;line-height:1.45;text-rendering:optimizeSpeed}
pre code,pre pre{color:inherit;font-size:inherit;line-height:inherit}
pre>code{display:block}
pre.nowrap,pre.nowrap pre{white-space:pre;word-wrap:normal}
em em{font-style:normal}
strong strong{font-weight:400}
.keyseq{color:rgba(51,51,51,.8)}
kbd{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;display:inline-block;color:rgba(0,0,0,.8);font-size:.65em;line-height:1.45;background:#f7f7f7;border:1px solid #ccc;border-radius:3px;box-shadow:0 1px 0 rgba(0,0,0,.2),inset 0 0 0 .1em #fff;margin:0 .15em;padding:.2em .5em;vertical-align:middle;position:relative;top:-.1em;white-space:nowrap}
.keyseq kbd:first-child{margin-left:0}
.keyseq kbd:last-child{margin-right:0}
.menuseq,.menuref{color:#000}
.menuseq b:not(.caret),.menuref{font-weight:inherit}
.menuseq{word-spacing:-.02em}
.menuseq b.caret{font-size:1.25em;line-height:.8}
.menuseq i.caret{font-weight:bold;text-align:center;width:.45em}
b.button::before,b.button::after{position:relative;top:-1px;font-weight:400}
b.button::before{content:"[";padding:0 3px 0 2px}
b.button::after{content:"]";padding:0 2px 0 3px}
p a>code:hover{color:rgba(0,0,0,.9)}
#header,#content,#footnotes,#footer{width:100%;margin:0 auto;max-width:62.5em;*zoom:1;position:relative;padding-left:.9375em;padding-right:.9375em}
#header::before,#header::after,#content::before,#content::after,#footnotes::before,#footnotes::after,#footer::before,#footer::after{content:" ";display:table}
#header::after,#content::after,#footnotes::after,#footer::after{clear:both}
#content{margin-top:1.25em}
#content::before{content:none}
#header>h1:first-child{color:rgba(0,0,0,.85);margin-top:2.25rem;margin-bottom:0}
#header>h1:first-child+#toc{margin-top:8px;border-top:1px solid #dddddf}
#header>h1:only-child{border-bottom:1px solid #dddddf;padding-bottom:8px}
#header .details{border-bottom:1px solid #dddddf;line-height:1.45;padding-top:.25em;padding-bottom:.25em;padding-left:.25em;color:rgba(0,0,0,.6);display:flex;flex-flow:row wrap}
#header .details span:first-child{margin-left:-.125em}
#header .details span.email a{color:rgba(0,0,0,.85)}
#header .details br{display:none}
#header .details br+span::before{content:"\00a0\2013\00a0"}
#header .details br+span.author::before{content:"\00a0\22c5\00a0";color:rgba(0,0,0,.85)}
#header .details br+span#revremark::before{content:"\00a0|\00a0"}
#header #revnumber{text-transform:capitalize}
#header #revnumber::after{content:"\00a0"}
#content>h1:first-child:not([class]){color:rgba(0,0,0,.85);border-bottom:1px solid #dddddf;padding-bottom:8px;margin-top:0;padding-top:1rem;margin-bottom:1.25rem}
#toc{border-bottom:1px solid #e7e7e9;padding-bottom:.5em}
#toc>ul{margin-left:.125em}
#toc ul.sectlevel0>li>a{font-style:italic}
#toc ul.sectlevel0 ul.sectlevel1{margin:.5em 0}
#toc ul{font-family:"Open Sans","DejaVu Sans",sans-serif;list-style-type:none}
#toc li{line-height:1.3334;margin-top:.3334em}
#toc a{text-decoration:none}
#toc a:active{text-decoration:underline}
#toctitle{color:#7a2518;font-size:1.2em}
@media screen and (min-width:768px){#toctitle{font-size:1.375em}
body.toc2{padding-left:15em;padding-right:0}
body.toc2 #header>h1:nth-last-child(2){border-bottom:1px solid #dddddf;padding-bottom:8px}
#toc.toc2{margin-top:0!important;background:#f8f8f7;position:fixed;width:15em;left:0;top:0;border-right:1px solid #e7e7e9;border-top-width:0!important;border-bottom-width:0!important;z-index:1000;padding:1.25em 1em;height:100%;overflow:auto}
#toc.toc2 #toctitle{margin-top:0;margin-bottom:.8rem;font-size:1.2em}
#toc.toc2>ul{font-size:.9em;margin-bottom:0}
#toc.toc2 ul ul{margin-left:0;padding-left:1em}
#toc.toc2 ul.sectlevel0 ul.sectlevel1{padding-left:0;margin-top:.5em;margin-bottom:.5em}
body.toc2.toc-right{padding-left:0;padding-right:15em}
body.toc2.toc-right #toc.toc2{border-right-width:0;border-left:1px solid #e7e7e9;left:auto;right:0}}
@media screen and (min-width:1280px){body.toc2{padding-left:20em;padding-right:0}
#toc.toc2{width:20em}
#toc.toc2 #toctitle{font-size:1.375em}
#toc.toc2>ul{font-size:.95em}
#toc.toc2 ul ul{padding-left:1.25em}
body.toc2.toc-right{padding-left:0;padding-right:20em}}
#content #toc{border:1px solid #e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;border-radius:4px}
#content #toc>:first-child{margin-top:0}
#content #toc>:last-child{margin-bottom:0}
#footer{max-width:none;background:rgba(0,0,0,.8);padding:1.25em}
#footer-text{color:hsla(0,0%,100%,.8);line-height:1.44}
#content{margin-bottom:.625em}
.sect1{padding-bottom:.625em}
@media screen and (min-width:768px){#content{margin-bottom:1.25em}
.sect1{padding-bottom:1.25em}}
.sect1:last-child{padding-bottom:0}
.sect1+.sect1{border-top:1px solid #e7e7e9}
#content h1>a.anchor,h2>a.anchor,h3>a.anchor,#toctitle>a.anchor,.sidebarblock>.content>.title>a.anchor,h4>a.anchor,h5>a.anchor,h6>a.anchor{position:absolute;z-index:1001;width:1.5ex;margin-left:-1.5ex;display:block;text-decoration:none!important;visibility:hidden;text-align:center;font-weight:400}
#content h1>a.anchor::before,h2>a.anchor::before,h3>a.anchor::before,#toctitle>a.anchor::before,.sidebarblock>.content>.title>a.anchor::before,h4>a.anchor::before,h5>a.anchor::before,h6>a.anchor::before{content:"\00A7";font-size:.85em;display:block;padding-top:.1em}
#content h1:hover>a.anchor,#content h1>a.anchor:hover,h2:hover>a.anchor,h2>a.anchor:hover,h3:hover>a.anchor,#toctitle:hover>a.anchor,.sidebarblock>.content>.title:hover>a.anchor,h3>a.anchor:hover,#toctitle>a.anchor:hover,.sidebarblock>.content>.title>a.anchor:hover,h4:hover>a.anchor,h4>a.anchor:hover,h5:hover>a.anchor,h5>a.anchor:hover,h6:hover>a.anchor,h6>a.anchor:hover{visibility:visible}
#content h1>a.link,h2>a.link,h3>a.link,#toctitle>a.link,.sidebarblock>.content>.title>a.link,h4>a.link,h5>a.link,h6>a.link{color:#ba3925;text-decoration:none}
#content h1>a.link:hover,h2>a.link:hover,h3>a.link:hover,#toctitle>a.link:hover,.sidebarblock>.content>.title>a.link:hover,h4>a.link:hover,h5>a.link:hover,h6>a.link:hover{color:#a53221}
details,.audioblock,.imageblock,.literalblock,.listingblock,.stemblock,.videoblock{margin-bottom:1.25em}
details{margin-left:1.25rem}
details>summary{cursor:pointer;display:block;position:relative;line-height:1.6;margin-bottom:.625rem;outline:none;-webkit-tap-highlight-color:transparent}
details>summary::-webkit-details-marker{display:none}
details>summary::before{content:"";border:solid transparent;border-left:solid;border-width:.3em 0 .3em .5em;position:absolute;top:.5em;left:-1.25rem;transform:translateX(15%)}
details[open]>summary::before{border:solid transparent;border-top:solid;border-width:.5em .3em 0;transform:translateY(15%)}
details>summary::after{content:"";width:1.25rem;height:1em;position:absolute;top:.3em;left:-1.25rem}
.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{text-rendering:optimizeLegibility;text-align:left;font-family:"Noto Serif","DejaVu Serif",serif;font-size:1rem;font-style:italic}
table.tableblock.fit-content>caption.title{white-space:nowrap;width:0}
.paragraph.lead>p,#preamble>.sectionbody>[class=paragraph]:first-of-type p{font-size:1.21875em;line-height:1.6;color:rgba(0,0,0,.85)}
.admonitionblock>table{border-collapse:separate;border:0;background:none;width:100%}
.admonitionblock>table td.icon{text-align:center;width:80px}
.admonitionblock>table td.icon img{max-width:none}
.admonitionblock>table td.icon .title{font-weight:bold;font-family:"Open Sans","DejaVu Sans",sans-serif;text-transform:uppercase}
.admonitionblock>table td.content{padding-left:1.125em;padding-right:1.25em;border-left:1px solid #dddddf;color:rgba(0,0,0,.6);word-wrap:anywhere}
.admonitionblock>table td.content>:last-child>:last-child{margin-bottom:0}
.exampleblock>.content{border:1px solid #e6e6e6;margin-bottom:1.25em;padding:1.25em;background:#fff;border-radius:4px}
.sidebarblock{border:1px solid #dbdbd6;margin-bottom:1.25em;padding:1.25em;background:#f3f3f2;border-radius:4px}
.sidebarblock>.content>.title{color:#7a2518;margin-top:0;text-align:center}
.exampleblock>.content>:first-child,.sidebarblock>.content>:first-child{margin-top:0}
.exampleblock>.content>:last-child,.exampleblock>.content>:last-child>:last-child,.exampleblock>.content .olist>ol>li:last-child>:last-child,.exampleblock>.content .ulist>ul>li:last-child>:last-child,.exampleblock>.content .qlist>ol>li:last-child>:last-child,.sidebarblock>.content>:last-child,.sidebarblock>.content>:last-child>:last-child,.sidebarblock>.content .olist>ol>li:last-child>:last-child,.sidebarblock>.content .ulist>ul>li:last-child>:last-child,.sidebarblock>.content .qlist>ol>li:last-child>:last-child{margin-bottom:0}
.literalblock pre,.listingblock>.content>pre{border-radius:4px;overflow-x:auto;padding:1em;font-size:.8125em}
@media screen and (min-width:768px){.literalblock pre,.listingblock>.content>pre{font-size:.90625em}}
@media screen and (min-width:1280px){.literalblock pre,.listingblock>.content>pre{font-size:1em}}
.literalblock pre,.listingblock>.content>pre:not(.highlight),.listingblock>.content>pre[class=highlight],.listingblock>.content>pre[class^="highlight "]{background:#f7f7f8}
.literalblock.output pre{color:#f7f7f8;background:rgba(0,0,0,.9)}
.listingblock>.content{position:relative}
.listingblock code[data-lang]::before{display:none;content:attr(data-lang);position:absolute;font-size:.75em;top:.425rem;right:.5rem;line-height:1;text-transform:uppercase;color:inherit;opacity:.5}
.listingblock:hover code[data-lang]::before{display:block}
.listingblock.terminal pre .command::before{content:attr(data-prompt);padding-right:.5em;color:inherit;opacity:.5}
.listingblock.terminal pre .command:not([data-prompt])::before{content:"$"}
.listingblock pre.highlightjs{padding:0}
.listingblock pre.highlightjs>code{padding:1em;border-radius:4px}
.listingblock pre.prettyprint{border-width:0}
.prettyprint{background:#f7f7f8}
pre.prettyprint .linenums{line-height:1.45;margin-left:2em}
pre.prettyprint li{background:none;list-style-type:inherit;padding-left:0}
pre.prettyprint li code[data-lang]::before{opacity:1}
pre.prettyprint li:not(:first-child) code[data-lang]::before{display:none}
table.linenotable{border-collapse:separate;border:0;margin-bottom:0;background:none}
table.linenotable td[class]{color:inherit;vertical-align:top;padding:0;line-height:inherit;white-space:normal}
table.linenotable td.code{padding-left:.75em}
table.linenotable td.linenos,pre.pygments .linenos{border-right:1px solid;opacity:.35;padding-right:.5em;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
pre.pygments span.linenos{display:inline-block;margin-right:.75em}
.quoteblock{margin:0 1em 1.25em 1.5em;display:table}
.quoteblock:not(.excerpt)>.title{margin-left:-1.5em;margin-bottom:.75em}
.quoteblock blockquote,.quoteblock p{color:rgba(0,0,0,.85);font-size:1.15rem;line-height:1.75;word-spacing:.1em;letter-spacing:0;font-style:italic;text-align:justify}
.quoteblock blockquote{margin:0;padding:0;border:0}
.quoteblock blockquote::before{content:"\201c";float:left;font-size:2.75em;font-weight:bold;line-height:.6em;margin-left:-.6em;color:#7a2518;text-shadow:0 1px 2px rgba(0,0,0,.1)}
.quoteblock blockquote>.paragraph:last-child p{margin-bottom:0}
.quoteblock .attribution{margin-top:.75em;margin-right:.5ex;text-align:right}
.verseblock{margin:0 1em 1.25em}
.verseblock pre{font-family:"Open Sans","DejaVu Sans",sans-serif;font-size:1.15rem;color:rgba(0,0,0,.85);font-weight:300;text-rendering:optimizeLegibility}
.verseblock pre strong{font-weight:400}
.verseblock .attribution{margin-top:1.25rem;margin-left:.5ex}
.quoteblock .attribution,.verseblock .attribution{font-size:.9375em;line-height:1.45;font-style:italic}
.quoteblock .attribution br,.verseblock .attribution br{display:none}
.quoteblock .attribution cite,.verseblock .attribution cite{display:block;letter-spacing:-.025em;color:rgba(0,0,0,.6)}
.quoteblock.abstract blockquote::before,.quoteblock.excerpt blockquote::before,.quoteblock .quoteblock blockquote::before{display:none}
.quoteblock.abstract blockquote,.quoteblock.abstract p,.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{line-height:1.6;word-spacing:0}
.quoteblock.abstract{margin:0 1em 1.25em;display:block}
.quoteblock.abstract>.title{margin:0 0 .375em;font-size:1.15em;text-align:center}
.quoteblock.excerpt>blockquote,.quoteblock .quoteblock{padding:0 0 .25em 1em;border-left:.25em solid #dddddf}
.quoteblock.excerpt,.quoteblock .quoteblock{margin-left:0}
.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{color:inherit;font-size:1.0625rem}
.quoteblock.excerpt .attribution,.quoteblock .quoteblock .attribution{color:inherit;font-size:.85rem;text-align:left;margin-right:0}
p.tableblock:last-child{margin-bottom:0}
td.tableblock>.content{margin-bottom:1.25em;word-wrap:anywhere}
td.tableblock>.content>:last-child{margin-bottom:-1.25em}
table.tableblock,th.tableblock,td.tableblock{border:0 solid #dedede}
table.grid-all>*>tr>*{border-width:1px}
table.grid-cols>*>tr>*{border-width:0 1px}
table.grid-rows>*>tr>*{border-width:1px 0}
table.frame-all{border-width:1px}
table.frame-ends{border-width:1px 0}
table.frame-sides{border-width:0 1px}
table.frame-none>colgroup+*>:first-child>*,table.frame-sides>colgroup+*>:first-child>*{border-top-width:0}
table.frame-none>:last-child>:last-child>*,table.frame-sides>:last-child>:last-child>*{border-bottom-width:0}
table.frame-none>*>tr>:first-child,table.frame-ends>*>tr>:first-child{border-left-width:0}
table.frame-none>*>tr>:last-child,table.frame-ends>*>tr>:last-child{border-right-width:0}
table.stripes-all>*>tr,table.stripes-odd>*>tr:nth-of-type(odd),table.stripes-even>*>tr:nth-of-type(even),table.stripes-hover>*>tr:hover{background:#f8f8f7}
th.halign-left,td.halign-left{text-align:left}
th.halign-right,td.halign-right{text-align:right}
th.halign-center,td.halign-center{text-align:center}
th.valign-top,td.valign-top{vertical-align:top}
th.valign-bottom,td.valign-bottom{vertical-align:bottom}
th.valign-middle,td.valign-middle{vertical-align:middle}
table thead th,table tfoot th{font-weight:bold}
tbody tr th{background:#f7f8f7}
tbody tr th,tbody tr th p,tfoot tr th,tfoot tr th p{color:rgba(0,0,0,.8);font-weight:bold}
p.tableblock>code:only-child{background:none;padding:0}
p.tableblock{font-size:1em}
ol{margin-left:1.75em}
ul li ol{margin-left:1.5em}
dl dd{margin-left:1.125em}
dl dd:last-child,dl dd:last-child>:last-child{margin-bottom:0}
li p,ul dd,ol dd,.olist .olist,.ulist .ulist,.ulist .olist,.olist .ulist{margin-bottom:.625em}
ul.checklist,ul.none,ol.none,ul.no-bullet,ol.no-bullet,ol.unnumbered,ul.unstyled,ol.unstyled{list-style-type:none}
ul.no-bullet,ol.no-bullet,ol.unnumbered{margin-left:.625em}
ul.unstyled,ol.unstyled{margin-left:0}
li>p:empty:only-child::before{content:"";display:inline-block}
ul.checklist>li>p:first-child{margin-left:-1em}
ul.checklist>li>p:first-child>.fa-square-o:first-child,ul.checklist>li>p:first-child>.fa-check-square-o:first-child{width:1.25em;font-size:.8em;position:relative;bottom:.125em}
ul.checklist>li>p:first-child>input[type=checkbox]:first-child{margin-right:.25em}
ul.inline{display:flex;flex-flow:row wrap;list-style:none;margin:0 0 .625em -1.25em}
ul.inline>li{margin-left:1.25em}
.unstyled dl dt{font-weight:400;font-style:normal}
ol.arabic{list-style-type:decimal}
ol.decimal{list-style-type:decimal-leading-zero}
ol.loweralpha{list-style-type:lower-alpha}
ol.upperalpha{list-style-type:upper-alpha}
ol.lowerroman{list-style-type:lower-roman}
ol.upperroman{list-style-type:upper-roman}
ol.lowergreek{list-style-type:lower-greek}
.hdlist>table,.colist>table{border:0;background:none}
.hdlist>table>tbody>tr,.colist>table>tbody>tr{background:none}
td.hdlist1,td.hdlist2{vertical-align:top;padding:0 .625em}
td.hdlist1{font-weight:bold;padding-bottom:1.25em}
td.hdlist2{word-wrap:anywhere}
.literalblock+.colist,.listingblock+.colist{margin-top:-.5em}
.colist td:not([class]):first-child{padding:.4em .75em 0;line-height:1;vertical-align:top}
.colist td:not([class]):first-child img{max-width:none}
.colist td:not([class]):last-child{padding:.25em 0}
.thumb,.th{line-height:0;display:inline-block;border:4px solid #fff;box-shadow:0 0 0 1px #ddd}
.imageblock.left{margin:.25em .625em 1.25em 0}
.imageblock.right{margin:.25em 0 1.25em .625em}
.imageblock>.title{margin-bottom:0}
.imageblock.thumb,.imageblock.th{border-width:6px}
.imageblock.thumb>.title,.imageblock.th>.title{padding:0 .125em}
.image.left,.image.right{margin-top:.25em;margin-bottom:.25em;display:inline-block;line-height:0}
.image.left{margin-right:.625em}
.image.right{margin-left:.625em}
a.image{text-decoration:none;display:inline-block}
a.image object{pointer-events:none}
sup.footnote,sup.footnoteref{font-size:.875em;position:static;vertical-align:super}
sup.footnote a,sup.footnoteref a{text-decoration:none}
sup.footnote a:active,sup.footnoteref a:active,#footnotes .footnote a:first-of-type:active{text-decoration:underline}
#footnotes{padding-top:.75em;padding-bottom:.75em;margin-bottom:.625em}
#footnotes hr{width:20%;min-width:6.25em;margin:-.25em 0 .75em;border-width:1px 0 0}
#footnotes .footnote{padding:0 .375em 0 .225em;line-height:1.3334;font-size:.875em;margin-left:1.2em;margin-bottom:.2em}
#footnotes .footnote a:first-of-type{font-weight:bold;text-decoration:none;margin-left:-1.05em}
#footnotes .footnote:last-of-type{margin-bottom:0}
#content #footnotes{margin-top:-.625em;margin-bottom:0;padding:.75em 0}
div.unbreakable{page-break-inside:avoid}
.big{font-size:larger}
.small{font-size:smaller}
.underline{text-decoration:underline}
.overline{text-decoration:overline}
.line-through{text-decoration:line-through}
.aqua{color:#00bfbf}
.aqua-background{background:#00fafa}
.black{color:#000}
.black-background{background:#000}
.blue{color:#0000bf}
.blue-background{background:#0000fa}
.fuchsia{color:#bf00bf}
.fuchsia-background{background:#fa00fa}
.gray{color:#606060}
.gray-background{background:#7d7d7d}
.green{color:#006000}
.green-background{background:#007d00}
.lime{color:#00bf00}
.lime-background{background:#00fa00}
.maroon{color:#600000}
.maroon-background{background:#7d0000}
.navy{color:#000060}
.navy-background{background:#00007d}
.olive{color:#606000}
.olive-background{background:#7d7d00}
.purple{color:#600060}
.purple-background{background:#7d007d}
.red{color:#bf0000}
.red-background{background:#fa0000}
.silver{color:#909090}
.silver-background{background:#bcbcbc}
.teal{color:#006060}
.teal-background{background:#007d7d}
.white{color:#bfbfbf}
.white-background{background:#fafafa}
.yellow{color:#bfbf00}
.yellow-background{background:#fafa00}
span.icon>.fa{cursor:default}
a span.icon>.fa{cursor:inherit}
.admonitionblock td.icon [class^="fa icon-"]{font-size:2.5em;text-shadow:1px 1px 2px rgba(0,0,0,.5);cursor:default}
.admonitionblock td.icon .icon-note::before{content:"\f05a";color:#19407c}
.admonitionblock td.icon .icon-tip::before{content:"\f0eb";text-shadow:1px 1px 2px rgba(155,155,0,.8);color:#111}
.admonitionblock td.icon .icon-warning::before{content:"\f071";color:#bf6900}
.admonitionblock td.icon .icon-caution::before{content:"\f06d";color:#bf3400}
.admonitionblock td.icon .icon-important::before{content:"\f06a";color:#bf0000}
.conum[data-value]{display:inline-block;color:#fff!important;background:rgba(0,0,0,.8);border-radius:50%;text-align:center;font-size:.75em;width:1.67em;height:1.67em;line-height:1.67em;font-family:"Open Sans","DejaVu Sans",sans-serif;font-style:normal;font-weight:bold}
.conum[data-value] *{color:#fff!important}
.conum[data-value]+b{display:none}
.conum[data-value]::after{content:attr(data-value)}
pre .conum[data-value]{position:relative;top:-.125em}
b.conum *{color:inherit!important}
.conum:not([data-value]):empty{display:none}
dt,th.tableblock,td.content,div.footnote{text-rendering:optimizeLegibility}
h1,h2,p,td.content,span.alt,summary{letter-spacing:-.01em}
p strong,td.content strong,div.footnote strong{letter-spacing:-.005em}
p,blockquote,dt,td.content,td.hdlist1,span.alt,summary{font-size:1.0625rem}
p{margin-bottom:1.25rem}
.sidebarblock p,.sidebarblock dt,.sidebarblock td.content,p.tableblock{font-size:1em}
.exampleblock>.content{background:#fffef7;border-color:#e0e0dc;box-shadow:0 1px 4px #e0e0dc}
.print-only{display:none!important}
@page{margin:1.25cm .75cm}
@media print{*{box-shadow:none!important;text-shadow:none!important}
html{font-size:80%}
a{color:inherit!important;text-decoration:underline!important}
a.bare,a[href^="#"],a[href^="mailto:"]{text-decoration:none!important}
a[href^="http:"]:not(.bare)::after,a[href^="https:"]:not(.bare)::after{content:"(" attr(href) ")";display:inline-block;font-size:.875em;padding-left:.25em}
abbr[title]{border-bottom:1px dotted}
abbr[title]::after{content:" (" attr(title) ")"}
pre,blockquote,tr,img,object,svg{page-break-inside:avoid}
thead{display:table-header-group}
svg{max-width:100%}
p,blockquote,dt,td.content{font-size:1em;orphans:3;widows:3}
h2,h3,#toctitle,.sidebarblock>.content>.title{page-break-after:avoid}
#header,#content,#footnotes,#footer{max-width:none}
#toc,.sidebarblock,.exampleblock>.content{background:none!important}
#toc{border-bottom:1px solid #dddddf!important;padding-bottom:0!important}
body.book #header{text-align:center}
body.book #header>h1:first-child{border:0!important;margin:2.5em 0 1em}
body.book #header .details{border:0!important;display:block;padding:0!important}
body.book #header .details span:first-child{margin-left:0!important}
body.book #header .details br{display:block}
body.book #header .details br+span::before{content:none!important}
body.book #toc{border:0!important;text-align:left!important;padding:0!important;margin:0!important}
body.book #toc,body.book #preamble,body.book h1.sect0,body.book .sect1>h2{page-break-before:always}
.listingblock code[data-lang]::before{display:block}
#footer{padding:0 .9375em}
.hide-on-print{display:none!important}
.print-only{display:block!important}
.hide-for-print{display:none!important}
.show-for-print{display:inherit!important}}
@media amzn-kf8,print{#header>h1:first-child{margin-top:1.25rem}
.sect1{padding:0!important}
.sect1+.sect1{border:0}
#footer{background:none}
#footer-text{color:rgba(0,0,0,.6);font-size:.9em}}
@media amzn-kf8{#header,#content,#footnotes,#footer{padding:0}}
</style>
</head>
<body class="article">
<div id="header">
<h1>Bundle URIs</h1>
</div>
<div id="content">
<div id="preamble">
<div class="sectionbody">
<div class="paragraph">
<p>Git bundles are files that store a pack-file along with some extra metadata,
including a set of refs and a (possibly empty) set of necessary commits. See
<a href="../git-bundle.html">git-bundle(1)</a> and <a href="../gitformat-bundle.html">gitformat-bundle(5)</a> for more information.</p>
</div>
<div class="paragraph">
<p>Bundle URIs are locations where Git can download one or more bundles in
order to bootstrap the object database in advance of fetching the remaining
objects from a remote.</p>
</div>
<div class="paragraph">
<p>One goal is to speed up clones and fetches for users with poor network
connectivity to the origin server. Another benefit is to allow heavy users,
such as CI build farms, to use local resources for the majority of Git data
and thereby reducing the load on the origin server.</p>
</div>
<div class="paragraph">
<p>To enable the bundle URI feature, users can specify a bundle URI using
command-line options or the origin server can advertise one or more URIs
via a protocol v2 capability.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_design_goals">Design Goals</h2>
<div class="sectionbody">
<div class="paragraph">
<p>The bundle URI standard aims to be flexible enough to satisfy multiple
workloads. The bundle provider and the Git client have several choices in
how they create and consume bundle URIs.</p>
</div>
<div class="ulist">
<ul>
<li>
<p>Bundles can have whatever name the server desires. This name could refer
to immutable data by using a hash of the bundle contents. However, this
means that a new URI will be needed after every update of the content.
This might be acceptable if the server is advertising the URI (and the
server is aware of new bundles being generated) but would not be
ergonomic for users using the command line option.</p>
</li>
<li>
<p>The bundles could be organized specifically for bootstrapping full
clones, but could also be organized with the intention of bootstrapping
incremental fetches. The bundle provider must decide on one of several
organization schemes to minimize client downloads during incremental
fetches, but the Git client can also choose whether to use bundles for
either of these operations.</p>
</li>
<li>
<p>The bundle provider can choose to support full clones, partial clones,
or both. The client can detect which bundles are appropriate for the
repository’s partial clone filter, if any.</p>
</li>
<li>
<p>The bundle provider can use a single bundle (for clones only), or a
list of bundles. When using a list of bundles, the provider can specify
whether or not the client needs <em>all</em> of the bundle URIs for a full
clone, or if <em>any</em> one of the bundle URIs is sufficient. This allows the
bundle provider to use different URIs for different geographies.</p>
</li>
<li>
<p>The bundle provider can organize the bundles using heuristics, such as
creation tokens, to help the client prevent downloading bundles it does
not need. When the bundle provider does not provide these heuristics,
the client can use optimizations to minimize how much of the data is
downloaded.</p>
</li>
<li>
<p>The bundle provider does not need to be associated with the Git server.
The client can choose to use the bundle provider without it being
advertised by the Git server.</p>
</li>
<li>
<p>The client can choose to discover bundle providers that are advertised
by the Git server. This could happen during <code>git</code> <code>clone</code>, during
<code>git</code> <code>fetch</code>, both, or neither. The user can choose which combination
works best for them.</p>
</li>
<li>
<p>The client can choose to configure a bundle provider manually at any
time. The client can also choose to specify a bundle provider manually
as a command-line option to <code>git</code> <code>clone</code>.</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>Each repository is different and every Git server has different needs.
Hopefully the bundle URI feature is flexible enough to satisfy all needs.
If not, then the feature can be extended through its versioning mechanism.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_server_requirements">Server requirements</h2>
<div class="sectionbody">
<div class="paragraph">
<p>To provide a server-side implementation of bundle servers, no other parts
of the Git protocol are required. This allows server maintainers to use
static content solutions such as CDNs in order to serve the bundle files.</p>
</div>
<div class="paragraph">
<p>At the current scope of the bundle URI feature, all URIs are expected to
be HTTP(S) URLs where content is downloaded to a local file using a <code>GET</code>
request to that URL. The server could include authentication requirements
to those requests with the aim of triggering the configured credential
helper for secure access. (Future extensions could use "file://" URIs or
SSH URIs.)</p>
</div>
<div class="paragraph">
<p>Assuming a <code>200</code> <code>OK</code> response from the server, the content at the URL is
inspected. First, Git attempts to parse the file as a bundle file of
version 2 or higher. If the file is not a bundle, then the file is parsed
as a plain-text file using Git’s config parser. The key-value pairs in
that config file are expected to describe a list of bundle URIs. If
neither of these parse attempts succeed, then Git will report an error to
the user that the bundle URI provided erroneous data.</p>
</div>
<div class="paragraph">
<p>Any other data provided by the server is considered erroneous.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_bundle_lists">Bundle Lists</h2>
<div class="sectionbody">
<div class="paragraph">
<p>The Git server can advertise bundle URIs using a set of <code>key=value</code> pairs.
A bundle URI can also serve a plain-text file in the Git config format
containing these same <code>key=value</code> pairs. In both cases, we consider this
to be a <em>bundle list</em>. The pairs specify information about the bundles
that the client can use to make decisions for which bundles to download
and which to ignore.</p>
</div>
<div class="paragraph">
<p>A few keys focus on properties of the list itself.</p>
</div>
<div class="dlist">
<dl>
<dt class="hdlist1">bundle.version</dt>
<dd>
<p>(Required) This value provides a version number for the bundle
list. If a future Git change enables a feature that needs the Git
client to react to a new key in the bundle list file, then this version
will increment. The only current version number is 1, and if any other
value is specified then Git will fail to use this file.</p>
</dd>
<dt class="hdlist1">bundle.mode</dt>
<dd>
<p>(Required) This value has one of two values: <code>all</code> and <code>any</code>. When <code>all</code>
is specified, then the client should expect to need all of the listed
bundle URIs that match their repository’s requirements. When <code>any</code> is
specified, then the client should expect that any one of the bundle URIs
that match their repository’s requirements will suffice. Typically, the
<code>any</code> option is used to list a number of different bundle servers
located in different geographies.</p>
</dd>
<dt class="hdlist1">bundle.heuristic</dt>
<dd>
<p>If this string-valued key exists, then the bundle list is designed to
work well with incremental <code>git</code> <code>fetch</code> commands. The heuristic signals
that there are additional keys available for each bundle that help
determine which subset of bundles the client should download. The only
heuristic currently planned is <code>creationToken</code>.</p>
</dd>
</dl>
</div>
<div class="paragraph">
<p>The remaining keys include an <em><id></em> segment which is a server-designated
name for each available bundle. The <em><id></em> must contain only alphanumeric
and <code>-</code> characters.</p>
</div>
<div class="dlist">
<dl>
<dt class="hdlist1">bundle.<id>.uri</dt>
<dd>
<p>(Required) This string value is the URI for downloading bundle <em><id></em>.
If the URI begins with a protocol (<code>http://</code> or <code>https://</code>) then the URI
is absolute. Otherwise, the URI is interpreted as relative to the URI
used for the bundle list. If the URI begins with <code>/</code>, then that relative
path is relative to the domain name used for the bundle list. (This use
of relative paths is intended to make it easier to distribute a set of
bundles across a large number of servers or CDNs with different domain
names.)</p>
</dd>
<dt class="hdlist1">bundle.<id>.filter</dt>
<dd>
<p>This string value represents an object filter that should also appear in
the header of this bundle. The server uses this value to differentiate
different kinds of bundles from which the client can choose those that
match their object filters.</p>
</dd>
<dt class="hdlist1">bundle.<id>.creationToken</dt>
<dd>
<p>This value is a nonnegative 64-bit integer used for sorting the bundles
list. This is used to download a subset of bundles during a fetch when
<code>bundle.heuristic=creationToken</code>.</p>
</dd>
<dt class="hdlist1">bundle.<id>.location</dt>
<dd>
<p>This string value advertises a real-world location from where the bundle
URI is served. This can be used to present the user with an option for
which bundle URI to use or simply as an informative indicator of which
bundle URI was selected by Git. This is only valuable when
<code>bundle.mode</code> is <code>any</code>.</p>
</dd>
</dl>
</div>
<div class="paragraph">
<p>Here is an example bundle list using the Git config format:</p>
</div>
<div class="literalblock">
<div class="content">
<pre>[bundle]
version = 1
mode = all
heuristic = creationToken</pre>
</div>
</div>
<div class="literalblock">
<div class="content">
<pre>[bundle "2022-02-09-1644442601-daily"]
uri = https://bundles.example.com/git/git/2022-02-09-1644442601-daily.bundle
creationToken = 1644442601</pre>
</div>
</div>
<div class="literalblock">
<div class="content">
<pre>[bundle "2022-02-02-1643842562"]
uri = https://bundles.example.com/git/git/2022-02-02-1643842562.bundle
creationToken = 1643842562</pre>
</div>
</div>
<div class="literalblock">
<div class="content">
<pre>[bundle "2022-02-09-1644442631-daily-blobless"]
uri = 2022-02-09-1644442631-daily-blobless.bundle
creationToken = 1644442631
filter = blob:none</pre>
</div>
</div>
<div class="literalblock">
<div class="content">
<pre>[bundle "2022-02-02-1643842568-blobless"]
uri = /git/git/2022-02-02-1643842568-blobless.bundle
creationToken = 1643842568
filter = blob:none</pre>
</div>
</div>
<div class="paragraph">
<p>This example uses <code>bundle.mode=all</code> as well as the
<code>bundle.</code><em><id></em><code>.creationToken</code> heuristic. It also uses the <code>bundle.</code><em><id></em><code>.filter</code>
options to present two parallel sets of bundles: one for full clones and
another for blobless partial clones.</p>
</div>
<div class="paragraph">
<p>Suppose that this bundle list was found at the URI
<code>https://bundles.example.com/git/git/</code> and so the two blobless bundles have
the following fully-expanded URIs:</p>
</div>
<div class="ulist">
<ul>
<li>
<p><code>https://bundles.example.com/git/git/2022-02-09-1644442631-daily-blobless.bundle</code></p>
</li>
<li>
<p><code>https://bundles.example.com/git/git/2022-02-02-1643842568-blobless.bundle</code></p>
</li>
</ul>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_advertising_bundle_uris">Advertising Bundle URIs</h2>
<div class="sectionbody">
<div class="paragraph">
<p>If a user knows a bundle URI for the repository they are cloning, then
they can specify that URI manually through a command-line option. However,
a Git host may want to advertise bundle URIs during the clone operation,
helping users unaware of the feature.</p>
</div>
<div class="paragraph">
<p>The only thing required for this feature is that the server can advertise
one or more bundle URIs. This advertisement takes the form of a new
protocol v2 capability specifically for discovering bundle URIs.</p>
</div>
<div class="paragraph">
<p>The client could choose an arbitrary bundle URI as an option <em>or</em> select
the URI with best performance by some exploratory checks. It is up to the
bundle provider to decide if having multiple URIs is preferable to a
single URI that is geodistributed through server-side infrastructure.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_cloning_with_bundle_uris">Cloning with Bundle URIs</h2>
<div class="sectionbody">
<div class="paragraph">
<p>The primary need for bundle URIs is to speed up clones. The Git client
will interact with bundle URIs according to the following flow:</p>
</div>
<div class="olist arabic">
<ol class="arabic">
<li>
<p>The user specifies a bundle URI with the <code>--bundle-uri</code> command-line
option <em>or</em> the client discovers a bundle list advertised by the
Git server.</p>
</li>
<li>
<p>If the downloaded data from a bundle URI is a bundle, then the client
inspects the bundle headers to check that the prerequisite commit OIDs
are present in the client repository. If some are missing, then the
client delays unbundling until other bundles have been unbundled,
making those OIDs present. When all required OIDs are present, the
client unbundles that data using a refspec. The default refspec is
<code>+refs/heads/*:refs/bundles/*</code>, but this can be configured. These refs
are stored so that later <code>git</code> <code>fetch</code> negotiations can communicate each
bundled ref as a <code>have</code>, reducing the size of the fetch over the Git
protocol. To allow pruning refs from this ref namespace, Git may
introduce a numbered namespace (such as <code>refs/bundles/</code><em><i></em><code>/*</code>) such that
stale bundle refs can be deleted.</p>
</li>
<li>
<p>If the file is instead a bundle list, then the client inspects the
<code>bundle.mode</code> to see if the list is of the <code>all</code> or <code>any</code> form.</p>
<div class="olist loweralpha">
<ol class="loweralpha">
<li>
<p>If <code>bundle.mode=all</code>, then the client considers all bundle
URIs. The list is reduced based on the <code>bundle.</code><em><id></em><code>.filter</code> options
matching the client repository’s partial clone filter. Then, all
bundle URIs are requested. If the <code>bundle.</code><em><id></em><code>.creationToken</code>
heuristic is provided, then the bundles are downloaded in decreasing
order by the creation token, stopping when a bundle has all required
OIDs. The bundles can then be unbundled in increasing creation token
order. The client stores the latest creation token as a heuristic
for avoiding future downloads if the bundle list does not advertise
bundles with larger creation tokens.</p>
</li>
<li>
<p>If <code>bundle.mode=any</code>, then the client can choose any one of the
bundle URIs to inspect. The client can use a variety of ways to
choose among these URIs. The client can also fallback to another URI
if the initial choice fails to return a result.</p>
</li>
</ol>
</div>
</li>
</ol>
</div>
<div class="paragraph">
<p>Note that during a clone we expect that all bundles will be required, and
heuristics such as <code>bundle.</code><em><uri></em><code>.creationToken</code> can be used to download
bundles in chronological order or in parallel.</p>
</div>
<div class="paragraph">
<p>If a given bundle URI is a bundle list with a <code>bundle.heuristic</code>
value, then the client can choose to store that URI as its chosen bundle
URI. The client can then navigate directly to that URI during later <code>git</code>
<code>fetch</code> calls.</p>
</div>
<div class="paragraph">
<p>When downloading bundle URIs, the client can choose to inspect the initial
content before committing to downloading the entire content. This may
provide enough information to determine if the URI is a bundle list or
a bundle. In the case of a bundle, the client may inspect the bundle
header to determine that all advertised tips are already in the client
repository and cancel the remaining download.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_fetching_with_bundle_uris">Fetching with Bundle URIs</h2>
<div class="sectionbody">
<div class="paragraph">
<p>When the client fetches new data, it can decide to fetch from bundle
servers before fetching from the origin remote. This could be done via a
command-line option, but it is more likely useful to use a config value
such as the one specified during the clone.</p>
</div>
<div class="paragraph">
<p>The fetch operation follows the same procedure to download bundles from a
bundle list (although we do <em>not</em> want to use parallel downloads here). We
expect that the process will end when all prerequisite commit OIDs in a
thin bundle are already in the object database.</p>
</div>
<div class="paragraph">
<p>When using the <code>creationToken</code> heuristic, the client can avoid downloading
any bundles if their creation tokens are not larger than the stored
creation token. After fetching new bundles, Git updates this local
creation token.</p>
</div>
<div class="paragraph">
<p>If the bundle provider does not provide a heuristic, then the client
should attempt to inspect the bundle headers before downloading the full
bundle data in case the bundle tips already exist in the client
repository.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_error_conditions">Error Conditions</h2>
<div class="sectionbody">
<div class="paragraph">
<p>If the Git client discovers something unexpected while downloading
information according to a bundle URI or the bundle list found at that
location, then Git can ignore that data and continue as if it was not
given a bundle URI. The remote Git server is the ultimate source of truth,
not the bundle URI.</p>
</div>
<div class="paragraph">
<p>Here are a few example error conditions:</p>
</div>
<div class="ulist">
<ul>
<li>
<p>The client fails to connect with a server at the given URI or a connection
is lost without any chance to recover.</p>
</li>
<li>
<p>The client receives a 400-level response (such as <code>404</code> <code>Not</code> <code>Found</code> or
<code>401</code> <code>Not</code> <code>Authorized</code>). The client should use the credential helper to
find and provide a credential for the URI, but match the semantics of
Git’s other HTTP protocols in terms of handling specific 400-level
errors.</p>
</li>
<li>
<p>The server reports any other failure response.</p>
</li>
<li>
<p>The client receives data that is not parsable as a bundle or bundle list.</p>
</li>
<li>
<p>A bundle includes a filter that does not match expectations.</p>
</li>
<li>
<p>The client cannot unbundle the bundles because the prerequisite commit OIDs
are not in the object database and there are no more bundles to download.</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>There are also situations that could be seen as wasteful, but are not
error conditions:</p>
</div>
<div class="ulist">
<ul>
<li>
<p>The downloaded bundles contain more information than is requested by
the clone or fetch request. A primary example is if the user requests
a clone with <code>--single-branch</code> but downloads bundles that store every
reachable commit from all <code>refs/heads/*</code> references. This might be
initially wasteful, but perhaps these objects will become reachable by
a later ref update that the client cares about.</p>
</li>
<li>
<p>A bundle download during a <code>git</code> <code>fetch</code> contains objects already in the
object database. This is probably unavoidable if we are using bundles
for fetches, since the client will almost always be slightly ahead of
the bundle servers after performing its "catch-up" fetch to the remote
server. This extra work is most wasteful when the client is fetching
much more frequently than the server is computing bundles, such as if
the client is using hourly prefetches with background maintenance, but
the server is computing bundles weekly. For this reason, the client
should not use bundle URIs for fetch unless the server has explicitly
recommended it through a <code>bundle.heuristic</code> value.</p>
</li>
</ul>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_example_bundle_provider_organization">Example Bundle Provider organization</h2>
<div class="sectionbody">
<div class="paragraph">
<p>The bundle URI feature is intentionally designed to be flexible to
different ways a bundle provider wants to organize the object data.
However, it can be helpful to have a complete organization model described
here so providers can start from that base.</p>
</div>
<div class="paragraph">
<p>This example organization is a simplified model of what is used by the
GVFS Cache Servers (see section near the end of this document) which have
been beneficial in speeding up clones and fetches for very large
repositories, although using extra software outside of Git.</p>
</div>
<div class="paragraph">
<p>The bundle provider deploys servers across multiple geographies. Each
server manages its own bundle set. The server can track a number of Git
repositories, but provides a bundle list for each based on a pattern. For
example, when mirroring a repository at <code>https://</code><em><domain></em><code>/</code><em><org></em><code>/</code><em><repo></em>
the bundle server could have its bundle list available at
<code>https://</code><em><server-url></em><code>/</code><em><domain></em><code>/</code><em><org></em><code>/</code><em><repo></em>. The origin Git server can
list all of these servers under the "any" mode:</p>
</div>
<div class="literalblock">
<div class="content">
<pre>[bundle]
version = 1
mode = any</pre>
</div>
</div>
<div class="literalblock">
<div class="content">
<pre>[bundle "eastus"]
uri = https://eastus.example.com/<domain>/<org>/<repo></pre>
</div>
</div>
<div class="literalblock">
<div class="content">
<pre>[bundle "europe"]
uri = https://europe.example.com/<domain>/<org>/<repo></pre>
</div>
</div>
<div class="literalblock">
<div class="content">
<pre>[bundle "apac"]
uri = https://apac.example.com/<domain>/<org>/<repo></pre>
</div>
</div>
<div class="paragraph">
<p>This "list of lists" is static and only changes if a bundle server is
added or removed.</p>
</div>
<div class="paragraph">
<p>Each bundle server manages its own set of bundles. The initial bundle list
contains only a single bundle, containing all of the objects received from
cloning the repository from the origin server. The list uses the
<code>creationToken</code> heuristic and a <code>creationToken</code> is made for the bundle
based on the server’s timestamp.</p>
</div>
<div class="paragraph">
<p>The bundle server runs regularly-scheduled updates for the bundle list,
such as once a day. During this task, the server fetches the latest
contents from the origin server and generates a bundle containing the
objects reachable from the latest origin refs, but not contained in a
previously-computed bundle. This bundle is added to the list, with care
that the <code>creationToken</code> is strictly greater than the previous maximum
<code>creationToken</code>.</p>
</div>
<div class="paragraph">
<p>When the bundle list grows too large, say more than 30 bundles, then the
oldest "<em>N</em> minus 30" bundles are combined into a single bundle. This
bundle’s <code>creationToken</code> is equal to the maximum <code>creationToken</code> among the
merged bundles.</p>
</div>
<div class="paragraph">
<p>An example bundle list is provided here, although it only has two daily
bundles and not a full list of 30:</p>
</div>
<div class="literalblock">
<div class="content">
<pre>[bundle]
version = 1
mode = all
heuristic = creationToken</pre>
</div>