{"thread":{"id":"56220","subject":"[RFC PATCH 00/13] Add bundle-uri: resumably clones, static \"dumb\" CDN etc.","startedAt":"2021-08-05T15:07:39Z","lastAt":"2021-08-24T22:39:42Z","messageCount":26,"participants":["Ævar Arnfjörð Bjarmason","Jonathan Nieder","Derrick Stolee","brian m. carlson"],"isPatch":true,"patchVersion":1,"patchTotal":13},"messages":[{"id":"432104","messageId":"RFC-cover-00.13-0000000000-20210805T150534Z-avarab@gmail.com","threadId":"56220","inReplyTo":null,"subject":"[RFC PATCH 00/13] Add bundle-uri: resumably clones, static \"dumb\" CDN etc.","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-08-05T15:07:16Z","receivedAt":"2021-08-05T15:07:39Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"We're in the 2.33.0 rc cycle, and I'd hoped to have some more prep\nwork for this integrated already, but for now here's something\ninteresting I've been working on for early commentary/feedback.\n\nThis adds the the ability to protocol v2 for servers to optimistically\npre-seed supporting clients with one or more bundles via a new\n\"bundle-uri\" protocol extension.\n\nRight now only \"clone\" supports this, but it's a rather easy change on\ntop to add incremental \"fetch\" support as well.\n\nThe elevator pitch for this feature is (I guess it's a long elevator\nride..);\n\n * Allows for offloading most/all of a PACK \"fetch\" to \"dumb\" CDN's\n   that *don't* have very close coordination with the server running\n   \"git-upload-pack\" (unlike packfile-uri, more on that below).\n\n   I.e. distributing an up-to-date-enough bundle via something like\n   Debian's FTP mirror system, or a best-effort occasionally updated\n   CDN.\n\n   Should the bundle(s) be outdated, corrupt or whatever the client\n   gracefully recovers by either ignoring the bad data, or catching up\n   via negotiation with whatever data it did get.\n\n   Server operators should be confident in using bundle URIs, even if\n   the CDN they're pointing to is flaky, out of date, or even\n   sometimes outright broken or unreachable. The client will recover\n   in all those cases.\n\n * Makes performant git infrastructure more accessible, i.e. this\n   feature helps the last with an up-to-date repack with up-to-date\n   bitmaps when talking to a network-local git server, but a lot of\n   users have more option for scaling or distributing things via dumb\n   CDNs than a server that can run \"git-upload-pack\".\n\n * You can even bootstrap a clone of a remote server that doesn't\n   support bundle-uri with a local or remote bundle with the\n   \"transfer.injectBundleURI\" config.\n\n * Trivial path to resumable clones. Note that that's \"resumable\" in\n   the sense that curl(1) will resume a partially downloaded bundle,\n   we don't resume whatever state index-pack was in when the\n   connection was broken.\n\n   I have a POC of this working locally, it's just a matter of\n   invoking curl(1) with \"--continue-at -\".\n\n   The hindrance for resumably clones is now just the UI for git-clone\n   (i.e. stashing the partial data somewhere), not protocol\n   limitations.\n\nThis goes on top of the outstanding series I have for serve.c API\ncleanup & fixes at\nhttps://lore.kernel.org/git/cover-v4-00.10-00000000000-20210805T011823Z-avarab@gmail.com\n\nI also needed to grab one patch from my \"bundle unbundle progress\"\nseries:\nhttps://lore.kernel.org/git/cover-0.4-0000000000-20210727T004015Z-avarab@gmail.com/\n\nSomething like this approach had been suggested before in late 2011 by\nJeff King, see:\nhttps://lore.kernel.org/git/20111110074330.GA27925@sigill.intra.peff.net/;\nThere's significant differences in the approach, mainly due to\nprotocol v2 not existing at the time. I wrote most of this before\nfinding/seeing Jeff's earlier patches.\n\nFor a demo of how this works head over to 12/13:\nhttps://lore.kernel.org/git/RFC-patch-12.13-8dc5613e87-20210805T150534Z-avarab@gmail.com\n\nIn 13/13 there's a design doc discussing the approach, and major\ndifferences with the existing packfile-uri mechanism:\nhttps://lore.kernel.org/git/RFC-patch-13.13-1e657ed27a-20210805T150534Z-avarab@gmail.com\n\nThis can also be grabbed from the \"avar/bundle-uri-client-clone\"\nbranch of https://github.com/avar/git/\n\nÆvar Arnfjörð Bjarmason (13):\n  serve: add command to advertise bundle URIs\n  bundle-uri client: add \"bundle-uri\" parsing + tests\n  connect.c: refactor sending of agent & object-format\n  bundle-uri client: add minimal NOOP client\n  bundle-uri client: add \"git ls-remote-bundle-uri\"\n  bundle-uri client: add transfer.injectBundleURI support\n  bundle-uri client: add boolean transfer.bundleURI setting\n  bundle.h: make \"fd\" version of read_bundle_header() public\n  fetch-pack: add a deref_without_lazy_fetch_extended()\n  fetch-pack: move --keep=* option filling to a function\n  index-pack: add --progress-title option\n  bundle-uri client: support for bundle-uri with \"clone\"\n  bundle-uri docs: add design notes\n\n Documentation/config/transfer.txt          |  26 ++\n Documentation/git-index-pack.txt           |   6 +\n Documentation/git-ls-remote-bundle-uri.txt |  63 +++\n Documentation/git-ls-remote.txt            |   1 +\n Documentation/technical/bundle-uri.txt     | 119 ++++++\n Documentation/technical/protocol-v2.txt    | 145 +++++++\n Makefile                                   |   3 +\n builtin.h                                  |   1 +\n builtin/clone.c                            |   7 +\n builtin/index-pack.c                       |   6 +\n builtin/ls-remote-bundle-uri.c             |  90 +++++\n bundle-uri.c                               | 151 ++++++++\n bundle-uri.h                               |  30 ++\n bundle.c                                   |   8 +-\n bundle.h                                   |   2 +\n command-list.txt                           |   1 +\n connect.c                                  |  80 +++-\n fetch-pack.c                               | 304 ++++++++++++++-\n fetch-pack.h                               |   6 +\n git.c                                      |   1 +\n remote.h                                   |   4 +\n serve.c                                    |   6 +\n t/helper/test-bundle-uri.c                 |  80 ++++\n t/helper/test-tool.c                       |   1 +\n t/helper/test-tool.h                       |   1 +\n t/lib-t5730-protocol-v2-bundle-uri.sh      | 425 +++++++++++++++++++++\n t/t5701-git-serve.sh                       | 124 +++++-\n t/t5730-protocol-v2-bundle-uri-file.sh     |  36 ++\n t/t5731-protocol-v2-bundle-uri-git.sh      |  17 +\n t/t5732-protocol-v2-bundle-uri-http.sh     |  17 +\n t/t5750-bundle-uri-parse.sh                |  98 +++++\n transport-helper.c                         |  13 +\n transport-internal.h                       |   7 +\n transport.c                                | 120 ++++++\n transport.h                                |  22 ++\n 35 files changed, 1988 insertions(+), 33 deletions(-)\n create mode 100644 Documentation/git-ls-remote-bundle-uri.txt\n create mode 100644 Documentation/technical/bundle-uri.txt\n create mode 100644 builtin/ls-remote-bundle-uri.c\n create mode 100644 bundle-uri.c\n create mode 100644 bundle-uri.h\n create mode 100644 t/helper/test-bundle-uri.c\n create mode 100644 t/lib-t5730-protocol-v2-bundle-uri.sh\n create mode 100755 t/t5730-protocol-v2-bundle-uri-file.sh\n create mode 100755 t/t5731-protocol-v2-bundle-uri-git.sh\n create mode 100755 t/t5732-protocol-v2-bundle-uri-http.sh\n create mode 100755 t/t5750-bundle-uri-parse.sh\n\n-- \n2.33.0.rc0.646.g585563e77f\n\n"},{"id":"432105","messageId":"RFC-patch-01.13-4e1a0dbef5-20210805T150534Z-avarab@gmail.com","threadId":"56220","inReplyTo":"RFC-cover-00.13-0000000000-20210805T150534Z-avarab@gmail.com","subject":"[RFC PATCH 01/13] serve: add command to advertise bundle URIs","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-08-05T15:07:17Z","receivedAt":"2021-08-05T15:07:41Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"When the uploadpack.bundleURI config is set to a URI (or URIs, if set\n>1 times), advertise a \"bundle-uri\" command, then when the client\nrequests \"bundle-uri\" emit those URIs back at them.\n\nThe client CAN then request those URIs out of bounds, and after\nthey've done (after either disconnecting & coming back, or leaving us\nhanging), proceed with the rest of request flow. I.e. issuing a\n\"ls-refs\" followed by a \"fetch\". The client MAY then send us \"have\"\nlines with the tips they've unpacked from their newly acquired\nbundle(s).\n\nThis commit doesn't implement any of that required client behavior,\nonly the trivial server behavior of spewing a list of URLs at the\nclient on request.\n\nThere is already a uploadpack.blobPackfileUri setting for the server,\nso why is this needed? The Documentation/technical/bundle-uri.txt\nadded in a preceding commit discusses that in more detail, but in\nsummary:\n\n 1. There is no \"real\" support for in git.git. The\n    uploadpack.blobPackfileUri setting allows carving out a list of\n    blobs (actually any OIDs), but as alluded to in bfc2a36ff2a (Doc:\n    clarify contents of packfile sent as URI, 2021-01-20) the only\n    \"real\" implementation is JGit based.\n\n 2. The uploadpack.blobPackfileUri is a MUST where this is a\n    \"CAN\". I.e. once a client says they support packfile-uri of given\n    list of protocols the server will send them a PACK response\n    assuming they've downloaded the URI they client was sent, if the\n    client doesn't do that they don't have a valid repository.\n\n    Pointing at a bundle and having the client send us \"have\"\n    lines (or not, maybe they couldn't fetch it, or decided they\n    didn't want to) is more flexible, and can gracefully recover\n    e.g. if the CDN isn't reachable (maybe you do support \"https\", but\n    the CDN provider is down, or blocked your whole country).\n\n 3. Because of the disconnect in #2 \"dumb\" servers can seed\n    pre-clients, e.g. we might point to a repo.bundle whose exact\n    state we aren't sure of (a cronjob updates it, sometimes). The\n    client will discover its contents, and give us the \"have\" lines,\n    the \"packfile-uri\" method effectively requires the server to have\n    those exact \"have\" lines (or rather, it will produce a similar\n    PACK using give-or-take the same exclusions).\n\n 4. This provides an easy way to the long sought after \"resumable\n    clones\". I.e. since we can assume that it's in the server's\n    interest to keep their bundle(s) as up-to-date as possible, most\n    or all of the history we need to fetch will be in the bundle. If\n    we fail midway through the \"clone\" we can offload the problem of\n    resuming to wget/curl/rsync/whatever, instead of (as has been\n    suggested, but not implemented for the \"normal\" dialog)\n    \"repairing\" a partial PACK response or something.\n\nThere was a suggestion of implementing a similar feature long ago[1]\nby Jeff King. The main difference between it and this approach is that\nwe've since gained protocol v2, so we can add this as an optional path\nin the dialog between client and server. The 2011 implementation\nhooked into the transport mechanism to try to clone from a bundle\ndirectly. See also [2] and [3] for some later mentions of that\napproach.\n\nSee also [4] for the series that implemented\nuploadpack.blobPackfileUri, and [5] for a series on top that did the\n.gitmodules check in that context. See [6] for the \"ls-refs unborn\"\nfeature which modified code in similar areas of the request flow.\n\n1. https://lore.kernel.org/git/20111110074330.GA27925@sigill.intra.peff.net/\n2. https://lore.kernel.org/git/20190514092900.GA11679@sigill.intra.peff.net/\n3. https://lore.kernel.org/git/YFJWz5yIGng+a16k@coredump.intra.peff.net/\n4. https://lore.kernel.org/git/cover.1591821067.git.jonathantanmy@google.com/\n   Merged as 34e849b05a4 (Merge branch 'jt/cdn-offload', 2020-06-25)\n5. https://lore.kernel.org/git/cover.1614021092.git.jonathantanmy@google.com/\n   Merged as 6ee353d42f3 (Merge branch 'jt/transfer-fsck-across-packs',\n   2021-03-01)\n6. 69571dfe219 (Merge branch 'jt/clone-unborn-head', 2021-02-17)\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n Documentation/technical/protocol-v2.txt | 140 ++++++++++++++++++++++++\n Makefile                                |   1 +\n bundle-uri.c                            |  65 +++++++++++\n bundle-uri.h                            |  14 +++\n serve.c                                 |   6 +\n t/t5701-git-serve.sh                    | 124 ++++++++++++++++++++-\n 6 files changed, 349 insertions(+), 1 deletion(-)\n create mode 100644 bundle-uri.c\n create mode 100644 bundle-uri.h\n\ndiff --git a/Documentation/technical/protocol-v2.txt b/Documentation/technical/protocol-v2.txt\nindex 213538f1d0..d10d5e9ef6 100644\n--- a/Documentation/technical/protocol-v2.txt\n+++ b/Documentation/technical/protocol-v2.txt\n@@ -556,3 +556,143 @@ and associated requested information, each separated by a single space.\n \tattr = \"size\"\n \n \tobj-info = obj-id SP obj-size\n+\n+bundle-uri\n+~~~~~~~~~~\n+\n+If the 'bundle-uri' capability is advertised, the server supports the\n+`bundle-uri' command.\n+\n+The capability is currently advertised with no value (i.e. not\n+\"bundle-uri=somevalue\"), a value may be added in the future for\n+supporting command-wide extensions. Clients MUST ignore any unknown\n+capability values and proceed with the 'bundle-uri` dialog they\n+support.\n+\n+The 'bundle-uri' command is intended to be issued before `fetch` to\n+get URIs to bundle files (see linkgit:git-bundle[1]) to \"seed\" and\n+inform the subsequent `fetch` command.\n+\n+The client CAN issue `bundle-uri` before or after any other valid\n+command. It's expected that it'll be issued after an `ls-refs` and\n+before `fetch`.\n+\n+DISCUSSION of bundle-uri\n+^^^^^^^^^^^^^^^^^^^^^^^^\n+\n+The intent of the feature is optimize for server resource consumption\n+in the common case by changing the common case of fetching a very\n+large PACK during linkgit:git-clone[1] into a smaller incremental\n+fetch.\n+\n+It also allows servers to achieve better caching in combination with\n+an `uploadpack.packObjectsHook` (see linkgit:git-config[1]).\n+\n+By having new clones or fetches be a more predictable and common\n+negotiation against the tips of recently produces *.bundle file(s).\n+Servers might even pre-generate the results of such negotiations for\n+the `uploadpack.packObjectsHook` as new pushes come in.\n+\n+I.e. the server would anticipate that fresh clones will download a\n+known bundle, followed by catching up to the current state of the\n+repository using ref tips found in that bundle (or bundles).\n+\n+PROTOCOL for bundle-uri\n+^^^^^^^^^^^^^^^^^^^^^^^\n+\n+A `bundle-uri` request takes no arguments, and as noted above does not\n+currently advertise a capability value. Both may be added in the\n+future.\n+\n+When the client issues a `command=bundle-uri` the response is a list\n+of URIs the server would like the client to fetch out-of-bounds before\n+proceeding with the `fetch` request in this format:\n+\n+\toutput = bundle-uri-line\n+\t\t bundle-uri-line* flush-pkt\n+\n+\tbundle-uri-line = PKT-LINE(bundle-uri)\n+\t\t\t  *(SP bundle-feature-key *(=bundle-feature-val))\n+\t\t\t  LF\n+\n+\tbundle-uri = A URI such as a https://, ssh:// etc. URI\n+\n+\tbundle-feature-key = Any printable ASCII characters except SP or \"=\"\n+\tbundle-feature-val = Any printable ASCII characters except SP or \"=\"\n+\n+No `bundle-feature-key`=`bundle-feature-value` fields are currently\n+defined. See the discussion of features below.\n+\n+bundle-uri CLIENT AND SERVER EXPECTATIONS\n+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n+\n+The advertised bundles MUST contain one or more reference tips for use\n+by the client. Bundles that are not self-contained MUST use the\n+standard \"-\" prefixes in the bundle format to indicate their\n+prerequisites. I.e. they must be in the standard format \"git bundle\n+create\" would create.\n+\n+If after an `ls-refs` the client finds that the ref tips it wants can\n+be retrieved entirety from advertised bundle(s), it MAY\n+disconnect. The results of such a \"clone\" or \"fetch\" should be\n+indistinguishable from the state attained without using bundle-uri.\n+\n+The client MAY also keep the connection open pending download of the\n+bundle-uris, e.g. should on or more downloads (or their validation)\n+fail.\n+\n+The client MAY provide reference tips found in the bundle(s) to be\n+downloaded out-of-bounds as `have` lines in the `fetch` request. They\n+MAY also ignore the bundle(s) entirely (e.g. if they can't be\n+downloaded) or some combination of the two.\n+\n+For the convenience of clients bundles SHOULD be provided in the order\n+that they must be unpacked in if processed one-at-a-time by a dumber client.\n+\n+That usually means a \"big bundle\" first with most of the history\n+that's self-contained, optionally followed by incremental updates on\n+that \"big bundle\".\n+\n+This ordering is a mere convention and not a MUST, e.g. a repository\n+with N branches with disconnected histories might have N \"big\n+bundles\", each with their own self-contained history. A server might\n+also only provide \"incremental updates\".\n+\n+A client MUST consider the content of the bundles themselves and their\n+header as the ultimate source of truth. Servers MAY be tolerant of\n+simpler clients by using the convention outlined above.\n+\n+As noted before a client MUST gracefully degrade on errors, whether\n+that error is because of bad missing/data in the bundle URI(s), or\n+because that client is too dumb to e.g. understand and fully parse out\n+bundle headers and their prerequisite relationships.\n+\n+bundle-uri PROTOCOL FEATURES\n+^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n+\n+As noted above no `bundle-feature-key`=`bundle-feature-value` fields\n+are currently defined.\n+\n+They are intended for future per-URI metadata which older clients MUST\n+ignore and gracefully degrade on. Any fields they do recognize they\n+CAN also ignore.\n+\n+Any backwards-incompatible addition of pre-URI key-value will be\n+guarded by a new value or values in 'bundle-uri' capability\n+advertisement itself, and/or by new future `bundle-uri` request\n+arguments.\n+\n+While no per-URI key-value are currently supported currently they're\n+intended to support future features such as:\n+\n+ * Add a \"hash=<val>\" or \"size=<bytes>\" advertise the expected hash or\n+   size of the bundle file.\n+\n+ * Advertise that one or more bundle files are the same (to e.g. have\n+   clients round-robin or otherwise choose one of N possible files).\n+\n+ * A \"tip=<OID>\" shortcut. A client who'd have the advertised <OID>\n+   would know there was no need to download the relevant bundle(s),\n+   they've got that OID already (for multi-tips the client would need\n+   to fetch the bundle, or do e.g. HTTP range requests to get its\n+   header).\ndiff --git a/Makefile b/Makefile\nindex 9573190f1d..877c6c47b6 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -850,6 +850,7 @@ LIB_OBJS += blob.o\n LIB_OBJS += bloom.o\n LIB_OBJS += branch.o\n LIB_OBJS += bulk-checkin.o\n+LIB_OBJS += bundle-uri.o\n LIB_OBJS += bundle.o\n LIB_OBJS += cache-tree.o\n LIB_OBJS += cbtree.o\ndiff --git a/bundle-uri.c b/bundle-uri.c\nnew file mode 100644\nindex 0000000000..2d93e8b003\n--- /dev/null\n+++ b/bundle-uri.c\n@@ -0,0 +1,65 @@\n+#include \"cache.h\"\n+#include \"bundle-uri.h\"\n+#include \"pkt-line.h\"\n+#include \"config.h\"\n+\n+/**\n+ * serve.[ch] API.\n+ */\n+\n+/*\n+ * \"uploadpack.bundleURI\" is advertised only if there's URIs to serve\n+ * up per the config.\n+ */\n+static int advertise_bundle_uri = -1;\n+\n+static void send_bundle_uris(struct packet_writer *writer,\n+\t\t\t     struct string_list *uris)\n+{\n+\tstruct string_list_item *item;\n+\tfor_each_string_list_item(item, uris) {\n+\t\tconst char *uri = item->string;\n+\n+\t\tpacket_writer_write(writer, \"%s\", uri);\n+\t}\n+}\n+\n+static struct string_list bundle_uris = STRING_LIST_INIT_DUP;\n+\n+static int bundle_uri_startup_config(const char *var, const char *value,\n+\t\t\t\t     void *data)\n+{\n+\tif (!strcmp(var, \"uploadpack.bundleuri\")) {\n+\t\tadvertise_bundle_uri = 1;\n+\t\tstring_list_append(&bundle_uris, value);\n+\t}\n+\treturn 0;\n+}\n+\n+int bundle_uri_advertise(struct repository *r, struct strbuf *value)\n+{\n+\tif (advertise_bundle_uri == -1) {\n+\t\tgit_config(bundle_uri_startup_config, NULL);\n+\t\tif (advertise_bundle_uri == -1)\n+\t\t\tadvertise_bundle_uri = 0;\n+\t}\n+\treturn advertise_bundle_uri;\n+}\n+\n+int bundle_uri_command(struct repository *r,\n+\t\t       struct packet_reader *request)\n+{\n+\tstruct packet_writer writer;\n+\tpacket_writer_init(&writer, 1);\n+\n+\twhile (packet_reader_read(request) == PACKET_READ_NORMAL)\n+\t\tdie(\"bundle-uri: unexpected argument: '%s'\", request->line);\n+\tif (request->status != PACKET_READ_FLUSH)\n+\t\tdie(\"bundle-uri: expected flush after arguments\");\n+\n+\tsend_bundle_uris(&writer, &bundle_uris);\n+\n+\tpacket_writer_flush(&writer);\n+\n+\treturn 0;\n+}\ndiff --git a/bundle-uri.h b/bundle-uri.h\nnew file mode 100644\nindex 0000000000..6a40efeb39\n--- /dev/null\n+++ b/bundle-uri.h\n@@ -0,0 +1,14 @@\n+#ifndef BUNDLE_URI_H\n+#define BUNDLE_URI_H\n+\n+struct repository;\n+struct packet_reader;\n+struct packet_writer;\n+\n+/**\n+ * serve.[ch] API.\n+ */\n+int bundle_uri_advertise(struct repository *r, struct strbuf *value);\n+int bundle_uri_command(struct repository *r, struct packet_reader *request);\n+\n+#endif /* BUNDLE_URI_H */\ndiff --git a/serve.c b/serve.c\nindex 1817edc7f5..789bf5fc38 100644\n--- a/serve.c\n+++ b/serve.c\n@@ -8,6 +8,7 @@\n #include \"protocol-caps.h\"\n #include \"serve.h\"\n #include \"upload-pack.h\"\n+#include \"bundle-uri.h\"\n \n static int advertise_sid = -1;\n \n@@ -104,6 +105,11 @@ static struct protocol_capability capabilities[] = {\n \t\t.advertise = always_advertise,\n \t\t.command = cap_object_info,\n \t},\n+\t{\n+\t\t.name = \"bundle-uri\",\n+\t\t.advertise = bundle_uri_advertise,\n+\t\t.command = bundle_uri_command,\n+\t},\n };\n \n void protocol_v2_advertise_capabilities(void)\ndiff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh\nindex 930721f053..21d5314d83 100755\n--- a/t/t5701-git-serve.sh\n+++ b/t/t5701-git-serve.sh\n@@ -12,7 +12,7 @@ test_expect_success 'test capability advertisement' '\n \twrong_algo sha1:sha256\n \twrong_algo sha256:sha1\n \tEOF\n-\tcat >expect <<-EOF &&\n+\tcat >expect.base <<-EOF &&\n \tversion 2\n \tagent=git/$(git version | cut -d\" \" -f3)\n \tls-refs=unborn\n@@ -20,8 +20,11 @@ test_expect_success 'test capability advertisement' '\n \tserver-option\n \tobject-format=$(test_oid algo)\n \tobject-info\n+\tEOF\n+\tcat >expect.trailer <<-EOF &&\n \t0000\n \tEOF\n+\tcat expect.base expect.trailer >expect &&\n \n \tGIT_TEST_SIDEBAND_ALL=0 test-tool serve-v2 \\\n \t\t--advertise-capabilities >out &&\n@@ -266,4 +269,123 @@ test_expect_success 'basics of object-info' '\n \ttest_cmp expect actual\n '\n \n+# Test the basics of bundle-uri\n+#\n+test_expect_success 'test capability advertisement with uploadpack.bundleURI' '\n+\ttest_config uploadpack.bundleURI FAKE &&\n+\n+\tcat >expect.extra <<-EOF &&\n+\tbundle-uri\n+\tEOF\n+\tcat expect.base \\\n+\t    expect.extra \\\n+\t    expect.trailer >expect &&\n+\n+\tGIT_TEST_SIDEBAND_ALL=0 test-tool serve-v2 \\\n+\t\t--advertise-capabilities >out &&\n+\ttest-tool pkt-line unpack <out >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'basics of bundle-uri: dies if not enabled' '\n+\ttest-tool pkt-line pack >in <<-EOF &&\n+\tcommand=bundle-uri\n+\t0000\n+\tEOF\n+\n+\tcat >err.expect <<-\\EOF &&\n+\tfatal: invalid command '\"'\"'bundle-uri'\"'\"'\n+\tEOF\n+\n+\tcat >expect <<-\\EOF &&\n+\tERR serve: invalid command '\"'\"'bundle-uri'\"'\"'\n+\tEOF\n+\n+\ttest_must_fail test-tool serve-v2 --stateless-rpc <in >out 2>err.actual &&\n+\ttest_cmp err.expect err.actual &&\n+\ttest_must_be_empty out\n+'\n+\n+\n+test_expect_success 'basics of bundle-uri: enabled with single URI' '\n+\ttest_config uploadpack.bundleURI https://cdn.example.com/repo.bdl &&\n+\n+\ttest-tool pkt-line pack >in <<-EOF &&\n+\tcommand=bundle-uri\n+\tobject-format=$(test_oid algo)\n+\t0000\n+\tEOF\n+\n+\tcat >expect <<-EOF &&\n+\thttps://cdn.example.com/repo.bdl\n+\t0000\n+\tEOF\n+\n+\ttest-tool serve-v2 --stateless-rpc <in >out &&\n+\ttest-tool pkt-line unpack <out >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'basics of bundle-uri: enabled with single URI' '\n+\ttest_config uploadpack.bundleURI https://cdn.example.com/repo.bdl &&\n+\n+\ttest-tool pkt-line pack >in <<-EOF &&\n+\tcommand=bundle-uri\n+\tobject-format=$(test_oid algo)\n+\t0000\n+\tEOF\n+\n+\tcat >expect <<-EOF &&\n+\thttps://cdn.example.com/repo.bdl\n+\t0000\n+\tEOF\n+\n+\ttest-tool serve-v2 --stateless-rpc <in >out &&\n+\ttest-tool pkt-line unpack <out >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'basics of bundle-uri: enabled with two URIs' '\n+\ttest_config uploadpack.bundleURI https://cdn.example.com/repo.bdl &&\n+\ttest_config uploadpack.bundleURI https://cdn.example.com/recent.bdl --add &&\n+\n+\ttest-tool pkt-line pack >in <<-EOF &&\n+\tcommand=bundle-uri\n+\tobject-format=$(test_oid algo)\n+\t0000\n+\tEOF\n+\n+\tcat >expect <<-EOF &&\n+\thttps://cdn.example.com/repo.bdl\n+\thttps://cdn.example.com/recent.bdl\n+\t0000\n+\tEOF\n+\n+\ttest-tool serve-v2 --stateless-rpc <in >out &&\n+\ttest-tool pkt-line unpack <out >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'basics of bundle-uri: unknown future feature(s)' '\n+\ttest_config uploadpack.bundleURI https://cdn.example.com/fake.bdl &&\n+\n+\ttest-tool pkt-line pack >in <<-EOF &&\n+\tcommand=bundle-uri\n+\tobject-format=$(test_oid algo)\n+\t0001\n+\tsome-feature\n+\twe-do-not\n+\tknow=about\n+\t0000\n+\tEOF\n+\n+\tcat >err.expect <<-\\EOF &&\n+\tfatal: bundle-uri: unexpected argument: '\"'\"'some-feature'\"'\"'\n+\tEOF\n+\n+\ttest_must_fail test-tool serve-v2 --stateless-rpc <in >out 2>err.actual &&\n+\ttest_cmp err.expect err.actual &&\n+\ttest_must_be_empty out\n+'\n+\n test_done\n-- \n2.33.0.rc0.646.g585563e77f\n\n"},{"id":"432106","messageId":"RFC-patch-02.13-70c2209c10-20210805T150534Z-avarab@gmail.com","threadId":"56220","inReplyTo":"RFC-cover-00.13-0000000000-20210805T150534Z-avarab@gmail.com","subject":"[RFC PATCH 02/13] bundle-uri client: add \"bundle-uri\" parsing + tests","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-08-05T15:07:18Z","receivedAt":"2021-08-05T15:07:46Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Add a \"test-tool bundle-uri parse\" which parses the format defined in\nthe newly specified \"bundle-uri\" command.\n\nAs note in the \"bundle-uri\" section in protocol-v2.txt we haven't\nspecified any key-values yet, just URI lines, but we need to make sure\nour client doesn't die if this optional data is provided by the\nserver. Let's add a bundle_uri_parse_line() to do that, in subsequent\ncommits the actual client code in {connect,transport}.c will make use\nof it.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n Makefile                    |  1 +\n bundle-uri.c                | 80 ++++++++++++++++++++++++++++++\n bundle-uri.h                | 16 ++++++\n t/helper/test-bundle-uri.c  | 80 ++++++++++++++++++++++++++++++\n t/helper/test-tool.c        |  1 +\n t/helper/test-tool.h        |  1 +\n t/t5750-bundle-uri-parse.sh | 98 +++++++++++++++++++++++++++++++++++++\n 7 files changed, 277 insertions(+)\n create mode 100644 t/helper/test-bundle-uri.c\n create mode 100755 t/t5750-bundle-uri-parse.sh\n\ndiff --git a/Makefile b/Makefile\nindex 877c6c47b6..6d3612b962 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -699,6 +699,7 @@ PROGRAMS += $(patsubst %.o,git-%$X,$(PROGRAM_OBJS))\n TEST_BUILTINS_OBJS += test-advise.o\n TEST_BUILTINS_OBJS += test-bitmap.o\n TEST_BUILTINS_OBJS += test-bloom.o\n+TEST_BUILTINS_OBJS += test-bundle-uri.o\n TEST_BUILTINS_OBJS += test-chmtime.o\n TEST_BUILTINS_OBJS += test-config.o\n TEST_BUILTINS_OBJS += test-crontab.o\ndiff --git a/bundle-uri.c b/bundle-uri.c\nindex 2d93e8b003..d48bb78012 100644\n--- a/bundle-uri.c\n+++ b/bundle-uri.c\n@@ -63,3 +63,83 @@ int bundle_uri_command(struct repository *r,\n \n \treturn 0;\n }\n+\n+/**\n+ * General API for {transport,connect}.c etc.\n+ */\n+int bundle_uri_parse_line(struct string_list *bundle_uri, const char *line)\n+{\n+\tint i;\n+\tstruct string_list uri = STRING_LIST_INIT_DUP;\n+\tstruct string_list_item *item = NULL;\n+\tint err = 0;\n+\n+\t/*\n+\t * Right now we don't understand anything beyond the first SP,\n+\t * but let's be tolerant and ignore any future unknown\n+\t * fields. See the \"MUST\" note about \"bundle-feature-key\" in\n+\t * technical/protocol-v2.txt\n+\t */\n+\tif (string_list_split(&uri, line, ' ', -1) < 1)\n+\t\treturn error(_(\"bundle-uri line not in SP-delimited format: %s\"), line);\n+\n+\tfor (i = 0; i < uri.nr; i++) {\n+\t\tstruct string_list kv = STRING_LIST_INIT_DUP;\n+\t\tstruct string_list_item *kv_item = NULL;\n+\t\tconst char *arg = uri.items[i].string;\n+\t\tint fields;\n+\n+\t\t/*\n+\t\t * The \"string\" for each list item is the parsed URI\n+\t\t * at the start of the line\n+\t\t */\n+\t\tif (i == 0) {\n+\t\t\titem = string_list_append(bundle_uri, arg);\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\t/*\n+\t\t * Anything else on the line is keys or key-value\n+\t\t * pairs separated by \"=\".\n+\t\t *\n+\t\t * Let's parse the format, even if we don't understand\n+\t\t * any of the keys or values yet.\n+\t\t */\n+\t\tassert(item);\n+\t\targ = uri.items[i].string;\n+\t\tif (i == 1) {\n+\t\t\titem->util = xcalloc(1, sizeof(struct string_list));\n+\t\t\tstring_list_init(item->util, 1);\n+\t\t}\n+\n+\t\tfields = string_list_split(&kv, arg, '=', 2);\n+\t\tif (fields < 1 || fields > 2) {\n+\t\t\terr = error(\"expected `k` or `k=v` in column %d of bundle-uri line '%s', got '%s'\",\n+\t\t\t\t     i, line, arg);\n+\t\t\tstring_list_clear(&kv, 0);\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tkv_item = string_list_append(item->util, kv.items[0].string);\n+\t\tif (kv.nr == 2)\n+\t\t\tkv_item->util = xstrdup(kv.items[1].string);\n+\n+\t\tstring_list_clear(&kv, 0);\n+\t}\n+\tstring_list_clear(&uri, 0);\n+\treturn err;\n+}\n+\n+static void bundle_uri_string_list_clear_cb(void *util, const char *string)\n+{\n+\tstruct string_list *fields = util;\n+\tif (!fields)\n+\t\treturn;\n+\tstring_list_clear(fields, 1);\n+\tfree(fields);\n+}\n+\n+void bundle_uri_string_list_clear(struct string_list *bundle_uri)\n+{\n+\tstring_list_clear_func(bundle_uri, bundle_uri_string_list_clear_cb);\n+}\ndiff --git a/bundle-uri.h b/bundle-uri.h\nindex 6a40efeb39..2d271801b8 100644\n--- a/bundle-uri.h\n+++ b/bundle-uri.h\n@@ -4,6 +4,7 @@\n struct repository;\n struct packet_reader;\n struct packet_writer;\n+struct string_list;\n \n /**\n  * serve.[ch] API.\n@@ -11,4 +12,19 @@ struct packet_writer;\n int bundle_uri_advertise(struct repository *r, struct strbuf *value);\n int bundle_uri_command(struct repository *r, struct packet_reader *request);\n \n+/**\n+ * General API for {transport,connect}.c etc.\n+ */\n+\n+/**\n+ * bundle_uri_parse_line() returns 0 when a valid bundle-uri has been\n+ * added to `bundle_uri`, <0 on error.\n+ */\n+int bundle_uri_parse_line(struct string_list *bundle_uri, const char *line);\n+\n+/**\n+ * Clear the `bundle_uri` list. Just a very thin wrapper on\n+ * string_list_clear().\n+ */\n+void bundle_uri_string_list_clear(struct string_list *bundle_uri);\n #endif /* BUNDLE_URI_H */\ndiff --git a/t/helper/test-bundle-uri.c b/t/helper/test-bundle-uri.c\nnew file mode 100644\nindex 0000000000..014da1b6ab\n--- /dev/null\n+++ b/t/helper/test-bundle-uri.c\n@@ -0,0 +1,80 @@\n+#include \"test-tool.h\"\n+#include \"parse-options.h\"\n+#include \"bundle-uri.h\"\n+#include \"strbuf.h\"\n+#include \"string-list.h\"\n+\n+static int cmd__bundle_uri_parse(int argc, const char **argv)\n+{\n+\tconst char *usage[] = {\n+\t\t\"test-tool bundle-uri parse <in\",\n+\t\tNULL\n+\t};\n+\tstruct option options[] = {\n+\t\tOPT_END(),\n+\t};\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tstruct string_list list = STRING_LIST_INIT_DUP;\n+\tint err = 0;\n+\tstruct string_list_item *item;\n+\n+\targc = parse_options(argc, argv, NULL, options, usage, 0);\n+\tif (argc)\n+\t\tgoto usage;\n+\n+\twhile (strbuf_getline(&sb, stdin) != EOF) {\n+\t\tif (bundle_uri_parse_line(&list, sb.buf) < 0)\n+\t\t\terr = error(\"bad line: %s\", sb.buf);\n+\t}\n+\tfor_each_string_list_item(item, &list) {\n+\t\tstruct string_list_item *kv_item;\n+\t\tstruct string_list *kv = item->util;\n+\n+\t\tfprintf(stdout, \"%s\", item->string);\n+\t\tif (!kv) {\n+\t\t\tfprintf(stdout, \"\\n\");\n+\t\t\tcontinue;\n+\t\t}\n+\t\tfor_each_string_list_item(kv_item, kv) {\n+\t\t\tconst char *k = kv_item->string;\n+\t\t\tconst char *v = kv_item->util;\n+\n+\t\t\tif (v)\n+\t\t\t\tfprintf(stdout, \" [kv: %s => %s]\", k, v);\n+\t\t\telse\n+\t\t\t\tfprintf(stdout, \" [attr: %s]\", k);\n+\t\t}\n+\t\tfprintf(stdout, \"\\n\");\n+\t}\n+\tstrbuf_release(&sb);\n+\n+\tbundle_uri_string_list_clear(&list);\n+\n+\treturn err < 0 ? 1 : 0;\n+usage:\n+\tusage_with_options(usage, options);\n+}\n+\n+int cmd__bundle_uri(int argc, const char **argv)\n+{\n+\tconst char *usage[] = {\n+\t\t\"test-tool bundle-uri <subcommand> [<options>]\",\n+\t\tNULL\n+\t};\n+\tstruct option options[] = {\n+\t\tOPT_END(),\n+\t};\n+\n+\targc = parse_options(argc, argv, NULL, options, usage,\n+\t\t\t     PARSE_OPT_STOP_AT_NON_OPTION |\n+\t\t\t     PARSE_OPT_KEEP_ARGV0);\n+\tif (argc == 1)\n+\t\tgoto usage;\n+\n+\tif (!strcmp(argv[1], \"parse\"))\n+\t\treturn cmd__bundle_uri_parse(argc - 1, argv + 1);\n+\terror(\"there is no test-tool bundle-uri tool '%s'\", argv[1]);\n+\n+usage:\n+\tusage_with_options(usage, options);\n+}\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex 3ce5585e53..b6e1ee7b25 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -17,6 +17,7 @@ static struct test_cmd cmds[] = {\n \t{ \"advise\", cmd__advise_if_enabled },\n \t{ \"bitmap\", cmd__bitmap },\n \t{ \"bloom\", cmd__bloom },\n+\t{ \"bundle-uri\", cmd__bundle_uri },\n \t{ \"chmtime\", cmd__chmtime },\n \t{ \"config\", cmd__config },\n \t{ \"crontab\", cmd__crontab },\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex 9f0f522850..ef839ac726 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -7,6 +7,7 @@\n int cmd__advise_if_enabled(int argc, const char **argv);\n int cmd__bitmap(int argc, const char **argv);\n int cmd__bloom(int argc, const char **argv);\n+int cmd__bundle_uri(int argc, const char **argv);\n int cmd__chmtime(int argc, const char **argv);\n int cmd__config(int argc, const char **argv);\n int cmd__crontab(int argc, const char **argv);\ndiff --git a/t/t5750-bundle-uri-parse.sh b/t/t5750-bundle-uri-parse.sh\nnew file mode 100755\nindex 0000000000..ef1b9fedea\n--- /dev/null\n+++ b/t/t5750-bundle-uri-parse.sh\n@@ -0,0 +1,98 @@\n+#!/bin/sh\n+\n+test_description=\"Test bundle-uri bundle_uri_parse_line()\"\n+\n+TEST_NO_CREATE_REPO=1\n+. ./test-lib.sh\n+\n+test_expect_success 'bundle_uri_parse_line() just URIs' '\n+\tcat >in <<-\\EOF &&\n+\thttp://example.com/bundle.bdl\n+\thttps://example.com/bundle.bdl\n+\tfile:///usr/share/git/bundle.bdl\n+\tEOF\n+\n+\t# For the simple case\n+\tcp in expect &&\n+\n+\ttest-tool bundle-uri parse <in >actual 2>err &&\n+\ttest_must_be_empty err &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'bundle_uri_parse_line() with attributes' '\n+\tcat >in <<-\\EOF &&\n+\thttp://example.com/bundle1.bdl attr\n+\thttp://example.com/bundle2.bdl ibute\n+\tEOF\n+\n+\n+\tcat >expect <<-\\EOF &&\n+\thttp://example.com/bundle1.bdl [attr: attr]\n+\thttp://example.com/bundle2.bdl [attr: ibute]\n+\tEOF\n+\n+\ttest-tool bundle-uri parse <in >actual 2>err &&\n+\ttest_must_be_empty err &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'bundle_uri_parse_line() with attributes and key-value attributes' '\n+\tcat >in <<-\\EOF &&\n+\thttp://example.com/bundle1.bdl x a=b y c=d z e=f a=b\n+\tEOF\n+\n+\n+\tcat >expect <<-\\EOF &&\n+\thttp://example.com/bundle1.bdl [attr: x] [kv: a => b] [attr: y] [kv: c => d] [attr: z] [kv: e => f] [kv: a => b]\n+\tEOF\n+\n+\ttest-tool bundle-uri parse <in >actual 2>err &&\n+\ttest_must_be_empty err &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'bundle_uri_parse_line() parsing edge cases: extra SP' '\n+\tcat >in <<-\\EOF &&\n+\thttp://example.com/bundle1.bdl one-space\n+\thttp://example.com/bundle1.bdl  two-space\n+\thttp://example.com/bundle1.bdl   three-space\n+\tEOF\n+\n+\t# We are anal just the one SP\n+\tcat >expect <<-\\EOF &&\n+\thttp://example.com/bundle1.bdl [attr: one-space]\n+\thttp://example.com/bundle1.bdl [attr: ] [attr: two-space]\n+\thttp://example.com/bundle1.bdl [attr: ] [attr: ] [attr: three-space]\n+\tEOF\n+\n+\ttest-tool bundle-uri parse <in >actual 2>err &&\n+\ttest_must_be_empty err &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'bundle_uri_parse_line() parsing edge cases: multiple = in key-values' '\n+\tcat >in <<-\\EOF &&\n+\thttp://example.com/bundle1.bdl k=v=extra\n+\thttp://example.com/bundle2.bdl a=b k=v=extra c=d\n+\tEOF\n+\n+\tcat >err.expect <<-\\EOF &&\n+\terror: expected `k` or `k=v` in column 1 of bundle-uri line '\"'\"'http://example.com/bundle1.bdl k=v=extra'\"'\"', got '\"'\"'k=v=extra'\"'\"'\n+\terror: bad line: http://example.com/bundle1.bdl k=v=extra\n+\terror: expected `k` or `k=v` in column 2 of bundle-uri line '\"'\"'http://example.com/bundle2.bdl a=b k=v=extra c=d'\"'\"', got '\"'\"'k=v=extra'\"'\"'\n+\terror: bad line: http://example.com/bundle2.bdl a=b k=v=extra c=d\n+\tEOF\n+\n+\t# We fail, but try to continue parsing regardless\n+\tcat >expect <<-\\EOF &&\n+\thttp://example.com/bundle1.bdl\n+\thttp://example.com/bundle2.bdl [kv: a => b] [kv: c => d]\n+\tEOF\n+\n+\ttest_must_fail test-tool bundle-uri parse <in >actual 2>err.actual &&\n+\ttest_cmp err.expect err.actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_done\n-- \n2.33.0.rc0.646.g585563e77f\n\n"},{"id":"432107","messageId":"RFC-patch-04.13-21caf01775-20210805T150534Z-avarab@gmail.com","threadId":"56220","inReplyTo":"RFC-cover-00.13-0000000000-20210805T150534Z-avarab@gmail.com","subject":"[RFC PATCH 04/13] bundle-uri client: add minimal NOOP client","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-08-05T15:07:20Z","receivedAt":"2021-08-05T15:07:48Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Set up all the needed client parts of the \"bundle-uri\" protocol\nextension, without actually doing anything with the bundle URIs.\n\nI.e. if the server says it supports \"bundle-uri\" we'll issue a\ncommand=bundle-uri after command=ls-refs when we're cloning. We'll\nparse the returned output using the code already tested for in\nt5750-bundle-uri-parse.sh.\n\nWhat we aren't doing is actually acting on that data, i.e. downloading\nthe bundle(s) before we get to doing the command=fetch, and adjusting\nour negotiation dialog appropriately. I'll do that in subsequent\ncommits.\n\nThere's a question of what level of encapsulation we should use here,\nI've opted to use connect.h in clone.c, but we could also e.g. make\ntransport_get_remote_refs() invoke this, i.e. make it implicitly get\nthe bundle-uri list for later steps.\n\nThis approach means that we don't \"support\" this in \"git fetch\" for\nnow. I'm starting with the case of initial clones, although as noted\nin preceding commits to the protocol documentation nothing about this\napproach precludes getting bundles on incremental fetches.\n\nFor the t5732-protocol-v2-bundle-uri-http.sh it's not easy to set\nenvironment variables for git-upload-pack (it's started by Apache), so\nlet's skip the test under T5730_HTTP, and add unused T5730_{FILE,GIT}\nprerequisites for consistency and future use.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/clone.c                        |   7 ++\n bundle-uri.c                           |  10 +-\n connect.c                              |  47 +++++++++\n remote.h                               |   4 +\n t/lib-t5730-protocol-v2-bundle-uri.sh  | 140 +++++++++++++++++++++++++\n t/t5730-protocol-v2-bundle-uri-file.sh |  36 +++++++\n t/t5731-protocol-v2-bundle-uri-git.sh  |  17 +++\n t/t5732-protocol-v2-bundle-uri-http.sh |  17 +++\n transport-helper.c                     |  13 +++\n transport-internal.h                   |   7 ++\n transport.c                            |  48 +++++++++\n transport.h                            |  18 ++++\n 12 files changed, 362 insertions(+), 2 deletions(-)\n create mode 100644 t/lib-t5730-protocol-v2-bundle-uri.sh\n create mode 100755 t/t5730-protocol-v2-bundle-uri-file.sh\n create mode 100755 t/t5731-protocol-v2-bundle-uri-git.sh\n create mode 100755 t/t5732-protocol-v2-bundle-uri-http.sh\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 66fe66679c..1e4e6be57d 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -27,6 +27,7 @@\n #include \"iterator.h\"\n #include \"sigchain.h\"\n #include \"branch.h\"\n+#include \"connect.h\"\n #include \"remote.h\"\n #include \"run-command.h\"\n #include \"connected.h\"\n@@ -1292,6 +1293,12 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \n \trefs = transport_get_remote_refs(transport, &transport_ls_refs_options);\n \n+\t/*\n+\t * Populate transport->got_remote_bundle_uri and\n+\t * transport->bundle_uri. We might get nothing.\n+\t */\n+\ttransport_get_remote_bundle_uri(transport);\n+\n \tif (refs) {\n \t\tint hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport));\n \ndiff --git a/bundle-uri.c b/bundle-uri.c\nindex d48bb78012..56619bde22 100644\n--- a/bundle-uri.c\n+++ b/bundle-uri.c\n@@ -38,11 +38,16 @@ static int bundle_uri_startup_config(const char *var, const char *value,\n \n int bundle_uri_advertise(struct repository *r, struct strbuf *value)\n {\n+\tif (value &&\n+\t    git_env_bool(\"GIT_TEST_BUNDLE_URI_UNKNOWN_CAPABILITY_VALUE\", 0))\n+\t\tstrbuf_addstr(value, \"test-unknown-capability-value\");\n+\n \tif (advertise_bundle_uri == -1) {\n \t\tgit_config(bundle_uri_startup_config, NULL);\n \t\tif (advertise_bundle_uri == -1)\n \t\t\tadvertise_bundle_uri = 0;\n \t}\n+\n \treturn advertise_bundle_uri;\n }\n \n@@ -53,9 +58,10 @@ int bundle_uri_command(struct repository *r,\n \tpacket_writer_init(&writer, 1);\n \n \twhile (packet_reader_read(request) == PACKET_READ_NORMAL)\n-\t\tdie(\"bundle-uri: unexpected argument: '%s'\", request->line);\n+\t\tdie(_(\"bundle-uri: unexpected argument: '%s'\"), request->line);\n+\n \tif (request->status != PACKET_READ_FLUSH)\n-\t\tdie(\"bundle-uri: expected flush after arguments\");\n+\t\tdie(_(\"bundle-uri: expected flush after arguments\"));\n \n \tsend_bundle_uris(&writer, &bundle_uris);\n \ndiff --git a/connect.c b/connect.c\nindex 18dfeae2b8..c7601ffd83 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -15,6 +15,7 @@\n #include \"version.h\"\n #include \"protocol.h\"\n #include \"alias.h\"\n+#include \"bundle-uri.h\"\n \n static char *server_capabilities_v1;\n static struct strvec server_capabilities_v2 = STRVEC_INIT;\n@@ -489,6 +490,52 @@ static void send_capabilities(int fd_out, struct packet_reader *reader)\n \t}\n }\n \n+int get_remote_bundle_uri(int fd_out, struct packet_reader *reader,\n+\t\t\t  struct string_list *bundle_uri, int stateless_rpc)\n+{\n+\tint line_nr = 1;\n+\n+\t/* Assert bundle-uri support */\n+\tserver_supports_v2(\"bundle-uri\", 1);\n+\n+\t/* (Re-)send capabilities */\n+\tsend_capabilities(fd_out, reader);\n+\n+\t/* Send command */\n+\tpacket_write_fmt(fd_out, \"command=bundle-uri\\n\");\n+\tpacket_delim(fd_out);\n+\n+\t/* Send options */\n+\tif (git_env_bool(\"GIT_TEST_PROTOCOL_BAD_BUNDLE_URI\", 0))\n+\t\tpacket_write_fmt(fd_out, \"test-bad-client\\n\");\n+\tpacket_flush(fd_out);\n+\n+\t/* Process response from server */\n+\twhile (packet_reader_read(reader) == PACKET_READ_NORMAL) {\n+\t\tconst char *line = reader->line;\n+\t\tline_nr++;\n+\n+\t\tif (!bundle_uri_parse_line(bundle_uri, line))\n+\t\t\tcontinue;\n+\n+\t\treturn error(_(\"error on bundle-uri response line %d: %s\"),\n+\t\t\t     line_nr, line);\n+\t}\n+\n+\tif (reader->status != PACKET_READ_FLUSH)\n+\t\treturn error(_(\"expected flush after bundle-uri listing\"));\n+\n+\t/*\n+\t * Might die(), but obscure enough that that's OK, e.g. in\n+\t * serve.c we'll call BUG() on its equivalent (the\n+\t * PACKET_READ_RESPONSE_END check).\n+\t */\n+\tcheck_stateless_delimiter(stateless_rpc, reader,\n+\t\t\t\t  _(\"expected response end packet after ref listing\"));\n+\n+\treturn 0;\n+}\n+\n struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n \t\t\t     struct ref **list, int for_push,\n \t\t\t     struct transport_ls_refs_options *transport_options,\ndiff --git a/remote.h b/remote.h\nindex 5a59198252..a0823fe4e9 100644\n--- a/remote.h\n+++ b/remote.h\n@@ -202,6 +202,10 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n \t\t\t     const struct string_list *server_options,\n \t\t\t     int stateless_rpc);\n \n+/* Used for protocol v2 in order to retrieve refs from a remote */\n+int get_remote_bundle_uri(int fd_out, struct packet_reader *reader,\n+\t\t\t  struct string_list *bundle_uri, int stateless_rpc);\n+\n int resolve_remote_symref(struct ref *ref, struct ref *list);\n \n /*\ndiff --git a/t/lib-t5730-protocol-v2-bundle-uri.sh b/t/lib-t5730-protocol-v2-bundle-uri.sh\nnew file mode 100644\nindex 0000000000..6bd9d2dcfb\n--- /dev/null\n+++ b/t/lib-t5730-protocol-v2-bundle-uri.sh\n@@ -0,0 +1,140 @@\n+# Included from t573*-protocol-v2-bundle-uri-*.sh\n+\n+T5730_PARENT=\n+T5730_URI=\n+T5730_BUNDLE_URI=\n+case \"$T5730_PROTOCOL\" in\n+file)\n+\tT5730_PARENT=file_parent\n+\tT5730_URI=\"file://$PWD/file_parent\"\n+\tT5730_BUNDLE_URI=\"$T5730_URI/fake.bdl\"\n+\ttest_set_prereq T5730_FILE\n+\t;;\n+git)\n+\t. \"$TEST_DIRECTORY\"/lib-git-daemon.sh\n+\tstart_git_daemon --export-all --enable=receive-pack\n+\tT5730_PARENT=\"$GIT_DAEMON_DOCUMENT_ROOT_PATH/parent\"\n+\tT5730_URI=\"$GIT_DAEMON_URL/parent\"\n+\tT5730_BUNDLE_URI=\"https://example.com/fake.bdl\"\n+\ttest_set_prereq T5730_GIT\n+\t;;\n+http)\n+\t. \"$TEST_DIRECTORY\"/lib-httpd.sh\n+\tstart_httpd\n+\tT5730_PARENT=\"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\"\n+\tT5730_URI=\"$HTTPD_URL/smart/http_parent\"\n+\ttest_set_prereq T5730_HTTP\n+\t;;\n+*)\n+\tBUG \"Need to pass valid T5730_PROTOCOL (was $T5730_PROTOCOL)\"\n+\t;;\n+esac\n+\n+test_expect_success \"setup protocol v2 $T5730_PROTOCOL:// tests\" '\n+\tgit init \"$T5730_PARENT\" &&\n+\ttest_commit -C \"$T5730_PARENT\" one\n+'\n+\n+# Poor man's URI escaping. Good enough for the test suite whose trash\n+# directory has a space in it. See 93c3fcbe4d4 (git-svn: attempt to\n+# mimic SVN 1.7 URL canonicalization, 2012-07-28) for prior art.\n+test_uri_escape() {\n+\tsed 's/ /%20/g'\n+}\n+\n+case \"$T5730_PROTOCOL\" in\n+http)\n+\ttest_expect_success \"setup config for $T5730_PROTOCOL:// tests\" '\n+\t\tgit -C \"$T5730_PARENT\" config http.receivepack true\n+\t'\n+\t;;\n+*)\n+\t;;\n+esac\n+T5730_BUNDLE_URI_ESCAPED=$(echo \"$T5730_BUNDLE_URI\" | test_uri_escape)\n+\n+test_expect_success \"connect with $T5730_PROTOCOL:// using protocol v2: no bundle-uri\" '\n+\ttest_when_finished \"rm -f log\" &&\n+\n+\tGIT_TRACE_PACKET=\"$PWD/log\" \\\n+\tgit \\\n+\t\t-c protocol.version=2 \\\n+\t\tls-remote --symref \"$T5730_URI\" \\\n+\t\t>actual 2>err &&\n+\n+\t# Server responded using protocol v2\n+\tgrep \"< version 2\" log &&\n+\n+\t! grep bundle-uri log\n+'\n+\n+test_expect_success \"connect with $T5730_PROTOCOL:// using protocol v2: have bundle-uri\" '\n+\ttest_when_finished \"rm -f log\" &&\n+\n+\ttest_config -C \"$T5730_PARENT\" \\\n+\t\tuploadpack.bundleURI \"$T5730_BUNDLE_URI_ESCAPED\" &&\n+\n+\tGIT_TRACE_PACKET=\"$PWD/log\" \\\n+\tgit \\\n+\t\t-c protocol.version=2 \\\n+\t\tls-remote --symref \"$T5730_URI\" \\\n+\t\t>actual 2>err &&\n+\n+\t# Server responded using protocol v2\n+\tgrep \"< version 2\" log &&\n+\n+\t# Server advertised bundle-uri capability\n+\tgrep bundle-uri log\n+'\n+\n+test_expect_success !T5730_HTTP \"bad client with $T5730_PROTOCOL:// using protocol v2\" '\n+\ttest_when_finished \"rm -f log\" &&\n+\n+\ttest_config -C \"$T5730_PARENT\" uploadpack.bundleURI \\\n+\t\t\"$T5730_BUNDLE_URI_ESCAPED\" &&\n+\n+\tcat >err.expect <<-\\EOF &&\n+\tCloning into '\"'\"'child'\"'\"'...\n+\tEOF\n+\tcase \"$T5730_PROTOCOL\" in\n+\tfile)\n+\t\tcat >fatal-bundle-uri.expect <<-\\EOF\n+\t\tfatal: bundle-uri: unexpected argument: '\"'\"'test-bad-client'\"'\"'\n+\t\tEOF\n+\t\t;;\n+\t*)\n+\t\tcat >fatal.expect <<-\\EOF\n+\t\tfatal: read error: Connection reset by peer\n+\t\tEOF\n+\t\t;;\n+\tesac &&\n+\n+\ttest_when_finished \"rm -rf child\" &&\n+\ttest_must_fail ok=sigpipe env \\\n+\t\tGIT_TRACE_PACKET=\"$PWD/log\" \\\n+\t\tGIT_TEST_PROTOCOL_BAD_BUNDLE_URI=true \\\n+\t\tgit -c protocol.version=2 \\\n+\t\tclone \"$T5730_URI\" child \\\n+\t\t>out 2>err &&\n+\ttest_must_be_empty out &&\n+\n+\tgrep -v -e ^fatal: -e ^error: err >err.actual &&\n+\ttest_cmp err.expect err.actual &&\n+\n+\tcase \"$T5730_PROTOCOL\" in\n+\tfile)\n+\t\t# Due to general race conditions with client/server replies we\n+\t\t# may or may not get \"fatal: the remote end hung up\n+\t\t# expectedly\" here\n+\t\tgrep \"^fatal: bundle-uri:\" err >fatal-bundle-uri.actual &&\n+\t\ttest_cmp fatal-bundle-uri.expect fatal-bundle-uri.actual\n+\t\t;;\n+\t*)\n+\t\tgrep \"^fatal:\" err >fatal.actual &&\n+\t\ttest_cmp fatal.expect fatal.actual\n+\t\t;;\n+\tesac &&\n+\n+\tgrep \"clone> test-bad-client$\" log >sent-bad-request &&\n+\ttest_file_not_empty sent-bad-request\n+'\ndiff --git a/t/t5730-protocol-v2-bundle-uri-file.sh b/t/t5730-protocol-v2-bundle-uri-file.sh\nnew file mode 100755\nindex 0000000000..89203d3a23\n--- /dev/null\n+++ b/t/t5730-protocol-v2-bundle-uri-file.sh\n@@ -0,0 +1,36 @@\n+#!/bin/sh\n+\n+test_description=\"Test bundle-uri with protocol v2 and 'file://' transport\"\n+\n+TEST_NO_CREATE_REPO=1\n+\n+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n+\n+. ./test-lib.sh\n+\n+# Test protocol v2 with 'file://' transport\n+#\n+T5730_PROTOCOL=file\n+. \"$TEST_DIRECTORY\"/lib-t5730-protocol-v2-bundle-uri.sh\n+\n+test_expect_success \"unknown capability value with $T5730_PROTOCOL:// using protocol v2\" '\n+\ttest_when_finished \"rm -f log\" &&\n+\n+\ttest_config -C \"$T5730_PARENT\" \\\n+\t\tuploadpack.bundleURI \"$T5730_BUNDLE_URI_ESCAPED\" &&\n+\n+\tGIT_TRACE_PACKET=\"$PWD/log\" \\\n+\tGIT_TEST_BUNDLE_URI_UNKNOWN_CAPABILITY_VALUE=true \\\n+\tgit \\\n+\t\t-c protocol.version=2 \\\n+\t\tls-remote --symref \"$T5730_URI\" \\\n+\t\t>actual 2>err &&\n+\n+\t# Server responded using protocol v2\n+\tgrep \"< version 2\" log &&\n+\n+\tgrep \"> bundle-uri=test-unknown-capability-value\" log\n+'\n+\n+test_done\ndiff --git a/t/t5731-protocol-v2-bundle-uri-git.sh b/t/t5731-protocol-v2-bundle-uri-git.sh\nnew file mode 100755\nindex 0000000000..282847b311\n--- /dev/null\n+++ b/t/t5731-protocol-v2-bundle-uri-git.sh\n@@ -0,0 +1,17 @@\n+#!/bin/sh\n+\n+test_description=\"Test bundle-uri with protocol v2 and 'git://' transport\"\n+\n+TEST_NO_CREATE_REPO=1\n+\n+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n+\n+. ./test-lib.sh\n+\n+# Test protocol v2 with 'git://' transport\n+#\n+T5730_PROTOCOL=git\n+. \"$TEST_DIRECTORY\"/lib-t5730-protocol-v2-bundle-uri.sh\n+\n+test_done\ndiff --git a/t/t5732-protocol-v2-bundle-uri-http.sh b/t/t5732-protocol-v2-bundle-uri-http.sh\nnew file mode 100755\nindex 0000000000..fcc1cf3fae\n--- /dev/null\n+++ b/t/t5732-protocol-v2-bundle-uri-http.sh\n@@ -0,0 +1,17 @@\n+#!/bin/sh\n+\n+test_description=\"Test bundle-uri with protocol v2 and 'git://' transport\"\n+\n+TEST_NO_CREATE_REPO=1\n+\n+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n+\n+. ./test-lib.sh\n+\n+# Test protocol v2 with 'git://' transport\n+#\n+T5730_PROTOCOL=http\n+. \"$TEST_DIRECTORY\"/lib-t5730-protocol-v2-bundle-uri.sh\n+\n+test_done\ndiff --git a/transport-helper.c b/transport-helper.c\nindex e8dbdd1153..aff27bcf78 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -1260,9 +1260,22 @@ static struct ref *get_refs_list_using_list(struct transport *transport,\n \treturn ret;\n }\n \n+static int get_bundle_uri(struct transport *transport)\n+{\n+\tget_helper(transport);\n+\n+\tif (process_connect(transport, 0)) {\n+\t\tdo_take_over(transport);\n+\t\treturn transport->vtable->get_bundle_uri(transport);\n+\t}\n+\n+\treturn -1;\n+}\n+\n static struct transport_vtable vtable = {\n \t.set_option\t= set_helper_option,\n \t.get_refs_list\t= get_refs_list,\n+\t.get_bundle_uri = get_bundle_uri,\n \t.fetch_refs\t= fetch_refs,\n \t.push_refs\t= push_refs,\n \t.connect\t= connect_helper,\ndiff --git a/transport-internal.h b/transport-internal.h\nindex c4ca0b733a..90ea749e5c 100644\n--- a/transport-internal.h\n+++ b/transport-internal.h\n@@ -26,6 +26,13 @@ struct transport_vtable {\n \tstruct ref *(*get_refs_list)(struct transport *transport, int for_push,\n \t\t\t\t     struct transport_ls_refs_options *transport_options);\n \n+\t/**\n+\t * Populates the remote side's bundle-uri under protocol v2,\n+\t * if the \"bundle-uri\" capability was advertised. Returns 0 if\n+\t * OK, negative values on error.\n+\t */\n+\tint (*get_bundle_uri)(struct transport *transport);\n+\n \t/**\n \t * Fetch the objects for the given refs. Note that this gets\n \t * an array, and should ignore the list structure.\ndiff --git a/transport.c b/transport.c\nindex f9400b9b0b..444cf74756 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -22,6 +22,7 @@\n #include \"protocol.h\"\n #include \"object-store.h\"\n #include \"color.h\"\n+#include \"bundle-uri.h\"\n \n static int transport_use_color = -1;\n static char transport_colors[][COLOR_MAXLEN] = {\n@@ -345,6 +346,21 @@ static struct ref *get_refs_via_connect(struct transport *transport, int for_pus\n \treturn handshake(transport, for_push, options, 1);\n }\n \n+static int get_bundle_uri(struct transport *transport)\n+{\n+\tstruct git_transport_data *data = transport->data;\n+\tstruct packet_reader reader;\n+\tint stateless_rpc = transport->stateless_rpc;\n+\tstring_list_init(&transport->bundle_uri, 1);\n+\n+\tpacket_reader_init(&reader, data->fd[0], NULL, 0,\n+\t\t\t   PACKET_READ_CHOMP_NEWLINE |\n+\t\t\t   PACKET_READ_GENTLE_ON_EOF);\n+\n+\treturn get_remote_bundle_uri(data->fd[1], &reader,\n+\t\t\t\t     &transport->bundle_uri, stateless_rpc);\n+}\n+\n static int fetch_refs_via_pack(struct transport *transport,\n \t\t\t       int nr_heads, struct ref **to_fetch)\n {\n@@ -884,6 +900,7 @@ static int disconnect_git(struct transport *transport)\n \n static struct transport_vtable taken_over_vtable = {\n \t.get_refs_list\t= get_refs_via_connect,\n+\t.get_bundle_uri = get_bundle_uri,\n \t.fetch_refs\t= fetch_refs_via_pack,\n \t.push_refs\t= git_transport_push,\n \t.disconnect\t= disconnect_git\n@@ -1037,6 +1054,7 @@ static struct transport_vtable bundle_vtable = {\n \n static struct transport_vtable builtin_smart_vtable = {\n \t.get_refs_list\t= get_refs_via_connect,\n+\t.get_bundle_uri = get_bundle_uri,\n \t.fetch_refs\t= fetch_refs_via_pack,\n \t.push_refs\t= git_transport_push,\n \t.connect\t= connect_git,\n@@ -1050,6 +1068,7 @@ struct transport *transport_get(struct remote *remote, const char *url)\n \n \tret->progress = isatty(2);\n \tstring_list_init_dup(&ret->pack_lockfiles);\n+\tstring_list_init_dup(&ret->bundle_uri);\n \n \tif (!remote)\n \t\tBUG(\"No remote provided to transport_get()\");\n@@ -1453,6 +1472,34 @@ int transport_fetch_refs(struct transport *transport, struct ref *refs)\n \treturn rc;\n }\n \n+int transport_get_remote_bundle_uri(struct transport *transport)\n+{\n+\tconst struct transport_vtable *vtable = transport->vtable;\n+\n+\t/* Lazily configured */\n+\tif (transport->got_remote_bundle_uri++)\n+\t\treturn 0;\n+\n+\t/*\n+\t * \"Support\" protocol v0 and v2 without bundle-uri support by\n+\t * silently degrading to a NOOP.\n+\t */\n+\tif (!server_supports_v2(\"bundle-uri\", 0))\n+\t\treturn 0;\n+\n+\t/*\n+\t * This is intentionally below the transport.injectBundleURI,\n+\t * we want to be able to inject into protocol v0, or into the\n+\t * dialog of a server who doesn't support this.\n+\t */\n+\tif (!vtable->get_bundle_uri)\n+\t\treturn error(_(\"bundle-uri operation not supported by protocol\"));\n+\n+\tif (vtable->get_bundle_uri(transport) < 0)\n+\t\treturn error(_(\"could not retrieve server-advertised bundle-uri list\"));\n+\treturn 0;\n+}\n+\n void transport_unlock_pack(struct transport *transport)\n {\n \tint i;\n@@ -1478,6 +1525,7 @@ int transport_disconnect(struct transport *transport)\n \t\tret = transport->vtable->disconnect(transport);\n \tif (transport->got_remote_refs)\n \t\tfree_refs((void *)transport->remote_refs);\n+\tbundle_uri_string_list_clear(&transport->bundle_uri);\n \tfree(transport);\n \treturn ret;\n }\ndiff --git a/transport.h b/transport.h\nindex 1cbab11373..fb9f89f956 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -75,6 +75,18 @@ struct transport {\n \t */\n \tunsigned got_remote_refs : 1;\n \n+\t/**\n+\t * Indicates whether we already called get_bundle_uri_list(); set by\n+\t * transport.c::transport_get_remote_bundle_uri().\n+\t */\n+\tunsigned got_remote_bundle_uri : 1;\n+\n+\t/*\n+\t * The results of \"command=bundle-uri\", if both sides support\n+\t * the \"bundle-uri\" capability.\n+\t */\n+\tstruct string_list bundle_uri;\n+\n \t/*\n \t * Transports that call take-over destroys the data specific to\n \t * the transport type while doing so, and cannot be reused.\n@@ -270,6 +282,12 @@ struct transport_ls_refs_options {\n const struct ref *transport_get_remote_refs(struct transport *transport,\n \t\t\t\t\t    struct transport_ls_refs_options *transport_options);\n \n+/**\n+ * Retrieve bundle URI(s) from a remote. Populates \"struct\n+ * transport\"'s \"bundle_uri\" and \"got_remote_bundle_uri\".\n+ */\n+int transport_get_remote_bundle_uri(struct transport *transport);\n+\n /*\n  * Fetch the hash algorithm used by a remote.\n  *\n-- \n2.33.0.rc0.646.g585563e77f\n\n"},{"id":"432108","messageId":"RFC-patch-03.13-fc5c725307-20210805T150534Z-avarab@gmail.com","threadId":"56220","inReplyTo":"RFC-cover-00.13-0000000000-20210805T150534Z-avarab@gmail.com","subject":"[RFC PATCH 03/13] connect.c: refactor sending of agent & object-format","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-08-05T15:07:19Z","receivedAt":"2021-08-05T15:07:48Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Refactor the sending of the \"agent\" and \"object-format\" capabilities\ninto a function.\n\nThis was added in its current form in ab67235bc4 (connect: parse v2\nrefs with correct hash algorithm, 2020-05-25). When we connect to a v2\nserver we need to know about its object-format, and it needs to know\nabout ours. Since most things in connect.c and transport.c piggy-back\non the eager getting of remote refs via the handshake() those commands\ncan make use of the just-sent-over object-format by ls-refs.\n\nBut I'm about to add a command that may come after ls-refs, and may\nnot, but we need the server to know about our user-agent and\nobject-format. So let's split this into a function.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n connect.c | 33 ++++++++++++++++++++-------------\n 1 file changed, 20 insertions(+), 13 deletions(-)\n\ndiff --git a/connect.c b/connect.c\nindex 70b13389ba..18dfeae2b8 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -471,6 +471,24 @@ void check_stateless_delimiter(int stateless_rpc,\n \t\tdie(\"%s\", error);\n }\n \n+static void send_capabilities(int fd_out, struct packet_reader *reader)\n+{\n+\tconst char *hash_name;\n+\n+\tif (server_supports_v2(\"agent\", 0))\n+\t\tpacket_write_fmt(fd_out, \"agent=%s\", git_user_agent_sanitized());\n+\n+\tif (server_feature_v2(\"object-format\", &hash_name)) {\n+\t\tint hash_algo = hash_algo_by_name(hash_name);\n+\t\tif (hash_algo == GIT_HASH_UNKNOWN)\n+\t\t\tdie(_(\"unknown object format '%s' specified by server\"), hash_name);\n+\t\treader->hash_algo = &hash_algos[hash_algo];\n+\t\tpacket_write_fmt(fd_out, \"object-format=%s\", reader->hash_algo->name);\n+\t} else {\n+\t\treader->hash_algo = &hash_algos[GIT_HASH_SHA1];\n+\t}\n+}\n+\n struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n \t\t\t     struct ref **list, int for_push,\n \t\t\t     struct transport_ls_refs_options *transport_options,\n@@ -478,7 +496,6 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n \t\t\t     int stateless_rpc)\n {\n \tint i;\n-\tconst char *hash_name;\n \tstruct strvec *ref_prefixes = transport_options ?\n \t\t&transport_options->ref_prefixes : NULL;\n \tchar **unborn_head_target = transport_options ?\n@@ -488,18 +505,8 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n \tif (server_supports_v2(\"ls-refs\", 1))\n \t\tpacket_write_fmt(fd_out, \"command=ls-refs\\n\");\n \n-\tif (server_supports_v2(\"agent\", 0))\n-\t\tpacket_write_fmt(fd_out, \"agent=%s\", git_user_agent_sanitized());\n-\n-\tif (server_feature_v2(\"object-format\", &hash_name)) {\n-\t\tint hash_algo = hash_algo_by_name(hash_name);\n-\t\tif (hash_algo == GIT_HASH_UNKNOWN)\n-\t\t\tdie(_(\"unknown object format '%s' specified by server\"), hash_name);\n-\t\treader->hash_algo = &hash_algos[hash_algo];\n-\t\tpacket_write_fmt(fd_out, \"object-format=%s\", reader->hash_algo->name);\n-\t} else {\n-\t\treader->hash_algo = &hash_algos[GIT_HASH_SHA1];\n-\t}\n+\t/* Send capabilities */\n+\tsend_capabilities(fd_out, reader);\n \n \tif (server_options && server_options->nr &&\n \t    server_supports_v2(\"server-option\", 1))\n-- \n2.33.0.rc0.646.g585563e77f\n\n"},{"id":"432109","messageId":"RFC-patch-05.13-c4da15d9c0-20210805T150534Z-avarab@gmail.com","threadId":"56220","inReplyTo":"RFC-cover-00.13-0000000000-20210805T150534Z-avarab@gmail.com","subject":"[RFC PATCH 05/13] bundle-uri client: add \"git ls-remote-bundle-uri\"","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-08-05T15:07:21Z","receivedAt":"2021-08-05T15:07:51Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Add a git-ls-remote-bundle-uri command, this is a thin wrapper for\nissuing protocol v2 \"bundle-uri\" commands to a server, and to the\nparsing routines in bundle-uri.c.\n\nSince in the \"git clone\" case we'll have already done the handshake(),\nbut not here, introduce a \"got_advertisement\" state along with\n\"got_remote_heads\". It seems to me that the \"got_remote_heads\" is\nbadly named in the first place, and the whole logic of eagerly getting\nls-refs on handshake() or not could be refactored somewhat, but let's\nnot do that now, and instead just add another self-documenting state\nvariable.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n Documentation/git-ls-remote-bundle-uri.txt |  63 ++++++++++\n Documentation/git-ls-remote.txt            |   1 +\n Makefile                                   |   1 +\n builtin.h                                  |   1 +\n builtin/clone.c                            |   2 +-\n builtin/ls-remote-bundle-uri.c             |  90 ++++++++++++++\n command-list.txt                           |   1 +\n git.c                                      |   1 +\n t/lib-t5730-protocol-v2-bundle-uri.sh      | 132 +++++++++++++++++++++\n transport.c                                |  43 +++++--\n transport.h                                |   6 +-\n 11 files changed, 330 insertions(+), 11 deletions(-)\n create mode 100644 Documentation/git-ls-remote-bundle-uri.txt\n create mode 100644 builtin/ls-remote-bundle-uri.c\n\ndiff --git a/Documentation/git-ls-remote-bundle-uri.txt b/Documentation/git-ls-remote-bundle-uri.txt\nnew file mode 100644\nindex 0000000000..7d3d16a36f\n--- /dev/null\n+++ b/Documentation/git-ls-remote-bundle-uri.txt\n@@ -0,0 +1,63 @@\n+git-ls-remote-bundle-uri(1)\n+===========================\n+\n+NAME\n+----\n+git-ls-remote-bundle-uri - List 'bundle-uri' in a remote repository\n+\n+\n+SYNOPSIS\n+--------\n+[verse]\n+'git ls-remote-bundle-uri [--quiet] [--uri] [--upload-pack=<exec>]\n+\t[[-o | --server-option=]<option>] <repository>\n+\n+\n+DESCRIPTION\n+-----------\n+\n+Displays the `bundle-uri`s advertised by a remote repository. See\n+`bundle-uri` in link:technical/protocol-v2.html[the Git Wire Protocol,\n+Version 2] documentation for what the output format looks like.\n+\n+OPTIONS\n+-------\n+\n+-q::\n+--quiet::\n+\tDo not print remote URL to stderr in cases where the remote\n+\tname is inferred from config.\n++\n+When the remote name is not inferred (e.g. `git ls-remote-bundle-uri\n+origin`, or `git ls-remote-bundle-uri https://[...]`) the remote URL\n+is not printed in any case.\n+\n+--uri::\n+\tPrint only the URIs, and not any of their optional attributes.\n+\n+--upload-pack=<exec>::\n+\tSpecify the full path of 'git-upload-pack' on the remote\n+\thost. This allows listing references from repositories accessed via\n+\tSSH and where the SSH daemon does not use the PATH configured by the\n+\tuser.\n+\n+-o <option>::\n+--server-option=<option>::\n+\tTransmit the given string to the server when communicating using\n+\tprotocol version 2.  The given string must not contain a NUL or LF\n+\tcharacter.\n+\tWhen multiple `--server-option=<option>` are given, they are all\n+\tsent to the other side in the order listed on the command line.\n+\n+<repository>::\n+\tThe \"remote\" repository to query.  This parameter can be\n+\teither a URL or the name of a remote (see the GIT URLS and\n+\tREMOTES sections of linkgit:git-fetch[1]).\n+\n+SEE ALSO\n+--------\n+linkgit:git-ls-remote[1].\n+\n+GIT\n+---\n+Part of the linkgit:git[1] suite\ndiff --git a/Documentation/git-ls-remote.txt b/Documentation/git-ls-remote.txt\nindex 492e573856..86c07eff83 100644\n--- a/Documentation/git-ls-remote.txt\n+++ b/Documentation/git-ls-remote.txt\n@@ -114,6 +114,7 @@ c5db5456ae3b0873fc659c19fafdde22313cc441\trefs/tags/v0.99.2\n \n SEE ALSO\n --------\n+linkgit:git-ls-remote-bundle-uri[1].\n linkgit:git-check-ref-format[1].\n \n GIT\ndiff --git a/Makefile b/Makefile\nindex 6d3612b962..04ccb5af3a 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1117,6 +1117,7 @@ BUILTIN_OBJS += builtin/init-db.o\n BUILTIN_OBJS += builtin/interpret-trailers.o\n BUILTIN_OBJS += builtin/log.o\n BUILTIN_OBJS += builtin/ls-files.o\n+BUILTIN_OBJS += builtin/ls-remote-bundle-uri.o\n BUILTIN_OBJS += builtin/ls-remote.o\n BUILTIN_OBJS += builtin/ls-tree.o\n BUILTIN_OBJS += builtin/mailinfo.o\ndiff --git a/builtin.h b/builtin.h\nindex 16ecd5586f..9fd0529551 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -171,6 +171,7 @@ int cmd_log(int argc, const char **argv, const char *prefix);\n int cmd_log_reflog(int argc, const char **argv, const char *prefix);\n int cmd_ls_files(int argc, const char **argv, const char *prefix);\n int cmd_ls_tree(int argc, const char **argv, const char *prefix);\n+int cmd_ls_remote_bundle_uri(int argc, const char **argv, const char *prefix);\n int cmd_ls_remote(int argc, const char **argv, const char *prefix);\n int cmd_mailinfo(int argc, const char **argv, const char *prefix);\n int cmd_mailsplit(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 1e4e6be57d..80ef939904 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -1297,7 +1297,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \t * Populate transport->got_remote_bundle_uri and\n \t * transport->bundle_uri. We might get nothing.\n \t */\n-\ttransport_get_remote_bundle_uri(transport);\n+\ttransport_get_remote_bundle_uri(transport, 1);\n \n \tif (refs) {\n \t\tint hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport));\ndiff --git a/builtin/ls-remote-bundle-uri.c b/builtin/ls-remote-bundle-uri.c\nnew file mode 100644\nindex 0000000000..4e0950d581\n--- /dev/null\n+++ b/builtin/ls-remote-bundle-uri.c\n@@ -0,0 +1,90 @@\n+#include \"builtin.h\"\n+#include \"cache.h\"\n+#include \"transport.h\"\n+#include \"ref-filter.h\"\n+#include \"remote.h\"\n+#include \"refs.h\"\n+\n+static const char * const ls_remote_bundle_uri_usage[] = {\n+\tN_(\"git ls-remote-bundle-uri <repository>\"),\n+\tNULL\n+};\n+\n+int cmd_ls_remote_bundle_uri(int argc, const char **argv, const char *prefix)\n+{\n+\tconst char *dest = NULL;\n+\tint quiet = 0;\n+\tint uri = 0;\n+\tconst char *uploadpack = NULL;\n+\tstruct string_list server_options = STRING_LIST_INIT_DUP;\n+\tstruct remote *remote;\n+\tstruct transport *transport;\n+\tint status = 0;\n+\tstruct option options[] = {\n+\t\tOPT__QUIET(&quiet, N_(\"do not print remote URL\")),\n+\t\tOPT_BOOL(0, \"uri\", &uri, N_(\"limit to showing uri field\")),\n+\t\tOPT_STRING(0, \"upload-pack\", &uploadpack, N_(\"exec\"),\n+\t\t\t   N_(\"path of git-upload-pack on the remote host\")),\n+\t\tOPT_STRING_LIST('o', \"server-option\", &server_options,\n+\t\t\t\tN_(\"server-specific\"),\n+\t\t\t\tN_(\"option to transmit\")),\n+\t\tOPT_END()\n+\t};\n+\tstruct string_list_item *item;\n+\n+\targc = parse_options(argc, argv, prefix, options, ls_remote_bundle_uri_usage,\n+\t\t\t     PARSE_OPT_STOP_AT_NON_OPTION);\n+\tdest = argv[0];\n+\n+\tpacket_trace_identity(\"ls-remote-bundle-uri\");\n+\n+\tremote = remote_get(dest);\n+\tif (!remote) {\n+\t\tif (dest)\n+\t\t\tdie(\"bad repository '%s'\", dest);\n+\t\tdie(\"No remote configured to get bundle URIs from.\");\n+\t}\n+\tif (!remote->url_nr)\n+\t\tdie(\"remote %s has no configured URL\", dest);\n+\n+\ttransport = transport_get(remote, NULL);\n+\tif (uploadpack)\n+\t\ttransport_set_option(transport, TRANS_OPT_UPLOADPACK, uploadpack);\n+\tif (server_options.nr)\n+\t\ttransport->server_options = &server_options;\n+\n+\tif (!dest && !quiet)\n+\t\tfprintf(stderr, \"From %s\\n\", *remote->url);\n+\n+\tif (transport_get_remote_bundle_uri(transport, 0) < 0) {\n+\t\terror(_(\"could not get the bundle-uri list\"));\n+\t\tstatus = 1;\n+\t\tgoto cleanup;\n+\t}\n+\n+\tfor_each_string_list_item(item, &transport->bundle_uri) {\n+\t\tstruct string_list_item *kv_item;\n+\t\tstruct string_list *kv = item->util;\n+\n+\t\tfprintf(stdout, \"%s\", item->string);\n+\t\tif (uri || !kv) {\n+\t\t\tfprintf(stdout, \"\\n\");\n+\t\t\tcontinue;\n+\t\t}\n+\t\tfor_each_string_list_item(kv_item, kv) {\n+\t\t\tconst char *k = kv_item->string;\n+\t\t\tconst char *v = kv_item->util;\n+\n+\t\t\tif (v)\n+\t\t\t\tfprintf(stdout, \" %s=%s\", k, v);\n+\t\t\telse\n+\t\t\t\tfprintf(stdout, \" %s\", k);\n+\t\t}\n+\t\tfprintf(stdout, \"\\n\");\n+\t}\n+\n+cleanup:\n+\tif (transport_disconnect(transport))\n+\t\treturn 1;\n+\treturn status;\n+}\ndiff --git a/command-list.txt b/command-list.txt\nindex a289f09ed6..fa5d9596f1 100644\n--- a/command-list.txt\n+++ b/command-list.txt\n@@ -115,6 +115,7 @@ gitk                                    mainporcelain\n git-log                                 mainporcelain           info\n git-ls-files                            plumbinginterrogators\n git-ls-remote                           plumbinginterrogators\n+git-ls-remote-bundle-uri                plumbinginterrogators\n git-ls-tree                             plumbinginterrogators\n git-mailinfo                            purehelpers\n git-mailsplit                           purehelpers\ndiff --git a/git.c b/git.c\nindex 18bed9a996..b902732936 100644\n--- a/git.c\n+++ b/git.c\n@@ -545,6 +545,7 @@ static struct cmd_struct commands[] = {\n \t{ \"log\", cmd_log, RUN_SETUP },\n \t{ \"ls-files\", cmd_ls_files, RUN_SETUP },\n \t{ \"ls-remote\", cmd_ls_remote, RUN_SETUP_GENTLY },\n+\t{ \"ls-remote-bundle-uri\", cmd_ls_remote_bundle_uri, RUN_SETUP_GENTLY },\n \t{ \"ls-tree\", cmd_ls_tree, RUN_SETUP },\n \t{ \"mailinfo\", cmd_mailinfo, RUN_SETUP_GENTLY | NO_PARSEOPT },\n \t{ \"mailsplit\", cmd_mailsplit, NO_PARSEOPT },\ndiff --git a/t/lib-t5730-protocol-v2-bundle-uri.sh b/t/lib-t5730-protocol-v2-bundle-uri.sh\nindex 6bd9d2dcfb..fccf7ccaa2 100644\n--- a/t/lib-t5730-protocol-v2-bundle-uri.sh\n+++ b/t/lib-t5730-protocol-v2-bundle-uri.sh\n@@ -138,3 +138,135 @@ test_expect_success !T5730_HTTP \"bad client with $T5730_PROTOCOL:// using protoc\n \tgrep \"clone> test-bad-client$\" log >sent-bad-request &&\n \ttest_file_not_empty sent-bad-request\n '\n+\n+test_expect_success \"ls-remote-bundle-uri with $T5730_PROTOCOL:// using protocol v2\" '\n+\ttest_when_finished \"rm -f log\" &&\n+\n+\ttest_config -C \"$T5730_PARENT\" uploadpack.bundleURI \\\n+\t\t\"$T5730_BUNDLE_URI_ESCAPED\" &&\n+\n+\t# All data about bundle URIs\n+\tcat >expect <<-EOF &&\n+\t$T5730_BUNDLE_URI_ESCAPED\n+\tEOF\n+\tGIT_TRACE_PACKET=\"$PWD/log\" \\\n+\tgit \\\n+\t\t-c protocol.version=2 \\\n+\t\tls-remote-bundle-uri \\\n+\t\t\"$T5730_URI\" \\\n+\t\t>actual &&\n+\ttest_cmp expect actual &&\n+\n+\t# Only the URIs\n+\tgit \\\n+\t\t-c protocol.version=2 \\\n+\t\tls-remote-bundle-uri --uri \\\n+\t\t\"$T5730_URI\" \\\n+\t\t>actual2 &&\n+\ttest_cmp actual actual2\n+'\n+\n+test_expect_success \"ls-remote-bundle-uri with $T5730_PROTOCOL:// using protocol v2\" '\n+\ttest_when_finished \"rm -f log\" &&\n+\n+\tATTR=\"foo bar=baz\" &&\n+\ttest_config -C \"$T5730_PARENT\" uploadpack.bundleURI \\\n+\t\t\"$T5730_BUNDLE_URI_ESCAPED $ATTR\" &&\n+\n+\t# All data about bundle URIs\n+\tcat >expect <<-EOF &&\n+\t$T5730_BUNDLE_URI_ESCAPED $ATTR\n+\tEOF\n+\tGIT_TRACE_PACKET=\"$PWD/log\" \\\n+\tgit \\\n+\t\t-c protocol.version=2 \\\n+\t\tls-remote-bundle-uri \\\n+\t\t\"$T5730_URI\" \\\n+\t\t>actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"ls-remote-bundle-uri with $T5730_PROTOCOL:// using protocol v2: --uri\" '\n+\ttest_when_finished \"rm -f log\" &&\n+\n+\tATTR=\"foo bar=baz\" &&\n+\ttest_config -C \"$T5730_PARENT\" uploadpack.bundleURI \\\n+\t\t\"$T5730_BUNDLE_URI_ESCAPED $ATTR\" &&\n+\n+\t# All data about bundle URIs\n+\tcat >expect <<-EOF &&\n+\t$T5730_BUNDLE_URI_ESCAPED\n+\tEOF\n+\tGIT_TRACE_PACKET=\"$PWD/log\" \\\n+\tgit \\\n+\t\t-c protocol.version=2 \\\n+\t\tls-remote-bundle-uri \\\n+\t\t--uri \\\n+\t\t\"$T5730_URI\" \\\n+\t\t>actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"ls-remote-bundle-uri --[no-]quiet with $T5730_PROTOCOL:// using protocol v2\" '\n+\ttest_when_finished \"rm -f log\" &&\n+\n+\tcat >err.expect <<-\\EOF &&\n+\tCloning into '\"'\"'child'\"'\"'...\n+\tEOF\n+\n+\ttest_when_finished \"rm -rf child\" &&\n+\tGIT_TRACE_PACKET=\"$PWD/log\" \\\n+\tgit \\\n+\t\t-c protocol.version=2 \\\n+\t\t clone \"$T5730_URI\" child \\\n+\t\t >out 2>err.actual &&\n+\ttest_cmp err.expect err.actual &&\n+\ttest_must_be_empty out &&\n+\n+\ttest_config -C \"$T5730_PARENT\" uploadpack.bundleURI \\\n+\t\t\"$T5730_BUNDLE_URI_ESCAPED\" &&\n+\n+\t# Without --[no-]quiet\n+\tcat >out.expect <<-EOF &&\n+\t$T5730_BUNDLE_URI_ESCAPED\n+\tEOF\n+\tcat >err.expect <<-EOF &&\n+\tFrom $T5730_URI\n+\tEOF\n+\tgit \\\n+\t\t-C child \\\n+\t\t -c protocol.version=2 \\\n+\t\tls-remote-bundle-uri \\\n+\t\t>out.actual 2>err.actual &&\n+\ttest_cmp err.expect err.actual &&\n+\ttest_cmp out.expect out.actual &&\n+\n+\t# --no-quiet is the default\n+\tgit \\\n+\t\t-C child \\\n+\t\t-c protocol.version=2 \\\n+\t\tls-remote-bundle-uri \\\n+\t\t--no-quiet \\\n+\t\t>out.actual 2>err.actual &&\n+\ttest_cmp err.expect err.actual &&\n+\ttest_cmp out.expect out.actual &&\n+\n+\t# --quiet quiets the \"From\" line\n+\tgit \\\n+\t\t-C child \\\n+\t\t-c protocol.version=2 \\\n+\t\tls-remote-bundle-uri \\\n+\t\t--quiet \\\n+\t\t>out.actual 2>err &&\n+\ttest_must_be_empty err &&\n+\ttest_cmp out.expect out.actual &&\n+\n+\t# --quiet is implicit if the remote is not implicit\n+\tgit \\\n+\t\t-c protocol.version=2 \\\n+\t\tls-remote-bundle-uri \\\n+\t\t\"$T5730_URI\" \\\n+\t\t>out.actual 2>err &&\n+\ttest_must_be_empty err &&\n+\ttest_cmp out.expect out.actual\n+'\ndiff --git a/transport.c b/transport.c\nindex 444cf74756..286c96269d 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -187,6 +187,7 @@ struct git_transport_data {\n \tstruct git_transport_options options;\n \tstruct child_process *conn;\n \tint fd[2];\n+\tunsigned got_advertisement : 1;\n \tunsigned got_remote_heads : 1;\n \tenum protocol_version version;\n \tstruct oid_array extra_have;\n@@ -332,6 +333,7 @@ static struct ref *handshake(struct transport *transport, int for_push,\n \t\tBUG(\"unknown protocol version\");\n \t}\n \tdata->got_remote_heads = 1;\n+\tdata->got_advertisement = 1;\n \ttransport->hash_algo = reader.hash_algo;\n \n \tif (reader.line_peeked)\n@@ -353,6 +355,33 @@ static int get_bundle_uri(struct transport *transport)\n \tint stateless_rpc = transport->stateless_rpc;\n \tstring_list_init(&transport->bundle_uri, 1);\n \n+\tif (!data->got_advertisement) {\n+\t\tstruct ref *refs;\n+\t\tstruct git_transport_data *data = transport->data;\n+\t\tenum protocol_version version;\n+\n+\t\trefs = handshake(transport, 0, NULL, 0);\n+\t\tversion = data->version;\n+\n+\t\tswitch (version) {\n+\t\tcase protocol_v2:\n+\t\t\tassert(!refs);\n+\t\t\tbreak;\n+\t\tcase protocol_v0:\n+\t\tcase protocol_v1:\n+\t\tcase protocol_unknown_version:\n+\t\t\tassert(refs);\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\t/*\n+\t * \"Support\" protocol v0 and v2 without bundle-uri support by\n+\t * silently degrading to a NOOP.\n+\t */\n+\tif (!server_supports_v2(\"bundle-uri\", 0))\n+\t\treturn 0;\n+\n \tpacket_reader_init(&reader, data->fd[0], NULL, 0,\n \t\t\t   PACKET_READ_CHOMP_NEWLINE |\n \t\t\t   PACKET_READ_GENTLE_ON_EOF);\n@@ -1472,7 +1501,7 @@ int transport_fetch_refs(struct transport *transport, struct ref *refs)\n \treturn rc;\n }\n \n-int transport_get_remote_bundle_uri(struct transport *transport)\n+int transport_get_remote_bundle_uri(struct transport *transport, int quiet)\n {\n \tconst struct transport_vtable *vtable = transport->vtable;\n \n@@ -1480,20 +1509,16 @@ int transport_get_remote_bundle_uri(struct transport *transport)\n \tif (transport->got_remote_bundle_uri++)\n \t\treturn 0;\n \n-\t/*\n-\t * \"Support\" protocol v0 and v2 without bundle-uri support by\n-\t * silently degrading to a NOOP.\n-\t */\n-\tif (!server_supports_v2(\"bundle-uri\", 0))\n-\t\treturn 0;\n-\n \t/*\n \t * This is intentionally below the transport.injectBundleURI,\n \t * we want to be able to inject into protocol v0, or into the\n \t * dialog of a server who doesn't support this.\n \t */\n-\tif (!vtable->get_bundle_uri)\n+\tif (!vtable->get_bundle_uri) {\n+\t\tif (quiet)\n+\t\t\treturn -1;\n \t\treturn error(_(\"bundle-uri operation not supported by protocol\"));\n+\t}\n \n \tif (vtable->get_bundle_uri(transport) < 0)\n \t\treturn error(_(\"could not retrieve server-advertised bundle-uri list\"));\ndiff --git a/transport.h b/transport.h\nindex fb9f89f956..14c3298583 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -285,8 +285,12 @@ const struct ref *transport_get_remote_refs(struct transport *transport,\n /**\n  * Retrieve bundle URI(s) from a remote. Populates \"struct\n  * transport\"'s \"bundle_uri\" and \"got_remote_bundle_uri\".\n+ *\n+ * With `quiet=1` it will not complain if the serve doesn't support\n+ * the protocol, but only if we discover the server uses it, and\n+ * encounter issues then.\n  */\n-int transport_get_remote_bundle_uri(struct transport *transport);\n+int transport_get_remote_bundle_uri(struct transport *transport, int quiet);\n \n /*\n  * Fetch the hash algorithm used by a remote.\n-- \n2.33.0.rc0.646.g585563e77f\n\n"},{"id":"432110","messageId":"RFC-patch-07.13-f0e4052de4-20210805T150534Z-avarab@gmail.com","threadId":"56220","inReplyTo":"RFC-cover-00.13-0000000000-20210805T150534Z-avarab@gmail.com","subject":"[RFC PATCH 07/13] bundle-uri client: add boolean transfer.bundleURI setting","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-08-05T15:07:23Z","receivedAt":"2021-08-05T15:07:52Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"The yet-to-be introduced client support for bundle-uri will always\nfall back on a full clone, but we'd still like to be able to ignore a\nserver's bundle-uri advertisement entirely.\n\nThis is useful for testing, and if a server is pointing to bad\nbundles, they take a while to time out etc.\n\nSince we might see the config in any order we need to clear out any\naccumulated bundle_uri list when we see transfer.bundleURI=false\nsetting, and not add any more things to the list.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n Documentation/config/transfer.txt |  6 ++++++\n transport.c                       | 15 ++++++++++++++-\n 2 files changed, 20 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/config/transfer.txt b/Documentation/config/transfer.txt\nindex a9aff7f982..1ed026421c 100644\n--- a/Documentation/config/transfer.txt\n+++ b/Documentation/config/transfer.txt\n@@ -74,6 +74,12 @@ transfer.advertiseSID::\n \tBoolean. When true, client and server processes will advertise their\n \tunique session IDs to their remote counterpart. Defaults to false.\n \n+transfer.bundleURI::\n+\tWhen set to `false` ignores any server advertisement of\n+\t`bundle-uri` and proceed with a \"normal\" clone/fetch even if\n+\tusing bundles to bootstap is possible. Defaults to `true`,\n+\ti.e. bundle-uri is tried whenever a server offers it.\n+\n transfer.injectBundleURI::\n \tAllows for the injection of `bundle-uri` lines into the\n \tprotocol v2 transport dialog (see `protocol.version` in\ndiff --git a/transport.c b/transport.c\nindex bd8a914652..80d1857374 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -1503,6 +1503,7 @@ int transport_fetch_refs(struct transport *transport, struct ref *refs)\n \n struct config_cb {\n \tstruct transport *transport;\n+\tint disabled;\n \tint configured;\n \tint ret;\n };\n@@ -1513,7 +1514,15 @@ static int bundle_uri_config(const char *var, const char *value, void *data)\n \tstruct transport *transport = cb->transport;\n \tstruct string_list *uri = &transport->bundle_uri;\n \n-\tif (!strcmp(var, \"transfer.injectbundleuri\")) {\n+\tif (!strcmp(var, \"transfer.bundleuri\")) {\n+\t\tcb->disabled = !git_config_bool(var, value);\n+\t\tif (cb->disabled)\n+\t\t\tbundle_uri_string_list_clear(uri);\n+\t\treturn 0;\n+\t}\n+\n+\tif (!cb->disabled &&\n+\t    !strcmp(var, \"transfer.injectbundleuri\")) {\n \t\tcb->configured = 1;\n \t\tif (!value)\n \t\t\tcb->ret = error(_(\"bad (empty) transfer.injectBundleURI\"));\n@@ -1538,6 +1547,10 @@ int transport_get_remote_bundle_uri(struct transport *transport, int quiet)\n \n \tgit_config(bundle_uri_config, &cb);\n \n+\t/* Don't use bundle-uri at all */\n+\tif (cb.disabled)\n+\t\treturn 0;\n+\n \t/* Our own config can fake it up with transport.injectBundleURI */\n \tif (cb.configured)\n \t\treturn cb.ret;\n-- \n2.33.0.rc0.646.g585563e77f\n\n"},{"id":"432111","messageId":"RFC-patch-09.13-20646ee07e-20210805T150534Z-avarab@gmail.com","threadId":"56220","inReplyTo":"RFC-cover-00.13-0000000000-20210805T150534Z-avarab@gmail.com","subject":"[RFC PATCH 09/13] fetch-pack: add a deref_without_lazy_fetch_extended()","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-08-05T15:07:25Z","receivedAt":"2021-08-05T15:07:54Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Add a version of the deref_without_lazy_fetch function which can be\ncalled with custom oi_flags and to grab information about the\n\"object_type\". This will be used for the bundle-uri client in a\nsubsequent commit.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n fetch-pack.c | 25 ++++++++++++++++++-------\n 1 file changed, 18 insertions(+), 7 deletions(-)\n\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex b0c7be717c..b0b21cc969 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -114,17 +114,18 @@ static void for_each_cached_alternate(struct fetch_negotiator *negotiator,\n \t\tcb(negotiator, cache.items[i]);\n }\n \n-static struct commit *deref_without_lazy_fetch(const struct object_id *oid,\n-\t\t\t\t\t       int mark_tags_complete)\n+static struct commit *deref_without_lazy_fetch_extended(const struct object_id *oid,\n+\t\t\t\t\t\t\tint mark_tags_complete,\n+\t\t\t\t\t\t\tenum object_type *type,\n+\t\t\t\t\t\t\tunsigned int oi_flags)\n {\n-\tenum object_type type;\n-\tstruct object_info info = { .typep = &type };\n+\tstruct object_info info = { .typep = type };\n \n \twhile (1) {\n \t\tif (oid_object_info_extended(the_repository, oid, &info,\n-\t\t\t\t\t     OBJECT_INFO_SKIP_FETCH_OBJECT | OBJECT_INFO_QUICK))\n+\t\t\t\t\t     oi_flags))\n \t\t\treturn NULL;\n-\t\tif (type == OBJ_TAG) {\n+\t\tif (*type == OBJ_TAG) {\n \t\t\tstruct tag *tag = (struct tag *)\n \t\t\t\tparse_object(the_repository, oid);\n \n@@ -137,11 +138,21 @@ static struct commit *deref_without_lazy_fetch(const struct object_id *oid,\n \t\t\tbreak;\n \t\t}\n \t}\n-\tif (type == OBJ_COMMIT)\n+\tif (*type == OBJ_COMMIT)\n \t\treturn (struct commit *) parse_object(the_repository, oid);\n \treturn NULL;\n }\n \n+\n+static struct commit *deref_without_lazy_fetch(const struct object_id *oid,\n+\t\t\t\t\t       int mark_tags_complete)\n+{\n+\tenum object_type type;\n+\tunsigned flags = OBJECT_INFO_SKIP_FETCH_OBJECT | OBJECT_INFO_QUICK;\n+\treturn deref_without_lazy_fetch_extended(oid, mark_tags_complete,\n+\t\t\t\t\t\t &type, flags);\n+}\n+\n static int rev_list_insert_ref(struct fetch_negotiator *negotiator,\n \t\t\t       const struct object_id *oid)\n {\n-- \n2.33.0.rc0.646.g585563e77f\n\n"},{"id":"432112","messageId":"RFC-patch-06.13-3427cb4de7-20210805T150534Z-avarab@gmail.com","threadId":"56220","inReplyTo":"RFC-cover-00.13-0000000000-20210805T150534Z-avarab@gmail.com","subject":"[RFC PATCH 06/13] bundle-uri client: add transfer.injectBundleURI support","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-08-05T15:07:22Z","receivedAt":"2021-08-05T15:07:55Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Add the ability to inject \"fake\" bundle URIs into the newly supported\nbundle-uri dialog. As discussed in the added documentation this allows\nus to pretend as though the remote supports bundle URIs.\n\nThis will be useful both for ad-hoc testing, and for the real use-case\nof retrofitting bundle URI support on-the-fly, i.e. to have:\n\n\tgit -c transfer.injectBundleURI \"file://$(pwd)/local.bdl\" \\\n\tclone https://example.com/git.git\"\n\nBe similar in spirit to:\n\n\tgit clone --reference local-clone.git --disassociate \\\n\thttps://example.com/git.git\"\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n Documentation/config/transfer.txt     | 20 ++++++++++++\n t/lib-t5730-protocol-v2-bundle-uri.sh | 46 +++++++++++++++++++++++++++\n transport.c                           | 33 +++++++++++++++++++\n 3 files changed, 99 insertions(+)\n\ndiff --git a/Documentation/config/transfer.txt b/Documentation/config/transfer.txt\nindex 505126a780..a9aff7f982 100644\n--- a/Documentation/config/transfer.txt\n+++ b/Documentation/config/transfer.txt\n@@ -73,3 +73,23 @@ transfer.unpackLimit::\n transfer.advertiseSID::\n \tBoolean. When true, client and server processes will advertise their\n \tunique session IDs to their remote counterpart. Defaults to false.\n+\n+transfer.injectBundleURI::\n+\tAllows for the injection of `bundle-uri` lines into the\n+\tprotocol v2 transport dialog (see `protocol.version` in\n+\tlinkgit:git-config[1]). See `bundle-uri` in\n+\tlink:technical/protocol-v2.html[the Git Wire Protocol, Version\n+\t2] documentation for what the format looks like.\n++\n+Can be given more than once, each key being injected as one line into\n+the dialog.\n++\n+This is useful for testing the `bundle-uri` facility, and to e.g. use\n+linkgit:git-clone[1] to clone from a server which does not support\n+`bundle-uri`, but where the clone can benefit from getting some or\n+most of the data from a static bundle retrieved from elsewhere.\n++\n+Impacts any command that uses the transport to communicate with remote\n+linkgit:git-upload-pack[1] processes, e.g. linkgit:git-clone[1],\n+linkgit:git-fetch[1] and the linkgit:git-ls-remote-bundle-uri[1]\n+inspection command, this includes the `file://` protocol.\ndiff --git a/t/lib-t5730-protocol-v2-bundle-uri.sh b/t/lib-t5730-protocol-v2-bundle-uri.sh\nindex fccf7ccaa2..e5d17f6a81 100644\n--- a/t/lib-t5730-protocol-v2-bundle-uri.sh\n+++ b/t/lib-t5730-protocol-v2-bundle-uri.sh\n@@ -270,3 +270,49 @@ test_expect_success \"ls-remote-bundle-uri --[no-]quiet with $T5730_PROTOCOL:// u\n \ttest_must_be_empty err &&\n \ttest_cmp out.expect out.actual\n '\n+\n+test_expect_success \"ls-remote-bundle-uri with -c transfer.injectBundleURI using with $T5730_PROTOCOL:// using protocol v2\" '\n+\ttest_when_finished \"rm -f log\" &&\n+\n+\ttest_config -C \"$T5730_PARENT\" uploadpack.bundleURI \\\n+\t\t\"$T5730_BUNDLE_URI_ESCAPED\" &&\n+\n+\tcat >expect <<-\\EOF &&\n+\thttps://injected.example.com/fake-1.bdl\n+\thttps://injected.example.com/fake-2.bdl\n+\tEOF\n+\tGIT_TRACE_PACKET=\"$PWD/log\" \\\n+\tgit \\\n+\t\t-c protocol.version=2 \\\n+\t\t-c transfer.injectBundleURI=\"https://injected.example.com/fake-1.bdl\" \\\n+\t\t-c transfer.injectBundleURI=\"https://injected.example.com/fake-2.bdl\" \\\n+\t\tls-remote-bundle-uri \\\n+\t\t\"$T5730_URI\" \\\n+\t\t>actual 2>err &&\n+\ttest_cmp expect actual &&\n+\ttest_path_is_missing log\n+'\n+\n+test_expect_success \"ls-remote-bundle-uri with bad -c transfer.injectBundleURI protocol v2 with $T5730_PROTOCOL://\" '\n+\ttest_when_finished \"rm -f log\" &&\n+\n+\ttest_config -C \"$T5730_PARENT\" uploadpack.bundleURI \\\n+\t\t\"$T5730_BUNDLE_URI_ESCAPED\" &&\n+\n+\tcat >err.expect <<-\\EOF &&\n+\terror: bad (empty) transfer.injectBundleURI\n+\terror: could not get the bundle-uri list\n+\tEOF\n+\n+\ttest_must_fail env \\\n+\t\tGIT_TRACE_PACKET=\"$PWD/log\" \\\n+\t\tgit \\\n+\t\t-c protocol.version=2 \\\n+\t\t-c transfer.injectBundleURI \\\n+\t\tls-remote-bundle-uri \\\n+\t\t\"$T5730_URI\" \\\n+\t\t>out 2>err.actual &&\n+\ttest_must_be_empty out &&\n+\ttest_cmp err.expect err.actual &&\n+\ttest_path_is_missing log\n+'\ndiff --git a/transport.c b/transport.c\nindex 286c96269d..bd8a914652 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -1501,14 +1501,47 @@ int transport_fetch_refs(struct transport *transport, struct ref *refs)\n \treturn rc;\n }\n \n+struct config_cb {\n+\tstruct transport *transport;\n+\tint configured;\n+\tint ret;\n+};\n+\n+static int bundle_uri_config(const char *var, const char *value, void *data)\n+{\n+\tstruct config_cb *cb = data;\n+\tstruct transport *transport = cb->transport;\n+\tstruct string_list *uri = &transport->bundle_uri;\n+\n+\tif (!strcmp(var, \"transfer.injectbundleuri\")) {\n+\t\tcb->configured = 1;\n+\t\tif (!value)\n+\t\t\tcb->ret = error(_(\"bad (empty) transfer.injectBundleURI\"));\n+\t\telse if (bundle_uri_parse_line(uri, value) < 0)\n+\t\t\tcb->ret = error(_(\"bad transfer.injectBundleURI: '%s'\"),\n+\t\t\t\t\tvalue);\n+\t\treturn 0;\n+\t}\n+\treturn 0;\n+}\n+\n int transport_get_remote_bundle_uri(struct transport *transport, int quiet)\n {\n \tconst struct transport_vtable *vtable = transport->vtable;\n+\tstruct config_cb cb = {\n+\t\t.transport = transport,\n+\t};\n \n \t/* Lazily configured */\n \tif (transport->got_remote_bundle_uri++)\n \t\treturn 0;\n \n+\tgit_config(bundle_uri_config, &cb);\n+\n+\t/* Our own config can fake it up with transport.injectBundleURI */\n+\tif (cb.configured)\n+\t\treturn cb.ret;\n+\n \t/*\n \t * This is intentionally below the transport.injectBundleURI,\n \t * we want to be able to inject into protocol v0, or into the\n-- \n2.33.0.rc0.646.g585563e77f\n\n"},{"id":"432113","messageId":"RFC-patch-08.13-c9700e66d9-20210805T150534Z-avarab@gmail.com","threadId":"56220","inReplyTo":"RFC-cover-00.13-0000000000-20210805T150534Z-avarab@gmail.com","subject":"[RFC PATCH 08/13] bundle.h: make \"fd\" version of read_bundle_header() public","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-08-05T15:07:24Z","receivedAt":"2021-08-05T15:07:56Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Change the parse_bundle_header() function to be non-static, and rename\nit to parse_bundle_header_fd(). The parse_bundle_header() function is\nalready public, and it's a thin wrapper around this function. This\nwill be used by code that wants to pass a fd to the bundle API.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n bundle.c | 8 ++++----\n bundle.h | 2 ++\n 2 files changed, 6 insertions(+), 4 deletions(-)\n\ndiff --git a/bundle.c b/bundle.c\nindex ab63f40226..f3a87308cf 100644\n--- a/bundle.c\n+++ b/bundle.c\n@@ -61,8 +61,8 @@ static int parse_bundle_signature(struct bundle_header *header, const char *line\n \treturn -1;\n }\n \n-static int parse_bundle_header(int fd, struct bundle_header *header,\n-\t\t\t       const char *report_path)\n+int read_bundle_header_fd(int fd, struct bundle_header *header,\n+\t\t\t  const char *report_path)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n \tint status = 0;\n@@ -138,7 +138,7 @@ int read_bundle_header(const char *path, struct bundle_header *header)\n \n \tif (fd < 0)\n \t\treturn error(_(\"could not open '%s'\"), path);\n-\treturn parse_bundle_header(fd, header, path);\n+\treturn read_bundle_header_fd(fd, header, path);\n }\n \n int is_bundle(const char *path, int quiet)\n@@ -148,7 +148,7 @@ int is_bundle(const char *path, int quiet)\n \n \tif (fd < 0)\n \t\treturn 0;\n-\tfd = parse_bundle_header(fd, &header, quiet ? NULL : path);\n+\tfd = read_bundle_header_fd(fd, &header, quiet ? NULL : path);\n \tif (fd >= 0)\n \t\tclose(fd);\n \tbundle_header_release(&header);\ndiff --git a/bundle.h b/bundle.h\nindex 1927d8cd6a..8adf0e7393 100644\n--- a/bundle.h\n+++ b/bundle.h\n@@ -22,6 +22,8 @@ void bundle_header_release(struct bundle_header *header);\n \n int is_bundle(const char *path, int quiet);\n int read_bundle_header(const char *path, struct bundle_header *header);\n+int read_bundle_header_fd(int fd, struct bundle_header *header,\n+\t\t\t  const char *report_path);\n int create_bundle(struct repository *r, const char *path,\n \t\t  int argc, const char **argv, struct strvec *pack_options,\n \t\t  int version);\n-- \n2.33.0.rc0.646.g585563e77f\n\n"},{"id":"432114","messageId":"RFC-patch-10.13-f04cf22da4-20210805T150534Z-avarab@gmail.com","threadId":"56220","inReplyTo":"RFC-cover-00.13-0000000000-20210805T150534Z-avarab@gmail.com","subject":"[RFC PATCH 10/13] fetch-pack: move --keep=* option filling to a function","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-08-05T15:07:26Z","receivedAt":"2021-08-05T15:07:58Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Move the populating of the --keep=* option argument to \"index-pack\" to\na static function, a subsequent commit will make use of it in another\nfunction.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n fetch-pack.c | 20 ++++++++++++--------\n 1 file changed, 12 insertions(+), 8 deletions(-)\n\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex b0b21cc969..ef07b45072 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -826,6 +826,16 @@ static void parse_gitmodules_oids(int fd, struct oidset *gitmodules_oids)\n \t} while (1);\n }\n \n+static void add_index_pack_keep_option(struct strvec *args)\n+{\n+\tchar hostname[HOST_NAME_MAX + 1];\n+\n+\tif (xgethostname(hostname, sizeof(hostname)))\n+\t\txsnprintf(hostname, sizeof(hostname), \"localhost\");\n+\tstrvec_pushf(args, \"--keep=fetch-pack %\"PRIuMAX \" on %s\",\n+\t\t     (uintmax_t)getpid(), hostname);\n+}\n+\n /*\n  * If packfile URIs were provided, pass a non-NULL pointer to index_pack_args.\n  * The strings to pass as the --index-pack-arg arguments to http-fetch will be\n@@ -895,14 +905,8 @@ static int get_pack(struct fetch_pack_args *args,\n \t\t\tstrvec_push(&cmd.args, \"-v\");\n \t\tif (args->use_thin_pack)\n \t\t\tstrvec_push(&cmd.args, \"--fix-thin\");\n-\t\tif ((do_keep || index_pack_args) && (args->lock_pack || unpack_limit)) {\n-\t\t\tchar hostname[HOST_NAME_MAX + 1];\n-\t\t\tif (xgethostname(hostname, sizeof(hostname)))\n-\t\t\t\txsnprintf(hostname, sizeof(hostname), \"localhost\");\n-\t\t\tstrvec_pushf(&cmd.args,\n-\t\t\t\t     \"--keep=fetch-pack %\"PRIuMAX \" on %s\",\n-\t\t\t\t     (uintmax_t)getpid(), hostname);\n-\t\t}\n+\t\tif ((do_keep || index_pack_args) && (args->lock_pack || unpack_limit))\n+\t\t\tadd_index_pack_keep_option(&cmd.args);\n \t\tif (!index_pack_args && args->check_self_contained_and_connected)\n \t\t\tstrvec_push(&cmd.args, \"--check-self-contained-and-connected\");\n \t\telse\n-- \n2.33.0.rc0.646.g585563e77f\n\n"},{"id":"432115","messageId":"RFC-patch-11.13-7db2684d63-20210805T150534Z-avarab@gmail.com","threadId":"56220","inReplyTo":"RFC-cover-00.13-0000000000-20210805T150534Z-avarab@gmail.com","subject":"[RFC PATCH 11/13] index-pack: add --progress-title option","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-08-05T15:07:27Z","receivedAt":"2021-08-05T15:08:01Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Add a --progress-title option to index-pack, when data is piped into\nindex-pack its progress is a proxy for whatever's feeding it\ndata. This option will allow us to set a more relevant progress bar\ntitle.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n Documentation/git-index-pack.txt | 6 ++++++\n builtin/index-pack.c             | 6 ++++++\n 2 files changed, 12 insertions(+)\n\ndiff --git a/Documentation/git-index-pack.txt b/Documentation/git-index-pack.txt\nindex 7fa74b9e79..9fd5d8a2d4 100644\n--- a/Documentation/git-index-pack.txt\n+++ b/Documentation/git-index-pack.txt\n@@ -82,6 +82,12 @@ OPTIONS\n --strict::\n \tDie, if the pack contains broken objects or links.\n \n+--progress-title::\n+\tFor internal use only.\n++\n+Set the title of the \"Receiving objects\" progress bar (it's \"Indexing\n+objects\" under `--stdin`).\n+\n --check-self-contained-and-connected::\n \tDie if the pack contains broken links. For internal use only.\n \ndiff --git a/builtin/index-pack.c b/builtin/index-pack.c\nindex 8336466865..0841c039ae 100644\n--- a/builtin/index-pack.c\n+++ b/builtin/index-pack.c\n@@ -122,6 +122,7 @@ static int strict;\n static int do_fsck_object;\n static struct fsck_options fsck_options = FSCK_OPTIONS_MISSING_GITMODULES;\n static int verbose;\n+static const char *progress_title;\n static int show_resolving_progress;\n static int show_stat;\n static int check_self_contained_and_connected;\n@@ -1157,6 +1158,7 @@ static void parse_pack_objects(unsigned char *hash)\n \n \tif (verbose)\n \t\tprogress = start_progress(\n+\t\t\t\tprogress_title ? progress_title :\n \t\t\t\tfrom_stdin ? _(\"Receiving objects\") : _(\"Indexing objects\"),\n \t\t\t\tnr_objects);\n \tfor (i = 0; i < nr_objects; i++) {\n@@ -1806,6 +1808,10 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)\n \t\t\t\tinput_len = sizeof(*hdr);\n \t\t\t} else if (!strcmp(arg, \"-v\")) {\n \t\t\t\tverbose = 1;\n+\t\t\t} else if (!strcmp(arg, \"--progress-title\")) {\n+\t\t\t\tif (progress_title || (i+1) >= argc)\n+\t\t\t\t\tusage(index_pack_usage);\n+\t\t\t\tprogress_title = argv[++i];\n \t\t\t} else if (!strcmp(arg, \"--show-resolving-progress\")) {\n \t\t\t\tshow_resolving_progress = 1;\n \t\t\t} else if (!strcmp(arg, \"--report-end-of-input\")) {\n-- \n2.33.0.rc0.646.g585563e77f\n\n"},{"id":"432116","messageId":"RFC-patch-12.13-8dc5613e87-20210805T150534Z-avarab@gmail.com","threadId":"56220","inReplyTo":"RFC-cover-00.13-0000000000-20210805T150534Z-avarab@gmail.com","subject":"[RFC PATCH 12/13] bundle-uri client: support for bundle-uri with \"clone\"","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-08-05T15:07:28Z","receivedAt":"2021-08-05T15:08:02Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"In an earlier commit (\"bundle-uri client: add minimal NOOP client\") a\ntransport_get_remote_bundle_uri() call was added to builtin/clone.c to\nget any advertised bundle URIs from the server during cloning, but\nnothing was being done with them yet.\n\nThis implements real support for bundle-uri during the \"clone\"\nphase. It's not used at all by \"fetch\", but the code to support it is\nmostly here already, and will be finished later.\n\nUsing the new transfer.injectBundleURI support it's easy to test this\nmethod of cloning on a live server that doesn't support bundle-uri. In\na git.git checkout.\n\nFirst let's prepare two bundles:\n\n    git bundle create /tmp/git-master-only.bdl origin/master\n    git bundle create /tmp/git-master-to-next.bdl origin/master..origin/next\n\nAnd next, let's do a \"fake\" clone where we bootstrap from these\nbundles. The fetch.uriProtocols is needed because we'd otherwise\nignore \"file://\" URIs. This uses --no-tags --single-branch for\nsimplicity:\n\n    rm -rf /tmp/git.git &&\n    git \\\n\t-c protocol.version=2 \\\n        -c fetch.uriProtocols=file \\\n        -c transfer.injectBundleURI=\"file:///tmp/git-master-only.bdl\" \\\n\t-c transfer.injectBundleURI=\"file:///tmp/git-master-to-next.bdl\" \\\n\tclone --bare --no-tags --single-branch --branch next --template= \\\n\t--verbose --verbose \\\n\thttps://github.com/git/git.git /tmp/git.git\n\nWe'll then get output like:\n\n    Receiving bundle (1/2): 100% (300529/300529), 87.57 MiB | 32.70 MiB/s, done.\n    Resolving deltas: 100% (226765/226765), done.\n    have eb27b338a3e71c7c4079fbac8aeae3f8fbb5c687 commit via bundle-uri\n    Receiving bundle (2/2): 100% (725/725), 221.11 KiB | 22.11 MiB/s, done.\n    Resolving deltas: 100% (539/539), completed with 153 local objects.\n    have e1b32706d8dd5db1dc2e13f8e391651214f1d987 commit via bundle-uri\n    Marking e1b32706d8dd5db1dc2e13f8e391651214f1d987 as complete\n    already have e1b32706d8dd5db1dc2e13f8e391651214f1d987 (refs/heads/next)\n    Checking connectivity: 301210, done.\n\nI.e. we did an ls-refs on connection to the server, then retrieved the\nadvertised bundles (faked up via config in this case).\n\nWe then got all the data leading up to the current \"master\" from\nthere, and also the commit that's currently on \"next. In this case we\nfound that we didn't need to proceed further with the dialog.\n\nI.e. other than an ls-refs and the server waiting until we downloaded\nthe bundles, the server didn't need to do any work creating a PACK for\nus.\n\nIf we change \"--branch next\" into \"--branch seen\" in the above command\nwe'll get the same output at the start until the \"want\" line, then:\n\n    [...]\n    want 93021c12c9f91e0d750d3ca8750a62416f4ea81a (refs/heads/seen)\n    POST git-upload-pack (212 bytes)\n    remote: Enumerating objects: 2265, done.\n    remote: Counting objects: 100% (1576/1576), done.\n    remote: Compressing objects: 100% (233/233), done.\n    remote: Total 2265 (delta 1378), reused 1480 (delta 1341), pack-reused 689\n    Receiving objects: 100% (2265/2265), 2.17 MiB | 10.77 MiB/s, done.\n    Resolving deltas: 100% (1673/1673), completed with 339 local objects.\n    Checking connectivity: 303225, done.\n\nI.e. the server needed to send us an incremental update on top after\nwe'd unpacked the bundles, but this was a fairly minimal set of ~2k\nobjects. It didn't need to service a full clone.\n\nWe can see the savings on the server by setting up a local server at\nthe tip of \"next\":\n\n    rm -rf /tmp/git-server.git &&\n    git init --bare /tmp/git-server.git &&\n    git -C /tmp/git-server.git bundle unbundle /tmp/git-master-only.bdl &&\n    git -C /tmp/git-server.git bundle unbundle /tmp/git-master-to-next.bdl\n    git -C /tmp/git-server.git update-ref refs/heads/master $(git ls-remote /tmp/git-master-only.bdl | cut -f 1) &&\n    git -C /tmp/git-server.git update-ref refs/heads/next $(git ls-remote /tmp/git-master-to-next.bdl | cut -f 1) &&\n    git -C /tmp/git-server.git for-each-ref\n\nLet's then clone from it, and record the time we spend.\n\n    rm -rf /tmp/git.git /tmp/{client,server}.time &&\n    /usr/bin/time -o /tmp/client.time -v git \\\n\t-c protocol.version=2 \\\n        -c fetch.uriProtocols=file \\\n        -c transfer.injectBundleURI=\"file:///tmp/git-master-only.bdl\" \\\n\tclone \\\n\t--upload-pack '/usr/bin/time -o /tmp/server.time -v git-upload-pack' \\\n\t--bare --no-tags --single-branch --branch next --template= \\\n\t--verbose --verbose \\\n\tfile:///tmp/git-server.git /tmp/git.git &&\n    for i in client server\n    do\n        echo $i: &&\n        grep -e seconds -e wall -e Maximum -e context /tmp/$i.time\n    done\n\nThis gives us something like these results:\n\n    client:\n        User time (seconds): 46.34\n        System time (seconds): 0.67\n        Elapsed (wall clock) time (h:mm:ss or m:ss): 0:18.67\n        Maximum resident set size (kbytes): 207096\n        Voluntary context switches: 116058\n        Involuntary context switches: 220\n    server:\n        User time (seconds): 0.13\n        System time (seconds): 0.00\n        Elapsed (wall clock) time (h:mm:ss or m:ss): 0:14.08\n        Maximum resident set size (kbytes): 53168\n        Voluntary context switches: 255\n        Involuntary context switches: 7\n\nWhereas doing a normal \"clone\" (by e.g. adding \"-c\ntransfer.bundleURI=false\" to the above) will give something like:\n\n    client:\n        User time (seconds): 47.24\n        System time (seconds): 0.92\n        Elapsed (wall clock) time (h:mm:ss or m:ss): 0:18.55\n        Maximum resident set size (kbytes): 288104\n        Voluntary context switches: 136350\n        Involuntary context switches: 296\n    server:\n        User time (seconds): 5.73\n        System time (seconds): 0.24\n        Elapsed (wall clock) time (h:mm:ss or m:ss): 0:07.45\n        Maximum resident set size (kbytes): 288104\n        Voluntary context switches: 26568\n        Involuntary context switches: 111\n\nI.e. we can see that the win on the client in this case is negative,\nbut we use around over 2% of the CPU time on the server, and around\n20% of the memory. The client-visible time is a bit slower, by around\n2%.\n\nIn practice I think this will be more of a win-win. These results are\non an unloaded local machine, and don't account for the benefit of the\nserver being more likely to have a network-local version of most of\nthe repository via dumb CDNs.\n\nReal servers are also usually in a messier state of having various\nloose objects and more fragmented pack collections, and needing to\nspend CPU to assemble these. Frequent repacking and e.g. local caching\ne.g. via the uploadpack.packObjectsHook helps, but using this should\nmake it more accessible to run a highly performance git server.\n\nThis feature also makes things like resumable clones rather trivial to\nimplement, this approach was discussed in the past[1] as a means to\nget that feature.\n\n1. https://lore.kernel.org/git/20111110074330.GA27925@sigill.intra.peff.net/\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n fetch-pack.c                          | 259 ++++++++++++++++++++++++++\n fetch-pack.h                          |   6 +\n t/lib-t5730-protocol-v2-bundle-uri.sh | 109 ++++++++++-\n transport.c                           |   1 +\n 4 files changed, 374 insertions(+), 1 deletion(-)\n\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex ef07b45072..4ce4946963 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -25,6 +25,7 @@\n #include \"shallow.h\"\n #include \"commit-reach.h\"\n #include \"commit-graph.h\"\n+#include \"bundle.h\"\n \n static int transfer_unpack_limit = -1;\n static int fetch_unpack_limit = -1;\n@@ -1000,6 +1001,137 @@ static int get_pack(struct fetch_pack_args *args,\n \treturn 0;\n }\n \n+static int unbundle_bundle_uri(const char *bundle_uri, unsigned int nth,\n+\t\t\t       unsigned int total_nr, FILE *in, int in_fd,\n+\t\t\t       struct oid_array *bundle_oids,\n+\t\t\t       unsigned int use_thin_pack)\n+{\n+\tstruct child_process cmd = CHILD_PROCESS_INIT;\n+\tstruct bundle_header header = BUNDLE_HEADER_INIT;\n+\tint ret = 0;\n+\tstruct string_list_item *item;\n+\tstruct strbuf progress_title = STRBUF_INIT;\n+\tint code;\n+\n+\tret = read_bundle_header_fd(in_fd, &header, bundle_uri);\n+\tif (ret < 0) {\n+\t\tret = error(\"could not read_bundle_header(%s)\", bundle_uri);\n+\t\tgoto cleanup;\n+\t}\n+\n+\tfor_each_string_list_item(item, &header.references) {\n+\t\t/*\n+\t\t * The bundle's idea of the ref name is\n+\t\t * item->string.\n+\t\t *\n+\t\t * Here's where we could do concurrent negotiation\n+\t\t * with the server (and possibly start the fetch!)\n+\t\t * before or while we unpack the bundle with\n+\t\t * index-pack.\n+\t\t *\n+\t\t * The negotiator would need a small change to trust\n+\t\t * arbitrary OIDs instead of assuming it has existing\n+\t\t * in-repo \"struct commit *\", but ad-hoc testing\n+\t\t * reveals that it'll work & speed up the fetch even\n+\t\t * more, as we could proceed in parallel with the full\n+\t\t * bundle fetching as soon as we get the headers.\n+\t\t */\n+\t\tstruct object_id *oid = item->util;\n+\n+\t\toid_array_append(bundle_oids, oid);\n+\t}\n+\n+\tif (git_env_bool(\"GIT_TEST_BUNDLE_URI_FAIL_UNBUNDLE\", 0))\n+\t\tlseek(in_fd, 0, SEEK_SET);\n+\n+\tstrbuf_addf(&progress_title, \"Receiving bundle (%d/%d)\", nth, total_nr);\n+\n+\tstrvec_push(&cmd.args, \"index-pack\");\n+\tstrvec_push(&cmd.args, \"--stdin\");\n+\tstrvec_push(&cmd.args, \"-v\");\n+\tstrvec_push(&cmd.args, \"--progress-title\");\n+\tstrvec_push(&cmd.args, progress_title.buf);\n+\n+\tif (header.prerequisites.nr && use_thin_pack)\n+\t\tstrvec_push(&cmd.args, \"--fix-thin\");\n+\tstrvec_push(&cmd.args, \"--check-self-contained-and-connected\");\n+\tadd_index_pack_keep_option(&cmd.args);\n+\n+\tcmd.git_cmd = 1;\n+\tcmd.in = in_fd;\n+\tcmd.no_stdout = 1;\n+\tcmd.git_cmd = 1;\n+\n+\tif (start_command(&cmd)) {\n+\t\tret = error(_(\"fetch-pack: unable to spawn index-pack\"));\n+\t\tgoto cleanup;\n+\t}\n+\n+\tcode = finish_command(&cmd);\n+\n+\tif (header.prerequisites.nr && code == 1)\n+\t\t/*\n+\t\t * index-pack returns -1 on\n+\t\t * --check-self-contained-and-connected to indicate\n+\t\t * that the pack was indeed not self contained and\n+\t\t * connected. We know from the bundle header\n+\t\t * prerequisites.\n+\t\t */\n+\t\tcode = 0;\n+\n+\tif (code) {\n+\t\tret = error(_(\"fetch-pack: unable to finish index-pack, exited with %d\"), code);\n+\t\tgoto cleanup;\n+\t}\n+\n+cleanup:\n+\tstrbuf_release(&progress_title);\n+\tbundle_header_release(&header);\n+\treturn ret;\n+}\n+\n+static int get_bundle_uri(struct string_list_item *item, unsigned int nth,\n+\t\t\t  unsigned int total_nr, struct oid_array *bundle_oids,\n+\t\t\t  unsigned int use_thin_pack)\n+{\n+\tstruct child_process cmd = CHILD_PROCESS_INIT;\n+\tstruct strbuf tempfile = STRBUF_INIT;\n+\tint ret = 0;\n+\tconst char *uri = item->string;\n+\tFILE *out;\n+\tint out_fd;\n+\n+\tstrvec_push(&cmd.args, \"curl\");\n+\tstrvec_push(&cmd.args, \"--silent\");\n+\tstrvec_push(&cmd.args, \"--output\");\n+\tstrvec_push(&cmd.args, \"-\");\n+\tstrvec_push(&cmd.args, \"--\");\n+\tstrvec_push(&cmd.args, item->string);\n+\tcmd.git_cmd = 0;\n+\tcmd.no_stdin = 1;\n+\tcmd.out = -1;\n+\n+\tif (start_command(&cmd)) {\n+\t\tret = error(\"fetch-pack: unable to spawn http-fetch\");\n+\t\tgoto cleanup;\n+\t}\n+\n+\tout = xfdopen(cmd.out, \"r\");\n+\tout_fd = fileno(out);\n+\tret = unbundle_bundle_uri(uri, nth, total_nr, out, out_fd,\n+\t\t\t\t  bundle_oids, use_thin_pack);\n+\n+\tif (finish_command(&cmd)) {\n+\t\tret = error(\"fetch-pack: unable to finish http-fetch\");\n+\t\tgoto cleanup;\n+\t}\n+\n+cleanup:\n+\tstrbuf_release(&tempfile);\n+\n+\treturn ret;\n+}\n+\n static int cmp_ref_by_name(const void *a_, const void *b_)\n {\n \tconst struct ref *a = *((const struct ref **)a_);\n@@ -1549,6 +1681,130 @@ static void do_check_stateless_delimiter(int stateless_rpc,\n \t\t\t\t  _(\"git fetch-pack: expected response end packet\"));\n }\n \n+static int get_bundle_uri_add_known_common(struct string_list_item *item,\n+\t\t\t\t\t   unsigned int nth, unsigned int total_nr,\n+\t\t\t\t\t   struct fetch_negotiator *negotiator,\n+\t\t\t\t\t   struct fetch_pack_args *args,\n+\t\t\t\t\t   unsigned int use_thin_pack)\n+{\n+\tint i;\n+\tstruct oid_array bundle_oids = OID_ARRAY_INIT;\n+\n+\t/*\n+\t * We don't use OBJECT_INFO_QUICK here unlike in the rest of\n+\t * the fetch routines, that's because the rest of them don't\n+\t * need to consider a commit object that's just been\n+\t * downloaded for further negotiation, but bundle-uri does for\n+\t * adding newly downloaded OIDs to the negotiator.\n+\t */\n+\tunsigned oi_flags = OBJECT_INFO_SKIP_FETCH_OBJECT;\n+\n+\tif (get_bundle_uri(item, nth, total_nr, &bundle_oids, use_thin_pack) < 0)\n+\t\treturn error(_(\"could not get the bundle URI #%d\"), nth);\n+\n+\tfor (i = 0; i < bundle_oids.nr; i++) {\n+\t\tstruct object_id *oid = &bundle_oids.oid[i];\n+\t\tenum object_type type = OBJ_NONE;\n+\t\tstruct commit *c = deref_without_lazy_fetch_extended(oid, 0,\n+\t\t\t\t\t\t\t\t     &type,\n+\t\t\t\t\t\t\t\t     oi_flags);\n+\t\tif (!c) {\n+\t\t\tif (type == OBJ_BLOB || type == OBJ_TREE) {\n+\t\t\t\tprint_verbose(args, \"have %s %s via bundle-uri (ignoring due to type)\",\n+\t\t\t\t\t      oid_to_hex(oid), type_name(type));\n+\t\t\t\tcontinue;\n+\t\t\t} else if (type) {\n+\t\t\t\t/*\n+\t\t\t\t * OBJ_TAG should have been peeled,\n+\t\t\t\t * and OBJ_COMMIT should have a\n+\t\t\t\t * non-NULL \"c\".\n+\t\t\t\t *\n+\t\t\t\t * Should be a BUG() if we were not\n+\t\t\t\t * bending over backwards to make\n+\t\t\t\t * bundle-uri soft-fail.\n+\t\t\t\t */\n+\t\t\t\treturn error(_(\"bundle-uri says it has %s, got it at unexpected type %s\"),\n+\t\t\t\t\t     oid_to_hex(oid), type_name(type));\n+\t\t\t}\n+\t\t}\n+\n+\t\tprint_verbose(args, \"have %s %s via bundle-uri\",\n+\t\t\t      oid_to_hex(oid), type_name(type));\n+\n+\t\tnegotiator->known_common(negotiator, c);\n+\t\tmark_complete(oid);\n+\t}\n+\treturn 0;\n+}\n+\n+static void do_fetch_pack_v2_bundle_uri(struct fetch_pack_args *args,\n+\t\t\t\t\tstruct string_list  *bundle_uri,\n+\t\t\t\t\tstruct fetch_negotiator *negotiator)\n+{\n+\tstruct string_list_item *item;\n+\tstruct string_list list = STRING_LIST_INIT_NODUP;\n+\tstruct string_list default_protocols = STRING_LIST_INIT_NODUP;\n+\tstruct string_list *ok_protocols;\n+\n+\tif (!bundle_uri)\n+\t\treturn;\n+\n+\tif (!bundle_uri->nr)\n+\t\treturn;\n+\n+\tif (uri_protocols.nr) {\n+\t\tok_protocols = &uri_protocols;\n+\t} else {\n+\t\tstring_list_append(&default_protocols, \"http\");\n+\t\tstring_list_append(&default_protocols, \"https\");\n+\t\tok_protocols = &default_protocols;\n+\t}\n+\n+\tfor_each_string_list_item(item, bundle_uri) {\n+\t\tconst char *uri = item->string;\n+\t\tint protocol_ok = 0;\n+\t\tstruct string_list_item *item2;\n+\n+\t\tfor_each_string_list_item(item2, ok_protocols) {\n+\t\t\tconst char *s = item2->string;\n+\t\t\tconst char *p;\n+\n+\t\t\tif (skip_prefix(item->string, s, &p) &&\n+\t\t\t    starts_with(p, \"://\")) {\n+\t\t\t\tprotocol_ok = 1;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\n+\t\tif (!protocol_ok) {\n+\t\t\tprint_verbose(args, \"skipping bundle-uri not on protocol whitelist: %s\",\n+\t\t\t\t      item->string);\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tstring_list_append(&list, uri)->util = item->util;\n+\t}\n+\n+\tif (list.nr) {\n+\t\tint i;\n+\t\tunsigned int total_nr = list.nr;\n+\n+\t\ttrace2_region_enter(\"fetch-pack\", \"bundle-uri\", the_repository);\n+\t\tfor (i = 0; i < total_nr; i++) {\n+\t\t\tstruct string_list_item item = list.items[i];\n+\t\t\tunsigned int nth = i + 1;\n+\n+\t\t\tget_bundle_uri_add_known_common(&item, nth, total_nr,\n+\t\t\t\t\t\t\tnegotiator, args,\n+\t\t\t\t\t\t\targs->use_thin_pack);\n+\t\t}\n+\t\ttrace2_region_leave(\"fetch-pack\", \"bundle-uri\", the_repository);\n+\t}\n+\n+\tstring_list_clear(&default_protocols, 0);;\n+}\n+\n+\n static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n \t\t\t\t    int fd[2],\n \t\t\t\t    const struct ref *orig_ref,\n@@ -1572,10 +1828,13 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n \tstruct string_list packfile_uris = STRING_LIST_INIT_DUP;\n \tint i;\n \tstruct strvec index_pack_args = STRVEC_INIT;\n+\tstruct string_list *bundle_uri = args->bundle_uri;\n \n \tnegotiator = &negotiator_alloc;\n \tfetch_negotiator_init(r, negotiator);\n \n+\tdo_fetch_pack_v2_bundle_uri(args, bundle_uri, negotiator);\n+\n \tpacket_reader_init(&reader, fd[0], NULL, 0,\n \t\t\t   PACKET_READ_CHOMP_NEWLINE |\n \t\t\t   PACKET_READ_DIE_ON_ERR_PACKET);\ndiff --git a/fetch-pack.h b/fetch-pack.h\nindex 7f94a2a583..b19fd7d93b 100644\n--- a/fetch-pack.h\n+++ b/fetch-pack.h\n@@ -24,6 +24,12 @@ struct fetch_pack_args {\n \t */\n \tconst struct oid_array *negotiation_tips;\n \n+\t/*\n+\t * A pointer to the already populated transport.bundle_uri\n+\t * struct.\n+\t */\n+\tstruct string_list *bundle_uri;\n+\n \tunsigned deepen_relative:1;\n \tunsigned quiet:1;\n \tunsigned keep_pack:1;\ndiff --git a/t/lib-t5730-protocol-v2-bundle-uri.sh b/t/lib-t5730-protocol-v2-bundle-uri.sh\nindex e5d17f6a81..db845fd4c3 100644\n--- a/t/lib-t5730-protocol-v2-bundle-uri.sh\n+++ b/t/lib-t5730-protocol-v2-bundle-uri.sh\n@@ -7,6 +7,8 @@ case \"$T5730_PROTOCOL\" in\n file)\n \tT5730_PARENT=file_parent\n \tT5730_URI=\"file://$PWD/file_parent\"\n+\tT5730_URI_BDL_PROTO=\"file://\"\n+\tT5730_URI_BDL=\"$T5730_URI_BDL_PROTO$PWD/file_parent\"\n \tT5730_BUNDLE_URI=\"$T5730_URI/fake.bdl\"\n \ttest_set_prereq T5730_FILE\n \t;;\n@@ -15,6 +17,8 @@ git)\n \tstart_git_daemon --export-all --enable=receive-pack\n \tT5730_PARENT=\"$GIT_DAEMON_DOCUMENT_ROOT_PATH/parent\"\n \tT5730_URI=\"$GIT_DAEMON_URL/parent\"\n+\tT5730_URI_BDL_PROTO=\"file://\"\n+\tT5730_URI_BDL=\"$T5730_URI_BDL_PROTO$GIT_DAEMON_DOCUMENT_ROOT_PATH/parent\"\n \tT5730_BUNDLE_URI=\"https://example.com/fake.bdl\"\n \ttest_set_prereq T5730_GIT\n \t;;\n@@ -23,6 +27,8 @@ http)\n \tstart_httpd\n \tT5730_PARENT=\"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\"\n \tT5730_URI=\"$HTTPD_URL/smart/http_parent\"\n+\tT5730_URI_BDL_PROTO=\"http://\"\n+\tT5730_URI_BDL=\"$HTTPD_URL/dumb/http_parent\"\n \ttest_set_prereq T5730_HTTP\n \t;;\n *)\n@@ -32,7 +38,20 @@ esac\n \n test_expect_success \"setup protocol v2 $T5730_PROTOCOL:// tests\" '\n \tgit init \"$T5730_PARENT\" &&\n-\ttest_commit -C \"$T5730_PARENT\" one\n+\ttest_commit -C \"$T5730_PARENT\" one &&\n+\ttest_commit -C \"$T5730_PARENT\" two &&\n+\ttest_commit -C \"$T5730_PARENT\" three &&\n+\ttest_commit -C \"$T5730_PARENT\" four &&\n+\ttest_commit -C \"$T5730_PARENT\" five &&\n+\ttest_commit -C \"$T5730_PARENT\" six &&\n+\n+\tmkdir \"$T5730_PARENT\"/bdl &&\n+\tgit -C \"$T5730_PARENT\" bundle create bdl/1.bdl one &&\n+\tgit -C \"$T5730_PARENT\" bundle create bdl/1-2.bdl one..two &&\n+\tgit -C \"$T5730_PARENT\" bundle create bdl/2-3.bdl two..three &&\n+\tgit -C \"$T5730_PARENT\" bundle create bdl/3-4.bdl three..four &&\n+\tgit -C \"$T5730_PARENT\" bundle create bdl/4-5.bdl four..five &&\n+\tgit -C \"$T5730_PARENT\" bundle create bdl/5-6.bdl five..six\n '\n \n # Poor man's URI escaping. Good enough for the test suite whose trash\n@@ -316,3 +335,91 @@ test_expect_success \"ls-remote-bundle-uri with bad -c transfer.injectBundleURI p\n \ttest_cmp err.expect err.actual &&\n \ttest_path_is_missing log\n '\n+\n+test_cmp_repo_refs() {\n+\tone=\"$1\"\n+\ttwo=\"$2\"\n+\tshift 2\n+\n+\tgit -C \"$one\" for-each-ref \"$@\" >expect &&\n+\tgit -C \"$two\" for-each-ref \"$@\" >actual &&\n+\ttest_cmp expect actual\n+}\n+\n+test_expect_success \"clone with bundle-uri protocol v2 over $T5730_PROTOCOL:// 1.bdl via $T5730_URI_BDL_PROTO\" '\n+\ttest_when_finished \"rm -f log\" &&\n+\n+\ttest_config -C \"$T5730_PARENT\" uploadpack.bundleURI \"$(echo $T5730_URI_BDL/bdl/1.bdl | test_uri_escape)\" &&\n+\n+\ttest_when_finished \"rm -rf log child\" &&\n+\tGIT_TRACE_PACKET=\"$PWD/log\" \\\n+\tgit \\\n+\t\t-c protocol.version=2 \\\n+\t\t-c fetch.uriProtocols=file,http \\\n+\t\tclone --verbose --verbose \\\n+\t\t\"$T5730_URI\" child >out 2>err &&\n+\tgrep -F \"Receiving bundle (1/1)\" err &&\n+\tgrep \"clone> want \" log &&\n+\ttest_cmp_repo_refs \"$T5730_PARENT\" child refs/heads refs/tags\n+'\n+\n+test_expect_success \"fetch with bundle-uri protocol v2 over $T5730_PROTOCOL:// 1.bdl via $T5730_URI_BDL_PROTO\" '\n+\ttest_when_finished \"rm -f log\" &&\n+\n+\ttest_config -C \"$T5730_PARENT\" uploadpack.bundleURI \"$(echo $T5730_URI_BDL/bdl/1.bdl | test_uri_escape)\" &&\n+\n+\ttest_when_finished \"rm -rf log child\" &&\n+\tgit init --bare child &&\n+\tgit -C child remote add --mirror=fetch origin \"$T5730_URI\" &&\n+\tGIT_TRACE_PACKET=\"$PWD/log\" \\\n+\tgit -C child \\\n+\t\t-c protocol.version=2 \\\n+\t\t-c fetch.uriProtocols=file,http \\\n+\t\tfetch --verbose --verbose >out 2>err &&\n+\t# Fetch is not supported yet\n+\t! grep -F \"Receiving bundle (1/1)\" err &&\n+\tgrep \"fetch> want \" log &&\n+\ttest_cmp_repo_refs \"$T5730_PARENT\" child refs/heads refs/tags\n+'\n+\n+test_expect_success \"clone with bundle-uri protocol v2 with $T5730_PROTOCOL:// 1 + 1-2 + [...].bdl via $T5730_URI_BDL_PROTO\" '\n+\ttest_when_finished \"rm -f log\" &&\n+\n+\ttest_config -C \"$T5730_PARENT\" uploadpack.bundleURI \"$(echo $T5730_URI_BDL/bdl/1.bdl | test_uri_escape)\" &&\n+\ttest_config -C \"$T5730_PARENT\" uploadpack.bundleURI \"$(echo $T5730_URI_BDL/bdl/1-2.bdl | test_uri_escape)\" --add &&\n+\ttest_config -C \"$T5730_PARENT\" uploadpack.bundleURI \"$(echo $T5730_URI_BDL/bdl/2-3.bdl | test_uri_escape)\" --add &&\n+\ttest_config -C \"$T5730_PARENT\" uploadpack.bundleURI \"$(echo $T5730_URI_BDL/bdl/3-4.bdl | test_uri_escape)\" --add &&\n+\n+\ttest_when_finished \"rm -rf log child\" &&\n+\tGIT_TRACE_PACKET=\"$PWD/log\" \\\n+\tgit \\\n+\t\t-c protocol.version=2 \\\n+\t\t-c fetch.uriProtocols=file,http \\\n+\t\tclone --verbose --verbose \\\n+\t\t\"$T5730_URI\" child >out 2>err &&\n+\tgrep -F \"Receiving bundle (4/4)\" err &&\n+\ttest_cmp_repo_refs \"$T5730_PARENT\" child refs/heads refs/tags &&\n+\tgrep \"clone> want \" log\n+'\n+\n+test_expect_success \"clone with bundle-uri protocol v2 with $T5730_PROTOCOL:// ALL.bdl via $T5730_URI_BDL_PROTO\" '\n+\ttest_when_finished \"rm -f log\" &&\n+\n+\ttest_config -C \"$T5730_PARENT\" uploadpack.bundleURI \"$(echo $T5730_URI_BDL/bdl/1.bdl | test_uri_escape)\" &&\n+\ttest_config -C \"$T5730_PARENT\" uploadpack.bundleURI \"$(echo $T5730_URI_BDL/bdl/1-2.bdl | test_uri_escape)\" --add &&\n+\ttest_config -C \"$T5730_PARENT\" uploadpack.bundleURI \"$(echo $T5730_URI_BDL/bdl/2-3.bdl | test_uri_escape)\" --add &&\n+\ttest_config -C \"$T5730_PARENT\" uploadpack.bundleURI \"$(echo $T5730_URI_BDL/bdl/3-4.bdl | test_uri_escape)\" --add &&\n+\ttest_config -C \"$T5730_PARENT\" uploadpack.bundleURI \"$(echo $T5730_URI_BDL/bdl/4-5.bdl | test_uri_escape)\" --add &&\n+\ttest_config -C \"$T5730_PARENT\" uploadpack.bundleURI \"$(echo $T5730_URI_BDL/bdl/5-6.bdl | test_uri_escape)\" --add &&\n+\n+\ttest_when_finished \"rm -rf log child\" &&\n+\tGIT_TRACE_PACKET=\"$PWD/log\" \\\n+\tgit \\\n+\t\t-c protocol.version=2 \\\n+\t\t-c fetch.uriProtocols=file,http \\\n+\t\tclone --verbose --verbose \\\n+\t\t\"$T5730_URI\" child >out 2>err &&\n+\tgrep -F \"Receiving bundle (6/6)\" err &&\n+\ttest_cmp_repo_refs \"$T5730_PARENT\" child refs/heads refs/tags &&\n+\t! grep \"clone> want \" log\n+'\ndiff --git a/transport.c b/transport.c\nindex 80d1857374..043afa4544 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -422,6 +422,7 @@ static int fetch_refs_via_pack(struct transport *transport,\n \targs.server_options = transport->server_options;\n \targs.negotiation_tips = data->options.negotiation_tips;\n \targs.reject_shallow_remote = transport->smart_options->reject_shallow;\n+\targs.bundle_uri = &transport->bundle_uri;\n \n \tif (!data->got_remote_heads) {\n \t\tint i;\n-- \n2.33.0.rc0.646.g585563e77f\n\n"},{"id":"432117","messageId":"RFC-patch-13.13-1e657ed27a-20210805T150534Z-avarab@gmail.com","threadId":"56220","inReplyTo":"RFC-cover-00.13-0000000000-20210805T150534Z-avarab@gmail.com","subject":"[RFC PATCH 13/13] bundle-uri docs: add design notes","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-08-05T15:07:29Z","receivedAt":"2021-08-05T15:08:04Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Add a design doc for the bundle-uri protocol extension to go along\nwith the packfile-uri extension added in cd8402e0fd8 (Documentation:\nadd Packfile URIs design doc, 2020-06-10).\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n Documentation/technical/bundle-uri.txt  | 119 ++++++++++++++++++++++++\n Documentation/technical/protocol-v2.txt |   5 +\n 2 files changed, 124 insertions(+)\n create mode 100644 Documentation/technical/bundle-uri.txt\n\ndiff --git a/Documentation/technical/bundle-uri.txt b/Documentation/technical/bundle-uri.txt\nnew file mode 100644\nindex 0000000000..5ae9a15eaf\n--- /dev/null\n+++ b/Documentation/technical/bundle-uri.txt\n@@ -0,0 +1,119 @@\n+Bundle URI Design Notes\n+=======================\n+\n+Protocol\n+--------\n+\n+See `bundle-uri` in the link:protocol-v2.html[protocol-v2]\n+documentation for a discussion of the bundle-uri command, and the\n+expectations of clients and servers.\n+\n+This document is a a more general discussion of how the `bundle-uri`\n+command fits in with the rest of the git ecosystem, its design goals\n+and non-goals, comparison to alternatives etc.\n+\n+Comparison with Packfile URIs\n+-----------------------------\n+\n+There is a similar \"Packfile URIs\" facility, see the\n+link:packfile-uri.html[packfile-uri] documentation for details.\n+\n+The Packfile URIs facility requires a much closer cooperation between\n+CDN and server than the bundle URI facility.\n+\n+I.e. the server MUST know what objects exist in the packfile URI it's\n+pointing to, as well as its pack checksum. Failure to do so will not\n+only result in a client error (the packfile hash won't match), but\n+even if it got past that would likely result in a corrupt repository\n+with tips pointing to unreachable objects.\n+\n+By comparison the bundle URIs are meant to be a \"dumb\" solution\n+friendly to e.g. having a weekly cronjob take a snapshot of a git\n+repository, that snapshot being uploaded to a network of FTP mirrors\n+(which may be inconsistent or out of date).\n+\n+The server does not need to know what state the side-channel download\n+is at, because the client will first validate it, and then optionally\n+negotiate with the server using what it discovers there.\n+\n+Using the local `transfer.injectBundleURI` configuration variable (see\n+linkgit:git-config[1]) the `bundle-uri` mechanism doesn't even need\n+the server to support it.\n+\n+Security\n+--------\n+\n+The omission of something equivalent to the packfile <OID> in the\n+Packfile URIs protocol is intentional, as having it would require\n+closer server and CDN cooperation than some server operators are\n+comfortable with.\n+\n+Furthermore, it is not needed for security. The server doesn't need to\n+trust its CDN. If the server were to attempt to send harmful content\n+to the client, the result would not validate against the server's\n+provided ref tips gotten from ls-refs.\n+\n+The lack of a such a hash does leave room open to a malicious CDN\n+operation to be annoying however. E.g. they could inject irrelevant\n+objects into the bundles, which would enlarge the downloaded\n+repository until a \"gc\" would eventually throw them away.\n+\n+In practice the lack of a hash is considered to be a non-issue. Anyone\n+concerned about such security problems between their server and their\n+CDN is going to be pointing to a \"https\" URL under their control. For\n+a client the \"threat\" is the same as without bundle-uri, i.e. a server\n+is free to be annoying today and send you garbage in the PACK that you\n+won't need.\n+\n+Security issues peculiar to bundle-uri\n+--------------------------------------\n+\n+Both packfile-uri and bundle-uri use the `fetch.uriProtocols`\n+configuration variable (see linkgit:git-config[1]) to configure which\n+protocols they support.\n+\n+By default this is set to \"http,https\" for both, but bundle-uri\n+supports adding \"file\" to that list. The server can thus point to\n+\"file://\" URIs it expects the client to have access to.\n+\n+This is primarily intended for use with the `transfer.injectBundleURI`\n+mechanism, but can also be useful e.g. in a centralized environment\n+where a server might point to a \"file:///mnt/bundles/big-repo.bdl\" it\n+knows to be mounted on the local machine (e.g. a racked server),\n+points to it in its \"bundle-uri\" response.\n+\n+The client can then add \"file\" to the `fetch.uriProtocols` list to\n+obey such responses. That does mean that a malicious server can point\n+to any arbitrary file on the local machine. The threat of this is\n+considered minimal, since anyone adding `file` to `fetch.uriProtocols`\n+likely knows what they're doing and controls both ands, and the worst\n+they can do is make a curl(1) pipe garbage into \"index-pack\" (which\n+will likely promptly die on the non-PACK-file).\n+\n+Security comparison with packfile-uri\n+-------------------------------------\n+\n+The initial implementation of packfile-uri needed special adjusting to\n+run \"git fsck\" on incoming .gitmodules files, this was to deal with a\n+general security issue in git, See CVE-2018-17456.\n+\n+The current packfile-uri mechanism requires special handling around\n+\"fsck\" to do such cross-PACK fsck's, this is because it first indexes\n+the \"incremental\" PACK, and then any PACK(s) provided via\n+packfile-uri, before finally doing a full connectivity check.\n+\n+This is effect doing the fsck one might do via \"clone\" and \"fetch\" in\n+reverse, or the equivalent of starting with the incremental \"fetch\",\n+followed by the \"clone\".\n+\n+Since the packfile-uri mechanism can result in the .gitmodules blob\n+referenced by such a \"fetch\" to be in the pack for the \"clone\" the\n+fetch-pack process needs to keep state between the indexing of\n+multiple packs, to remember to fsck the blob (via the \"clone\") later\n+after seeing it in a tree (from the \"fetch).\n+\n+There are no known security issues with the way packfile-uri does\n+this, but since bundle-uri effectively emulates what a which doesn't\n+support either \"bundle-uri\" or \"packfile-uri\" would do on clone/fetch,\n+any future security issues peculiar to the packfile-uri approach are\n+unlikely to be shared by it.\ndiff --git a/Documentation/technical/protocol-v2.txt b/Documentation/technical/protocol-v2.txt\nindex d10d5e9ef6..5536ea4b7e 100644\n--- a/Documentation/technical/protocol-v2.txt\n+++ b/Documentation/technical/protocol-v2.txt\n@@ -696,3 +696,8 @@ intended to support future features such as:\n    they've got that OID already (for multi-tips the client would need\n    to fetch the bundle, or do e.g. HTTP range requests to get its\n    header).\n+\n+bundle-uri SEE ALSO\n+^^^^^^^^^^^^^^^^^^^\n+\n+See the link:bundle-uri.html[Bundle URI Design Notes] for more.\n-- \n2.33.0.rc0.646.g585563e77f\n\n"},{"id":"432178","messageId":"YQ1JW8hHmG8B/oE3@google.com","threadId":"56220","inReplyTo":"RFC-cover-00.13-0000000000-20210805T150534Z-avarab@gmail.com","subject":"Re: [RFC PATCH 00/13] Add bundle-uri: resumably clones, static \"dumb\" CDN etc.","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2021-08-06T14:38:19Z","receivedAt":"2021-08-06T14:38:28Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Hi,\n\nÆvar Arnfjörð Bjarmason wrote:\n\n> We're in the 2.33.0 rc cycle, and I'd hoped to have some more prep\n> work for this integrated already, but for now here's something\n> interesting I've been working on for early commentary/feedback.\n>\n> This adds the the ability to protocol v2 for servers to optimistically\n> pre-seed supporting clients with one or more bundles via a new\n> \"bundle-uri\" protocol extension.\n\nMy initial thought here is that even though this includes a comparison\nto packfile URIs, I suspect you're underestimating them. :)\n\nWould it be possible to do the same pre-seeding using the packfile\nURIs protocol?  Nothing stops a server from sending more objects than\nthe client asked for.  Is the issue that you want the client to be\nable to list \"have\"s based on that pack?  Can't the server obtain that\nsame information at the same time as it obtains the bundle URL?\n\nThe reason I ask is that this contains a number of differences\nrelative to packfile URIs, most noticeably the use of bundles instead\nof packfiles as the format for the static content.  If we were\nstarting from scratch and chose this design _instead_ of packfile URIs\nthen that could make sense (though there are issues with the bundle\nformat that we can also go into), but in a world where people are also\nusing packfile URIs it makes for a kind of confusing UX.  Is a server\noperator expected to put both kinds of files on CDN and double their\nstorage bill?  Is this meant as an alternative, a replacement, or\nsomething that combines well together with the packfile URIs feature?\nWhat does the intended end state look like?\n\nProjects like chromium have been using packfile URIs in production for\nabout 11 months now and it's been working well.  Because of that, I'd\nbe interested in understanding its shortcomings and improving it in\nplace --- or in other words, I want _you_ to benefit from them instead\nof having to create an alternative to them.  Alternatively, if the\npackfile URIs protocol is fundamentally flawed, then I'd like us to\nunderstand that early and act on it instead of creating a parallel\nalternative and waiting for it to bitrot.\n\nI'll try to find time to look more closely at the patches to\nunderstand the use case in more detail, but it will take some time\nsince I'm currently focused on the -rc.\n\nThanks,\nJonathan\n"},{"id":"432189","messageId":"87h7g2zd8f.fsf@evledraar.gmail.com","threadId":"56220","inReplyTo":"YQ1JW8hHmG8B/oE3@google.com","subject":"Re: [RFC PATCH 00/13] Add bundle-uri: resumably clones, static \"dumb\" CDN etc.","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-08-06T16:26:26Z","receivedAt":"2021-08-06T17:37:09Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Fri, Aug 06 2021, Jonathan Nieder wrote:\n\n> Hi,\n>\n> Ævar Arnfjörð Bjarmason wrote:\n>\n>> We're in the 2.33.0 rc cycle, and I'd hoped to have some more prep\n>> work for this integrated already, but for now here's something\n>> interesting I've been working on for early commentary/feedback.\n>>\n>> This adds the the ability to protocol v2 for servers to optimistically\n>> pre-seed supporting clients with one or more bundles via a new\n>> \"bundle-uri\" protocol extension.\n>\n> My initial thought here is that even though this includes a comparison\n> to packfile URIs, I suspect you're underestimating them. :)\n>\n> Would it be possible to do the same pre-seeding using the packfile\n> URIs protocol?  Nothing stops a server from sending more objects than\n> the client asked for.  Is the issue that you want the client to be\n> able to list \"have\"s based on that pack?  Can't the server obtain that\n> same information at the same time as it obtains the bundle URL?\n\nHi. Thanks for taking a quick look.\n\nI think that the changes to Documentation/technical/protocol-v2.txt in\n01/13[1] and the Documentation/technical/bundle-uri.txt document added\nin 13/13[2] should address these questions.\n\nOr perhaps not, but they're my currently my best effort to explain the\ndifferences between the two and how they interact. So I think it's best\nto point to those instead of coming up with something in this reply,\nwhich'll inevitably be an incomplete rewrite of much of that.\n\nIn short, there are use-cases that packfile-uri is inherently unsuitable\nfor, or rather changing the packfile-uri feature to support them would\npretty much make it indistinguishable from this bundle-uri mechanism,\nwhich I think would just add more confusion to the protocol.\n\n> The reason I ask is that this contains a number of differences\n> relative to packfile URIs, most noticeably the use of bundles instead\n> of packfiles as the format for the static content.  If we were\n> starting from scratch and chose this design _instead_ of packfile URIs\n> then that could make sense (though there are issues with the bundle\n> format that we can also go into), but in a world where people are also\n> using packfile URIs it makes for a kind of confusing UX.  Is a server\n> operator expected to put both kinds of files on CDN and double their\n> storage bill?  Is this meant as an alternative, a replacement, or\n> something that combines well together with the packfile URIs feature?\n> What does the intended end state look like?\n\nThe two are complimentary, i.e. it's meant as something that combines\nwell with packfile-uri. One (packfile-uri) is meant as a dumb\npre-seeding of objects from static URLs that happens pre-negotiation.\n\nThe other (packfile-uri) is a post-negotiation mechanism for giving a\nclient not only a PACK, but complimenting it with a URI, together they\ntwo form one logical complete PACK. (I know that you know this, but for\nanyone reading along...).\n\n> Projects like chromium have been using packfile URIs in production for\n> about 11 months now and it's been working well.  Because of that, I'd\n> be interested in understanding its shortcomings and improving it in\n> place --- or in other words, I want _you_ to benefit from them instead\n> of having to create an alternative to them.  Alternatively, if the\n> packfile URIs protocol is fundamentally flawed, then I'd like us to\n> understand that early and act on it instead of creating a parallel\n> alternative and waiting for it to bitrot.\n\nI don't think there's any real shortcomings of packfile-uri in the senes\nthat it makes sense to improve it in the direction of this bundle-uri\nmechanism, they're simply doing different things at different phases in\nthe dialog.\n\nWhen packfile-uri was initially being discussed I was interested in\ngetting something like what this bundle-uri mechanism to be a part it,\nbut it was clear from discussions with Jonathan Tan that he was taking\npackfile-uri in a very different direction. Links to those discussions:\n\n    https://lore.kernel.org/git/87k1hv6eel.fsf@evledraar.gmail.com/\n    https://lore.kernel.org/git/87pnpbsra8.fsf@evledraar.gmail.com/\n    https://lore.kernel.org/git/87zh35okzy.fsf@evledraar.gmail.com/\n\nA demo of this feature playing nice with a server that supports\npackfile-uri, although I haven't actually gotten it to also send me a\npackfile-uri if I pre-seed with bundles (perhaps it's only doing it on\nfull clones?):\n\n    git -c fetch.uriprotocols=https \\\n        -c transfer.injectBundleURI=\"https://vm.nix.is/~avar/noindex/codesearch-git-master.bdl\" \\\n        -c transfer.injectBundleURI=\"https://vm.nix.is/~avar/noindex/codesearch-git-recent.bdl\" \\\n        clone --bare https://chromium.googlesource.com/chromiumos/codesearch.git /tmp/codesearch.git\n\nWill emit:\n    \n    Cloning into bare repository '/tmp/codesearch.git'...\n    Receiving bundle (1/2): 100% (322661/322661), 89.66 MiB | 0 bytes/s, done.\n    Resolving deltas: 100% (142478/142478), done.\n    Receiving bundle (2/2): 100% (69378/69378), 5.51 MiB | 0 bytes/s, done.\n    Resolving deltas: 100% (52558/52558)completed with 4 local objects\n    remote: Total 1958 (delta 4), reused 1958 (delta 4)\n    Receiving objects: 100% (1958/1958), 1.60 MiB | 0 bytes/s, done.\n    Resolving deltas: 100% (4/4)completed with 3 local objects\n    Checking connectivity: 393997, done.\n\nI.e. I produced a combination of bundles going up to a commit at the\nstart of this month, so that last \"Receiving objects\" is the only\ndynamic fetching of objects via the negotiation.\n\n1. https://lore.kernel.org/git/RFC-patch-01.13-4e1a0dbef5-20210805T150534Z-avarab@gmail.com/\n2. https://lore.kernel.org/git/RFC-patch-13.13-1e657ed27a-20210805T150534Z-avarab@gmail.com/\n"},{"id":"432206","messageId":"YQ2eLRjMRnVpdGVZ@google.com","threadId":"56220","inReplyTo":"87h7g2zd8f.fsf@evledraar.gmail.com","subject":"Re: [RFC PATCH 00/13] Add bundle-uri: resumably clones, static \"dumb\" CDN etc.","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2021-08-06T20:40:13Z","receivedAt":"2021-08-06T20:40:19Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Hi,\n\nÆvar Arnfjörð Bjarmason wrote:\n\n> Or perhaps not, but they're my currently my best effort to explain the\n> differences between the two and how they interact. So I think it's best\n> to point to those instead of coming up with something in this reply,\n> which'll inevitably be an incomplete rewrite of much of that.\n>\n> In short, there are use-cases that packfile-uri is inherently unsuitable\n> for, or rather changing the packfile-uri feature to support them would\n> pretty much make it indistinguishable from this bundle-uri mechanism,\n> which I think would just add more confusion to the protocol.\n\nHm.  I was hoping you might say more about those use cases --- e.g. is\nthere a concrete installation that wants to take advantage of this?\nBy focusing on the real-world example, we'd get a better shared\nunderstanding of the underlying constraints.\n\nAfter all, both are ways to reduce the bandwidth of a clone or other\nlarge fetch operation by offloading the bulk of content to static\nserving.\n\nThanks,\nJonathan\n"},{"id":"432223","messageId":"878s1eynew.fsf@evledraar.gmail.com","threadId":"56220","inReplyTo":"YQ2eLRjMRnVpdGVZ@google.com","subject":"Re: [RFC PATCH 00/13] Add bundle-uri: resumably clones, static \"dumb\" CDN etc.","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-08-07T02:19:59Z","receivedAt":"2021-08-07T02:54:53Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Fri, Aug 06 2021, Jonathan Nieder wrote:\n\n> Ævar Arnfjörð Bjarmason wrote:\n>\n>> Or perhaps not, but they're my currently my best effort to explain the\n>> differences between the two and how they interact. So I think it's best\n>> to point to those instead of coming up with something in this reply,\n>> which'll inevitably be an incomplete rewrite of much of that.\n>>\n>> In short, there are use-cases that packfile-uri is inherently unsuitable\n>> for, or rather changing the packfile-uri feature to support them would\n>> pretty much make it indistinguishable from this bundle-uri mechanism,\n>> which I think would just add more confusion to the protocol.\n>\n> Hm.  I was hoping you might say more about those use cases --- e.g. is\n> there a concrete installation that wants to take advantage of this?\n> By focusing on the real-world example, we'd get a better shared\n> understanding of the underlying constraints.\n\nI hacked this up for potential use on GitLab's infrastructure, mainly as\na mechanism to relieve CPU pressure on CI thundering herds.\n\nOften you need full clones, and you sometimes need to do those from\nscratch. When you've just had a push come in it's handy to convert those\nto incremental fetches on top of a bundle you made recently.\n\nIt's not deployed on anything currently, it's just something I've been\nhacking up. I'll be on vacation much of the rest of this month, the plan\nis to start stressing it on real-world use-cases after that. I thought\nI'd send this RFC first.\n\n> After all, both are ways to reduce the bandwidth of a clone or other\n> large fetch operation by offloading the bulk of content to static\n> serving.\n\nThe support we have for packfile-uri in git.git now as far as the server\nside goes, I think it's fair to say, fairly immature, I gather that\nJGit's version is more advanced, and that's what's serving up things at\nGoogle at e.g. https://chromium.googlesource.com.\n\nI.e. right now for git-upload-pack you need to exhaustively enumerate\nall the objects to exclude, although there's some on-list patches\nrecently for being able to supply tips.\n\nMore importantly your CDN reliability MUST match that of your git\nserver, otherwise your clone fails (as the server has already sent the\n\"other side\" of the expected CDN pack).\n\nFurthermore you MUST as the server be able to tell the client what pack\nchecksum on the CDN they should expect, which requires a very tight\ncoupling between git server and CDN.\n\nYou can't e.g. say \"bootstrap with this bundle/pack\" and point to\nsomething like Debian's async-updated FTP network as a source. The\nbootstrap data may or may not be there, and it may or may not be as\nup-to-date as you'd like.\n\nI think any proposed integration into git.git should mainly consider the\nbulk of users, the big hosting providers can always run with their own\npatches.\n\nI think this approach opens up the potential for easier and therefore\nwider CDN integration for git servers for providers that aren't one of\nthe big fish. E.g. your CDN generation can be daily cronjob, and the\nserver can point to it blindly and hope for the best. The client will\noptimistically use the CDN data, and recover if not.\n\nI think one thing I oversold is the \"path to resumable clones\",\ni.e. that's all true, but I don't think that's really any harder go do\nwith packfile-uri in theory (in practice just serving up a sensible pack\nwith it is pretty tedious with git-upload-pack as it stands).\n\nThe negotiation aspect of it is also interesting and something I've been\nexperimenting with. I.e. the bundles are what the client sends as its\nHAVE tips. This allows a server to anticipate what dialog newly cloning\nclients are likely to have, and even pre-populate a cache of that\nlocally (it could even serve that diff up as a packfile-uri :).\n\nRight now it retrieves each bundle in full before adding the tips to\nnegotiate to a subsequent dialog, but I've successfully experimental\nlocally with negotiating on the basis of objects we don't even have\nyet. I.e. download the bundle(s), and as soon as we have the header fire\noff the dialog with the server to get its PACK on the basis of those\npromised tips.\n\nIt makes the recovery a bit more involved in case the bundles don't have\nwhat we're after, but this allows us to disconnect really fast from the\nserver and twiddle our own thumbs while we finish downloading the\nbundles to get the full clone. We already disconnect early in cases\nwhere the bundle(s) already have what we're after.\n\nThis E-Mail got rather long, but hey, I did point you parts of this\nseries that cover some/most of this, and since it wasn't clear what if\nanything of that you'd read ... :) Hopefully this summary is useful.\n"},{"id":"432374","messageId":"e7fe220b-2877-107e-8f7e-ea507a65feff@gmail.com","threadId":"56220","inReplyTo":"RFC-cover-00.13-0000000000-20210805T150534Z-avarab@gmail.com","subject":"Re: [RFC PATCH 00/13] Add bundle-uri: resumably clones, static \"dumb\" CDN etc.","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2021-08-10T13:55:47Z","receivedAt":"2021-08-10T13:55:52Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 8/5/2021 11:07 AM, Ævar Arnfjörð Bjarmason wrote:> We're in the 2.33.0 rc cycle, and I'd hoped to have some more prep\n> work for this integrated already, but for now here's something\n> interesting I've been working on for early commentary/feedback.\n\nI've taking a brief look at the code, but mostly have thoughts about the\ncore idea based on reading the documentation. I'll keep my feedback here\nrestricted to that, especially because I saw some thoughts from Jonathan\nthat question the idea.\n\n> This adds the the ability to protocol v2 for servers to optimistically\n> pre-seed supporting clients with one or more bundles via a new\n> \"bundle-uri\" protocol extension.\n\nTo make sure I understand things correctly, I'm going to rewrite the\ndescription of the feature in my own words. Please correct me if I have\nmisread something.\n\nThis presents a new capability for protocol v2 that allows a server to\nnotify a client that they can download a bundle to bootstrap their object\ndatabase, and then come back to the origin server to \"catch up\" the\nremaining new objects since that bundle via a fetch negotiation.\n\nThis idea is similar to the packfile-uri feature in that it offloads some\nserver load to a dumb data store (like a CDN) but differs in a few key\nways:\n\n1. The bundle download does not need to be controlled by the server, and\n   the server doesn't even need to know its contents. The client will\n   discover the content and share its view in a later negotiation.\n\n2. The packfile-uri could advertise a pack that contains only the large\n   reachable blobs, and hence could use the \"happy path\" for bitmaps to\n   compute a packfile containing all reachable objects except these large\n   blobs. For the bundle-uri feature to mimic this, the blobs would need\n   to be reachable from refs (perhaps shallow tags?) but it would not be\n   clear that the negotiation would prevent redownloading those files.\n   This is an area to explore and expand upon.\n\n3. The bundle-uri feature is focused on \"clone\" scenarios, and I don't see\n   how it could be used to help \"fetch\" scenarios. To be fair, I also have\n   been vocal about how the packfile-uri feature is very hard to make\n   helpful for the \"fetch\" scenario. The benefits to \"clone\" seem to be\n   worth the effort alone. I think the bundle-api doubles-down on that\n   being the core functionality that matters, and that is fine by me. It\n   sacrifices the flexibility of the packfile-uri with a lower maintenance\n   burden for servers that want to implement it.\n\nThe biggest benefit that I see is that the Git server does not need to\nworry about whether the bundle has an exact set of data that it expects.\nThere is no timing issue about whether or not the exact packfile is seeded.\nInstead, the server could have a fixed URL for its bundle and update its\ncontents (atomically) at any schedule without worrying about concurrent\nclones being interrupted. From an operational perspective, I find the\nbundle-uri a better way to offload \"clone\" load.\n\nThis also depends on that following \"git fetch\" being easy to serve. In\nthat sense, it can be beneficial to be somewhat aware of the bundles that\nare being served: can we store the bundled refs as reachability bitmaps so\nwe have those available for the negotiation in the following \"git fetch\"\noperations? This choice seems specific to how the server is deciding to\ncreate these bundles.\n\nIt also presents interesting ideas for how to create the bundle to focus\non some core portion of the repo. The \"thundering herd\" of CI machines\nthat re-clone repos at a high rate will also be likely to use the\n\"--single-branch\" option to reduce the refs that they will ask for in the\nnegotiation. In that sense, we won't want a snapshot of all refs at a\ngiven time and instead might prefer a snapshot of the default branch or\nsome set of highly-active branches.\n\nOne question I saw Jonathan ask was \"can we modify the packfile-uri\ncapability to be better?\" I think this feature has enough different goals\nthat they could co-exist. That's the point of protocol v2, right? Servers\ncan implement and advertise the subset of functionality that they think is\nbest for their needs.\n\nI hope my ramblings provide some amount of clarity to the discussion, but\nalso I intend to show support of the idea. If I was given the choice of\nwhich feature to support (I mostly work on the client experience, so take\nmy choice with a grain of salt), then I would focus on implementing the\nbundle-uri capability _before_ the packfile-uri capability. And that's the\nbest part: more options present more flexibility for different hosts to\nmake different decisions.\n\nThanks,\n-Stolee\n"},{"id":"432376","messageId":"c7ba048f-86a9-6f29-476a-7d546e477285@gmail.com","threadId":"56220","inReplyTo":"RFC-patch-01.13-4e1a0dbef5-20210805T150534Z-avarab@gmail.com","subject":"Re: [RFC PATCH 01/13] serve: add command to advertise bundle URIs","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2021-08-10T13:58:54Z","receivedAt":"2021-08-10T13:58:58Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 8/5/2021 11:07 AM, Ævar Arnfjörð Bjarmason wrote:\n...\n> +bundle-uri CLIENT AND SERVER EXPECTATIONS\n> +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n> +\n> +The advertised bundles MUST contain one or more reference tips for use\n> +by the client. Bundles that are not self-contained MUST use the\n> +standard \"-\" prefixes in the bundle format to indicate their\n> +prerequisites. I.e. they must be in the standard format \"git bundle\n> +create\" would create.\n> +\n> +If after an `ls-refs` the client finds that the ref tips it wants can\n> +be retrieved entirety from advertised bundle(s), it MAY\n> +disconnect. The results of such a \"clone\" or \"fetch\" should be\n> +indistinguishable from the state attained without using bundle-uri.\n> +\n> +The client MAY also keep the connection open pending download of the\n> +bundle-uris, e.g. should on or more downloads (or their validation)\n> +fail.\n\nThe only technical thought I had (so far) about this proposal was that\nleaving the connection open while downloading the bundle would leave\nunnecessary load on the servers when no communication is happening.\nThere is a cost to keeping an open SSH connection, so here it would be\ngood to at least have the Git client close the connection after\ngetting a 200 response from the bundle (but not waiting for all of its\ncontents).\n\nThanks,\n-Stolee\n"},{"id":"433442","messageId":"87mtp8ffy9.fsf@evledraar.gmail.com","threadId":"56220","inReplyTo":"c7ba048f-86a9-6f29-476a-7d546e477285@gmail.com","subject":"Re: [RFC PATCH 01/13] serve: add command to advertise bundle URIs","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-08-23T13:25:08Z","receivedAt":"2021-08-23T13:28:36Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Tue, Aug 10 2021, Derrick Stolee wrote:\n\n> On 8/5/2021 11:07 AM, Ævar Arnfjörð Bjarmason wrote:\n> ...\n>> +bundle-uri CLIENT AND SERVER EXPECTATIONS\n>> +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n>> +\n>> +The advertised bundles MUST contain one or more reference tips for use\n>> +by the client. Bundles that are not self-contained MUST use the\n>> +standard \"-\" prefixes in the bundle format to indicate their\n>> +prerequisites. I.e. they must be in the standard format \"git bundle\n>> +create\" would create.\n>> +\n>> +If after an `ls-refs` the client finds that the ref tips it wants can\n>> +be retrieved entirety from advertised bundle(s), it MAY\n>> +disconnect. The results of such a \"clone\" or \"fetch\" should be\n>> +indistinguishable from the state attained without using bundle-uri.\n>> +\n>> +The client MAY also keep the connection open pending download of the\n>> +bundle-uris, e.g. should on or more downloads (or their validation)\n>> +fail.\n>\n> The only technical thought I had (so far) about this proposal was that\n> leaving the connection open while downloading the bundle would leave\n> unnecessary load on the servers when no communication is happening.\n> There is a cost to keeping an open SSH connection, so here it would be\n> good to at least have the Git client close the connection after\n> getting a 200 response from the bundle (but not waiting for all of its\n> contents).\n\nThanks. Yes it's something I'll have to fix. I was hoping that I'd get\naway with it for an initial implementation, but e.g. using\ntransfer.injectBundleURI to bootstrap chromium.git's repo from a bundle\nwill take so long that Google's server will give up and hang up on you.\n\nI wonder if it's something the transport layer should be doing in\ngeneral to resume connections if they go stale if it's at a point of\nclean separation in the dialog, but in any case I'll need it for\nbundle-uri.\n\nClosing the connection is also going to be more expensive in some cases,\ne.g. if the bundle takes 1s we'll open/close/download\nbundle-uri/open/close the connection, instead of of open/download\nbundle-uri/close. I wonder if anyone cares though, we can always apply\nsome heuristic later I guess...\n"},{"id":"433444","messageId":"87k0kcfdg0.fsf@evledraar.gmail.com","threadId":"56220","inReplyTo":"e7fe220b-2877-107e-8f7e-ea507a65feff@gmail.com","subject":"Re: [RFC PATCH 00/13] Add bundle-uri: resumably clones, static \"dumb\" CDN etc.","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-08-23T13:28:42Z","receivedAt":"2021-08-23T14:22:44Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Tue, Aug 10 2021, Derrick Stolee wrote:\n\n> On 8/5/2021 11:07 AM, Ævar Arnfjörð Bjarmason wrote:> We're in the 2.33.0 rc cycle, and I'd hoped to have some more prep\n>> work for this integrated already, but for now here's something\n>> interesting I've been working on for early commentary/feedback.\n>\n> I've taking a brief look at the code, but mostly have thoughts about the\n> core idea based on reading the documentation. I'll keep my feedback here\n> restricted to that, especially because I saw some thoughts from Jonathan\n> that question the idea.\n\nThanks a lot for taking a look, and sorry about the very late reply. I\nwas away from the computer for a while (on vacation).\n\n>> This adds the the ability to protocol v2 for servers to optimistically\n>> pre-seed supporting clients with one or more bundles via a new\n>> \"bundle-uri\" protocol extension.\n>\n> To make sure I understand things correctly, I'm going to rewrite the\n> description of the feature in my own words. Please correct me if I have\n> misread something.\n\nThanks. I'll probably try to steal some of this summary...\n\n> This presents a new capability for protocol v2 that allows a server to\n> notify a client that they can download a bundle to bootstrap their object\n> database, and then come back to the origin server to \"catch up\" the\n> remaining new objects since that bundle via a fetch negotiation.\n>\n> This idea is similar to the packfile-uri feature in that it offloads some\n> server load to a dumb data store (like a CDN) but differs in a few key\n> ways:\n>\n> 1. The bundle download does not need to be controlled by the server, and\n>    the server doesn't even need to know its contents. The client will\n>    discover the content and share its view in a later negotiation.\n\nCorrect.\n\n> 2. The packfile-uri could advertise a pack that contains only the large\n>    reachable blobs, and hence could use the \"happy path\" for bitmaps to\n>    compute a packfile containing all reachable objects except these large\n>    blobs. For the bundle-uri feature to mimic this, the blobs would need\n>    to be reachable from refs (perhaps shallow tags?) but it would not be\n>    clear that the negotiation would prevent redownloading those files.\n>    This is an area to explore and expand upon.\n\nCorrect that this isn't handled currently.\n\nThe few paragraphs below are mostly a (hopefully helpful) digression.\n\nDespite what I said in\nhttps://lore.kernel.org/git/87h7g2zd8f.fsf@evledraar.gmail.com/ about\npackfile-uri and bundle-uri playing nicely together there are cases\nwhere they won't. They won't error out or anything, just that combining\nthem in those cases won't make much sense.\n\nIn particular if you now use packfile-uri to exclude one giant blob from\nyour early history, you can't use bundle-uri to serve up that early\nhistory without also including that blob (as we'll expect the bundles to\nbe connected).\n\nThat property isn't inherent though, in the same way that we're clever\nabout .gitmodules checking we could make note of any missing content and\ndo a full connectivity check at the end.\n\nBut if you're using packfile-uri for that \"one big blob on a CDN\" I\ndon't see why you wouldn't be happy to serve it up in your bundle, so I\ndon't see much of a practical reason for further work in that area.\n\nAnd yes, you do run into limitations in the general negotiation\nmechanism eventually (which to be fair, we probably wouldn't care about\nbefore bundle-uri). E.g. we can't say we HAVE a blob OID currently\n(server expects commit OID).\n\nI don't see why it *couldn't* be supported though, and would be handled\nunder the hood with the same happy path for bitmaps that we use for\npackfile-uri exclusions now...\n\n> 3. The bundle-uri feature is focused on \"clone\" scenarios, and I don't see\n>    how it could be used to help \"fetch\" scenarios. To be fair, I also have\n>    been vocal about how the packfile-uri feature is very hard to make\n>    helpful for the \"fetch\" scenario. The benefits to \"clone\" seem to be\n>    worth the effort alone. I think the bundle-api doubles-down on that\n>    being the core functionality that matters, and that is fine by me. It\n>    sacrifices the flexibility of the packfile-uri with a lower maintenance\n>    burden for servers that want to implement it.\n\nNot correct, I'm going to have it handle incremental fetches.\n\nWhat *is* correct is that the initial RFC patches here entirely punt on\nit (I only hook them into clone.c, no fetch.c).\n\nI focused on the \"clone\" case to get to a MVP earlier, and as you note I\nthink in practice most users of this will want to only use it for clone\nbootstrapping, and won't care so much about the incremental case.\n\nBut for the \"fetch\" case we'd simply start fetching the N bundles in\nparallel, and read their headers, then abort fetching any whose header\ndeclares tips that we already have.\n\nSo e.g. for a repo with a \"big\" bundle that's 1 week old, and 7\nincremental bundles for the last 7 days of updates we'd quickly find\nthat we only need the last 3 if we updated 4 days ago, based on only\nfetching their headers.\n\nThe client spec is very liberal on \"MAY\" instead of \"MUST\" for\nsupporting clients. E.g. a client might sensibly conclude that their\nmost recent commits are recent enough, and that it would probably be a\nwaste of time to fetch the headers of the pointed-to bundles, and just\ndo a normal fetch instead.\n\nThe response format is also currently:\n\n    <URI>[ <optional key-value>*]\n\nAnd nothing uses the \"optional key-value\". One of the main things I had\nin mind was to support something like (none of these key-values are\nspecified currently):\n\n    https://example.com/big.bundle tip=deadbeef1\n    https://example.com/incremental-1.bundle prereq=deadbeef1 tip=deadbeef2\n    https://example.com/incremental-2.bundle prereq=deadbeef2 tip=deadbeef3\n\nOr:\n\n    https://example.com/big.bundle type=complete\n    https://example.com/incremental-1.bundle type=incremental\n    https://example.com/incremental-2.bundle type=incremental\n\nI.e. allow the server to up-front to optionally specify some details\nabout the pointed-to-bundles, purely so the client can avoid the work\nand roundtrip of trying to fetch the headers of N pointed-to bundles\njust to see which if any it needs for a clone/fetch.\n\nThis peeking behind the curtain would always yield to the source of\ntruth of inpecting the bundle header/content itself, so it would be\npurely an optional client hint. It's also not as flexible as a full\nbundle header (e.g. can't supply N tips or N prereqs, but for the common\ncase of \"one big history\" it should work nicely.\n\nBut maybe this use for key-value headers would be a premature\noptimization, I haven't actually benchmarked it...\n\n> The biggest benefit that I see is that the Git server does not need to\n> worry about whether the bundle has an exact set of data that it expects.\n> There is no timing issue about whether or not the exact packfile is seeded.\n> Instead, the server could have a fixed URL for its bundle and update its\n> contents (atomically) at any schedule without worrying about concurrent\n> clones being interrupted. From an operational perspective, I find the\n> bundle-uri a better way to offload \"clone\" load.\n\nThanks, yeah. That's pretty the main reason I hacked this up when we\nalready have packefile-uri, being able to e.g. have a dumb cronjob\n(which may not even run) optimistically handle your CDN offloading.\n\n> This also depends on that following \"git fetch\" being easy to serve. In\n> that sense, it can be beneficial to be somewhat aware of the bundles that\n> are being served: can we store the bundled refs as reachability bitmaps so\n> we have those available for the negotiation in the following \"git fetch\"\n> operations? This choice seems specific to how the server is deciding to\n> create these bundles.\n\nYeah, one of the first things I'm experimenting with on the server side\n(but haven't yet) is to generate bundles on the one hand, and being\naware of the last generated bundle do the equivalent of pre-populating\nthe pack-objects hook cache in anticipation of pre-seeded clients coming\nin.\n\n> It also presents interesting ideas for how to create the bundle to focus\n> on some core portion of the repo. The \"thundering herd\" of CI machines\n> that re-clone repos at a high rate will also be likely to use the\n> \"--single-branch\" option to reduce the refs that they will ask for in the\n> negotiation. In that sense, we won't want a snapshot of all refs at a\n> given time and instead might prefer a snapshot of the default branch or\n> some set of highly-active branches.\n\nYes, the current RFC MVP I have is rather dumb about this, but it's an\neasy follow-up change along with the \"fetch\" support to e.g. have a\nserver have one \"big\" bundle of just its main branch in anticipation of\n--single-branch --no-tags fetches, and then the next bundle is the delta\nbetween that and the other branches.\n\nSo the first is master, the second is master..next, master..seen + tags\netc. The client from ls-refs what tips it wants, so it can then ignore\nor retrieve the bundles as appropriate.\n\nA server operator can then play around with what they think might be the\noptimal arrangement and split-up of bundles.\n\nThey obviously can't handle all cases, e.g. if you have a bundle with\n\"master\" and clone just git.git's \"todo\" branch with these current\npatches you'll get redundant objects you don't need currently, but a\nslightly smarter client will be ignoring that bundle-uri.\n\n> One question I saw Jonathan ask was \"can we modify the packfile-uri\n> capability to be better?\" I think this feature has enough different goals\n> that they could co-exist. That's the point of protocol v2, right? Servers\n> can implement and advertise the subset of functionality that they think is\n> best for their needs.\n\n*Nod*, I also think as shown with these patches the overall complexity\nafter setting up the scaffolding for a new feature isn't very high, and\nmuch of it is going away in some generally useful prep patches I'm\ntrying to get in...\n\n> I hope my ramblings provide some amount of clarity to the discussion, but\n> also I intend to show support of the idea. If I was given the choice of\n> which feature to support (I mostly work on the client experience, so take\n> my choice with a grain of salt), then I would focus on implementing the\n> bundle-uri capability _before_ the packfile-uri capability. And that's the\n> best part: more options present more flexibility for different hosts to\n> make different decisions.\n\nThanks again. I'll keep you CC'd on future submissions if that's OK.\n\nOne thing I could really use to move this forward is code review of some\nof the outstanding serieses I've got, in particular these two are\nimmediately applicable to this one. It's based on the former, and I\nneeded to steal one patch from the latter:\n\nhttps://lore.kernel.org/git/cover-v4-00.10-00000000000-20210805T011823Z-avarab@gmail.com/\nhttps://lore.kernel.org/git/cover-v2-0.4-00000000000-20210823T110136Z-avarab@gmail.com/\n"},{"id":"433506","messageId":"c56bec68-bf00-f3b0-74b4-fd3e47319000@gmail.com","threadId":"56220","inReplyTo":"87k0kcfdg0.fsf@evledraar.gmail.com","subject":"Re: [RFC PATCH 00/13] Add bundle-uri: resumably clones, static \"dumb\" CDN etc.","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2021-08-24T02:03:11Z","receivedAt":"2021-08-24T02:03:17Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 8/23/2021 9:28 AM, Ævar Arnfjörð Bjarmason wrote:\n> \n> On Tue, Aug 10 2021, Derrick Stolee wrote:\n> \n>> On 8/5/2021 11:07 AM, Ævar Arnfjörð Bjarmason wrote:> We're in the 2.33.0 rc cycle, and I'd hoped to have some more prep\n\n(I'll skip the parts where you agree with me.)\n\n>> 3. The bundle-uri feature is focused on \"clone\" scenarios, and I don't see\n>>    how it could be used to help \"fetch\" scenarios. To be fair, I also have\n>>    been vocal about how the packfile-uri feature is very hard to make\n>>    helpful for the \"fetch\" scenario. The benefits to \"clone\" seem to be\n>>    worth the effort alone. I think the bundle-api doubles-down on that\n>>    being the core functionality that matters, and that is fine by me. It\n>>    sacrifices the flexibility of the packfile-uri with a lower maintenance\n>>    burden for servers that want to implement it.\n> \n> Not correct, I'm going to have it handle incremental fetches.\n> \n> What *is* correct is that the initial RFC patches here entirely punt on\n> it (I only hook them into clone.c, no fetch.c).\n> \n> I focused on the \"clone\" case to get to a MVP earlier, and as you note I\n> think in practice most users of this will want to only use it for clone\n> bootstrapping, and won't care so much about the incremental case.\n> \n> But for the \"fetch\" case we'd simply start fetching the N bundles in\n> parallel, and read their headers, then abort fetching any whose header\n> declares tips that we already have.\n\nTo save network time, we would need to read data as it is streaming\nfrom the network and stop the download when we realize we don't need\nthe bundle? This seems wasteful, but I'm willing to see it work in\nreality before judging it too harshly.\n\nIt would be better if the origin Git server could see the tips that\nthe user has and then only recommend bundles that serve new data.\nThat requires a closer awareness of which bundles contain which\ncontent.\n\n> So e.g. for a repo with a \"big\" bundle that's 1 week old, and 7\n> incremental bundles for the last 7 days of updates we'd quickly find\n> that we only need the last 3 if we updated 4 days ago, based on only\n> fetching their headers.\n\nI can attest that using time-based packs can be a good way to catch\nup based on pre-computed pack-files instead of generating one on\ndemand. The GVFS protocol has a \"prefetch\" endpoint that gives all\npre-computed pack-files since a given timestamp. (One big difference\nis that those pack-files contain only commits and trees, not blobs.\nWe should ensure that your proposal can extend to bundles that serve\nfiltered repos such as --filter=blob:none.)\n\nThe thing that works especially well with the GVFS protocol is that\nif we are missing a reachable object from one of those pack-files,\nthen it is downloaded as a one-off request _when needed_. This allows\nthe pack-files to be computed by independent cache servers that have\ntheir own clocks and compute their own timestamps and generate these\npack-files as \"new objects I fetched since the last timestamp\".\n\nThis is all to say that your timestamp-based approach will work as\nlong as you are very careful in how the incremental bundles are\nconstructed. There can really only be one list of bundles at a time,\nand somehow we need those servers to atomically swap their list of\nbundles when they are reorganized.\n\n> The client spec is very liberal on \"MAY\" instead of \"MUST\" for\n> supporting clients. E.g. a client might sensibly conclude that their\n> most recent commits are recent enough, and that it would probably be a\n> waste of time to fetch the headers of the pointed-to bundles, and just\n> do a normal fetch instead.\n\nSensible. I wouldn't expect anything less.\n\n> The response format is also currently:\n> \n>     <URI>[ <optional key-value>*]\n> \n> And nothing uses the \"optional key-value\". One of the main things I had\n> in mind was to support something like (none of these key-values are\n> specified currently):\n> \n>     https://example.com/big.bundle tip=deadbeef1\n>     https://example.com/incremental-1.bundle prereq=deadbeef1 tip=deadbeef2\n>     https://example.com/incremental-2.bundle prereq=deadbeef2 tip=deadbeef3\n> \n> Or:\n> \n>     https://example.com/big.bundle type=complete\n>     https://example.com/incremental-1.bundle type=incremental\n>     https://example.com/incremental-2.bundle type=incremental\n> \n> I.e. allow the server to up-front to optionally specify some details\n> about the pointed-to-bundles, purely so the client can avoid the work\n> and roundtrip of trying to fetch the headers of N pointed-to bundles\n> just to see which if any it needs for a clone/fetch.\n\nOk, this seems like a reasonable way forward. If the bundles are\ntruly focused on data within a core \"trunk\" (say, the reflog of\nthe default branch) then this works. It gets more complicated for\nrepos with many long-lived branches that merge together over the\nspan of weeks. I'm talking about extreme outliers like the Windows\nOS repository, so take this concern with a grain of salt. The\npoint is that in that world, a single tip isn't enough sometimes.\n\n...\n>> One question I saw Jonathan ask was \"can we modify the packfile-uri\n>> capability to be better?\" I think this feature has enough different goals\n>> that they could co-exist. That's the point of protocol v2, right? Servers\n>> can implement and advertise the subset of functionality that they think is\n>> best for their needs.\n> \n> *Nod*, I also think as shown with these patches the overall complexity\n> after setting up the scaffolding for a new feature isn't very high, and\n> much of it is going away in some generally useful prep patches I'm\n> trying to get in...\n> \n>> I hope my ramblings provide some amount of clarity to the discussion, but\n>> also I intend to show support of the idea. If I was given the choice of\n>> which feature to support (I mostly work on the client experience, so take\n>> my choice with a grain of salt), then I would focus on implementing the\n>> bundle-uri capability _before_ the packfile-uri capability. And that's the\n>> best part: more options present more flexibility for different hosts to\n>> make different decisions.\n> \n> Thanks again. I'll keep you CC'd on future submissions if that's OK.\n\nSounds good. I'll try to prioritize review.\n\n> One thing I could really use to move this forward is code review of some\n> of the outstanding serieses I've got, in particular these two are\n> immediately applicable to this one. It's based on the former, and I\n> needed to steal one patch from the latter:\n> \n> https://lore.kernel.org/git/cover-v4-00.10-00000000000-20210805T011823Z-avarab@gmail.com/\n> https://lore.kernel.org/git/cover-v2-0.4-00000000000-20210823T110136Z-avarab@gmail.com/\n\nI'll see what I can contribute here.\n\nThanks,\n-Stolee\n"},{"id":"433633","messageId":"YSVpLjAhhS8v2dR7@camp.crustytoothpaste.net","threadId":"56220","inReplyTo":"RFC-patch-13.13-1e657ed27a-20210805T150534Z-avarab@gmail.com","subject":"Re: [RFC PATCH 13/13] bundle-uri docs: add design notes","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2021-08-24T21:48:30Z","receivedAt":"2021-08-24T21:48:37Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"On 2021-08-05 at 15:07:29, Ævar Arnfjörð Bjarmason wrote:\n> Add a design doc for the bundle-uri protocol extension to go along\n> with the packfile-uri extension added in cd8402e0fd8 (Documentation:\n> add Packfile URIs design doc, 2020-06-10).\n> \n> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n> ---\n>  Documentation/technical/bundle-uri.txt  | 119 ++++++++++++++++++++++++\n>  Documentation/technical/protocol-v2.txt |   5 +\n>  2 files changed, 124 insertions(+)\n>  create mode 100644 Documentation/technical/bundle-uri.txt\n> \n> diff --git a/Documentation/technical/bundle-uri.txt b/Documentation/technical/bundle-uri.txt\n> new file mode 100644\n> index 0000000000..5ae9a15eaf\n> --- /dev/null\n> +++ b/Documentation/technical/bundle-uri.txt\n> @@ -0,0 +1,119 @@\n> +Bundle URI Design Notes\n> +=======================\n> +\n> +Protocol\n> +--------\n> +\n> +See `bundle-uri` in the link:protocol-v2.html[protocol-v2]\n> +documentation for a discussion of the bundle-uri command, and the\n> +expectations of clients and servers.\n> +\n> +This document is a a more general discussion of how the `bundle-uri`\n> +command fits in with the rest of the git ecosystem, its design goals\n> +and non-goals, comparison to alternatives etc.\n> +\n> +Comparison with Packfile URIs\n> +-----------------------------\n> +\n> +There is a similar \"Packfile URIs\" facility, see the\n> +link:packfile-uri.html[packfile-uri] documentation for details.\n> +\n> +The Packfile URIs facility requires a much closer cooperation between\n> +CDN and server than the bundle URI facility.\n> +\n> +I.e. the server MUST know what objects exist in the packfile URI it's\n> +pointing to, as well as its pack checksum. Failure to do so will not\n> +only result in a client error (the packfile hash won't match), but\n> +even if it got past that would likely result in a corrupt repository\n> +with tips pointing to unreachable objects.\n> +\n> +By comparison the bundle URIs are meant to be a \"dumb\" solution\n> +friendly to e.g. having a weekly cronjob take a snapshot of a git\n> +repository, that snapshot being uploaded to a network of FTP mirrors\n> +(which may be inconsistent or out of date).\n> +\n> +The server does not need to know what state the side-channel download\n> +is at, because the client will first validate it, and then optionally\n> +negotiate with the server using what it discovers there.\n> +\n> +Using the local `transfer.injectBundleURI` configuration variable (see\n> +linkgit:git-config[1]) the `bundle-uri` mechanism doesn't even need\n> +the server to support it.\n\nOne thing I'm not seeing with this doc that I brought up during the\npackfile URI discussion is that HTTPS is broken for a decent number of\nGit users, and for them SSH is the only viable option.  This is true for\nusers of certain antivirus programs on Windows, as well as people who\nhave certain corporate proxies in their workplace.  For those people, as\nsoon as the server offers a bundle URI, their connection will stop\nworking.\n\nI know that you're probably thinking, \"Gee, how often does that happen?\"\nbut judging by the number of people on StackOverflow, this is actually\nvery common.  The antivirus programs that break Git are actually not\nuncommon and they are widely deployed on corporate machines, plus the\nfact that lots of companies sell TLS intercepting proxies, which are\nalmost always broken in this way.  Many of these users don't even know\nwhat's going on, so they simply lack the knowledge to take any action or\nask their network administrator for a fix.  For them, HTTPS just doesn't\nwork with Git, while it does for a web browser.\n\nSo we will probably want to make this behavior opt-in with a config\noption for SSH, or just not available for SSH at all, so that we don't\nmagically break users on upgrade who are relying on the SSH protocol not\nusing HTTPS under the hood[0], especially the users who won't even know\nwhat's wrong.\n\n[0] I can't tell you how many times users have complained about the Git\nLFS SSH protocol also using HTTPS implicitly.\n-- \nbrian m. carlson (he/him or they/them)\nToronto, Ontario, CA\n"},{"id":"433673","messageId":"871r6ieaq7.fsf@evledraar.gmail.com","threadId":"56220","inReplyTo":"c56bec68-bf00-f3b0-74b4-fd3e47319000@gmail.com","subject":"Re: [RFC PATCH 00/13] Add bundle-uri: resumably clones, static \"dumb\" CDN etc.","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-08-24T22:00:22Z","receivedAt":"2021-08-24T22:31:18Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Mon, Aug 23 2021, Derrick Stolee wrote:\n\n> On 8/23/2021 9:28 AM, Ævar Arnfjörð Bjarmason wrote:\n>> \n>> On Tue, Aug 10 2021, Derrick Stolee wrote:\n>> \n>>> On 8/5/2021 11:07 AM, Ævar Arnfjörð Bjarmason wrote:> We're in the 2.33.0 rc cycle, and I'd hoped to have some more prep\n>\n> (I'll skip the parts where you agree with me.)\n>\n>>> 3. The bundle-uri feature is focused on \"clone\" scenarios, and I don't see\n>>>    how it could be used to help \"fetch\" scenarios. To be fair, I also have\n>>>    been vocal about how the packfile-uri feature is very hard to make\n>>>    helpful for the \"fetch\" scenario. The benefits to \"clone\" seem to be\n>>>    worth the effort alone. I think the bundle-api doubles-down on that\n>>>    being the core functionality that matters, and that is fine by me. It\n>>>    sacrifices the flexibility of the packfile-uri with a lower maintenance\n>>>    burden for servers that want to implement it.\n>> \n>> Not correct, I'm going to have it handle incremental fetches.\n>> \n>> What *is* correct is that the initial RFC patches here entirely punt on\n>> it (I only hook them into clone.c, no fetch.c).\n>> \n>> I focused on the \"clone\" case to get to a MVP earlier, and as you note I\n>> think in practice most users of this will want to only use it for clone\n>> bootstrapping, and won't care so much about the incremental case.\n>> \n>> But for the \"fetch\" case we'd simply start fetching the N bundles in\n>> parallel, and read their headers, then abort fetching any whose header\n>> declares tips that we already have.\n>\n> To save network time, we would need to read data as it is streaming\n> from the network and stop the download when we realize we don't need\n> the bundle? This seems wasteful, but I'm willing to see it work in\n> reality before judging it too harshly.\n\nThe common case would be making a Range request to get (what's most\nlikely to contain) the header, so not deciding on the fly while we're\nredundantly getting data we may not use from the server.\n\nBut yes, I think for that incremental case it's likely to be better to\nskip it entirely for small-ish fetches, both for client & server.\n\nAn out by default here is that a server operator would be unlikely to\nconfigure this if they don't suspect that it's helping their use-case.\n\nI also wonder if I shouldn't make it explicit from day 1 in the spec\nthat either the first listed bundle is always one without prereqs, so we\ncould short-circuit this on \"fetch\" for what's likely to be the common\ncase of a server operator who's only interested in this for \"clone\",\ni.e. without any incremental bundles.\n\nIt would mostly close the door (or rather, result in redundant requests\nfor) someone who wants incremental-only bundles, but I can't imagine\nanyone using this at all would want that, but maybe...\n\n> It would be better if the origin Git server could see the tips that\n> the user has and then only recommend bundles that serve new data.\n> That requires a closer awareness of which bundles contain which\n> content.\n\nOnce we're imagining this happening after the user sends over the tips\nthey have we're really starting to talk about something that's pretty\nmuch indistinguishable from packfile-uri.\n\nI.e. that happen after ls-refs & fetch negotiation, whereas bundle-uri\nhappens after ls-refs, but pre-negotiation.\n\n\"Pretty much\" because we can imagine that instead of the server sending\nthe packfile-uri they'd send some hybrid of bundle-uri and packfile-uri\nwhere instead of sending a PACK they say \"oh if you want these tips\nhere's a bundle that has most/all of it, fetch that and get back to me\".\n\nExcept the \"get back to me\" probably can't happen, the client would\neither need to keep the server waiting as they fetch that\nhybrid-bundle-uri, or disconnect, fetch it, and then re-do the\nnegotiation.\n\nI can see how it might be useful if you're generating incremental\nbundles anyway, but I think mostly I'd just say \"well, use packfile-uri\nthen\", but maybe I'm not being imaginative enough ... :)\n\n>> So e.g. for a repo with a \"big\" bundle that's 1 week old, and 7\n>> incremental bundles for the last 7 days of updates we'd quickly find\n>> that we only need the last 3 if we updated 4 days ago, based on only\n>> fetching their headers.\n>\n> I can attest that using time-based packs can be a good way to catch\n> up based on pre-computed pack-files instead of generating one on\n> demand. The GVFS protocol has a \"prefetch\" endpoint that gives all\n> pre-computed pack-files since a given timestamp. (One big difference\n> is that those pack-files contain only commits and trees, not blobs.\n> We should ensure that your proposal can extend to bundles that serve\n> filtered repos such as --filter=blob:none.)\n\nI may be wrong, and I'm at all familiar with the guts of these filtering\nmechanisms, but would that even be possible? Doesn't that now involve\nthe server sending over a filtered PACK payload without the blobs per\nclient request?\n\nWhereas a bundle per my understanding must always be complete as far as\na commit range goes, i.e. it can have prereqs, but those prereqs are\ncommits, not arbitrary OIDs. But maybe arbitrary OIDs (i.e. the OIDs of\nthe missing blob/trees etc.) would work, I haven't tried to manually\ncraft such a thing...\n\n> The thing that works especially well with the GVFS protocol is that\n> if we are missing a reachable object from one of those pack-files,\n> then it is downloaded as a one-off request _when needed_. This allows\n> the pack-files to be computed by independent cache servers that have\n> their own clocks and compute their own timestamps and generate these\n> pack-files as \"new objects I fetched since the last timestamp\".\n\nI *think* that's covered by the above, but not sure, i.e. we're still\ndiscussing these filtered PACKs I think...\n\n> This is all to say that your timestamp-based approach will work as\n> long as you are very careful in how the incremental bundles are\n> constructed. There can really only be one list of bundles at a time,\n> and somehow we need those servers to atomically swap their list of\n> bundles when they are reorganized.\n\nThe current implementation theoretically racy in that we could have\nbundle uris on the server defined in different parts of config (e.g. via\nincludes), and have a A..F \"big\", bundle, then F..G, G..H etc, but in\nthe middle of a swap-out a client might see A..E, F..G.\n\nI think in practice the general way we read/write config in one location\nshould alleviate that, and for any remaining races the client will\nhandle it gracefully, i.e. we'll fetch that A..E try to apply (or\nrather, read fail on reading the prereqs in the header) F..G on top of\nit, fail and just fall back on a negotiation from E, rather than the\nexpected G.\n\n>> The client spec is very liberal on \"MAY\" instead of \"MUST\" for\n>> supporting clients. E.g. a client might sensibly conclude that their\n>> most recent commits are recent enough, and that it would probably be a\n>> waste of time to fetch the headers of the pointed-to bundles, and just\n>> do a normal fetch instead.\n>\n> Sensible. I wouldn't expect anything less.\n\n*Nod*, but it's useful to note that by comparison with packfile-uri, the\nplan here is to have the server and its CDN not have a hard dependency\non one another. I.e. with packfile-uri if your server and CDN have a 99%\navailability, your total availability is .99^2 =~ 98.01%.\n\nSo it's inherently a softer and more optimistic way to handle failures,\nand allows server operators who aren't 100% sure of their CDN never\nfailing them (least clones start failing) to experiment with using one.\n\n>> The response format is also currently:\n>> \n>>     <URI>[ <optional key-value>*]\n>> \n>> And nothing uses the \"optional key-value\". One of the main things I had\n>> in mind was to support something like (none of these key-values are\n>> specified currently):\n>> \n>>     https://example.com/big.bundle tip=deadbeef1\n>>     https://example.com/incremental-1.bundle prereq=deadbeef1 tip=deadbeef2\n>>     https://example.com/incremental-2.bundle prereq=deadbeef2 tip=deadbeef3\n>> \n>> Or:\n>> \n>>     https://example.com/big.bundle type=complete\n>>     https://example.com/incremental-1.bundle type=incremental\n>>     https://example.com/incremental-2.bundle type=incremental\n>> \n>> I.e. allow the server to up-front to optionally specify some details\n>> about the pointed-to-bundles, purely so the client can avoid the work\n>> and roundtrip of trying to fetch the headers of N pointed-to bundles\n>> just to see which if any it needs for a clone/fetch.\n>\n> Ok, this seems like a reasonable way forward. If the bundles are\n> truly focused on data within a core \"trunk\" (say, the reflog of\n> the default branch) then this works. It gets more complicated for\n> repos with many long-lived branches that merge together over the\n> span of weeks. I'm talking about extreme outliers like the Windows\n> OS repository, so take this concern with a grain of salt. The\n> point is that in that world, a single tip isn't enough sometimes.\n\n*Nod*, but note that this would just be a shortcut over using the Range\nrequest to get the header discussed above.\n\nFor the Windows repository you could presumably (using git.git as an\nexample) have bundles for:\n\n    ..master (the big bundle, no prereqs)\n    master..next\n    next..ds/add-with-sparse-index\n    next..ab/serve-cleanup\n\nI.e. ds/add-with-sparse-index are two divergent branches from \"next\"\nthat we'd like to get.\n\nA bundle header could of course have N branches and N prereqs, but for\nthe prereq/tip suggested above we'd handle the common case of a small\nnumber of \"big topics\" that are likely to diverge quite a bit, which I\nimagine the use-case you're noting is like.\n\n>>> One question I saw Jonathan ask was \"can we modify the packfile-uri\n>>> capability to be better?\" I think this feature has enough different goals\n>>> that they could co-exist. That's the point of protocol v2, right? Servers\n>>> can implement and advertise the subset of functionality that they think is\n>>> best for their needs.\n>> \n>> *Nod*, I also think as shown with these patches the overall complexity\n>> after setting up the scaffolding for a new feature isn't very high, and\n>> much of it is going away in some generally useful prep patches I'm\n>> trying to get in...\n>> \n>>> I hope my ramblings provide some amount of clarity to the discussion, but\n>>> also I intend to show support of the idea. If I was given the choice of\n>>> which feature to support (I mostly work on the client experience, so take\n>>> my choice with a grain of salt), then I would focus on implementing the\n>>> bundle-uri capability _before_ the packfile-uri capability. And that's the\n>>> best part: more options present more flexibility for different hosts to\n>>> make different decisions.\n>> \n>> Thanks again. I'll keep you CC'd on future submissions if that's OK.\n>\n> Sounds good. I'll try to prioritize review.\n>\n>> One thing I could really use to move this forward is code review of some\n>> of the outstanding serieses I've got, in particular these two are\n>> immediately applicable to this one. It's based on the former, and I\n>> needed to steal one patch from the latter:\n>> \n>> https://lore.kernel.org/git/cover-v4-00.10-00000000000-20210805T011823Z-avarab@gmail.com/\n>> https://lore.kernel.org/git/cover-v2-0.4-00000000000-20210823T110136Z-avarab@gmail.com/\n>\n> I'll see what I can contribute here.\n\nThanks, the reviews on the \"unbundle progress\" are already very\nuseful...\n"},{"id":"433674","messageId":"87y28qcvrp.fsf@evledraar.gmail.com","threadId":"56220","inReplyTo":"YSVpLjAhhS8v2dR7@camp.crustytoothpaste.net","subject":"Re: [RFC PATCH 13/13] bundle-uri docs: add design notes","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-08-24T22:33:04Z","receivedAt":"2021-08-24T22:39:42Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Tue, Aug 24 2021, brian m. carlson wrote:\n\n> [[PGP Signed Part:Undecided]]\n> On 2021-08-05 at 15:07:29, Ævar Arnfjörð Bjarmason wrote:\n>> Add a design doc for the bundle-uri protocol extension to go along\n>> with the packfile-uri extension added in cd8402e0fd8 (Documentation:\n>> add Packfile URIs design doc, 2020-06-10).\n>> \n>> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n>> ---\n>>  Documentation/technical/bundle-uri.txt  | 119 ++++++++++++++++++++++++\n>>  Documentation/technical/protocol-v2.txt |   5 +\n>>  2 files changed, 124 insertions(+)\n>>  create mode 100644 Documentation/technical/bundle-uri.txt\n>> \n>> diff --git a/Documentation/technical/bundle-uri.txt b/Documentation/technical/bundle-uri.txt\n>> new file mode 100644\n>> index 0000000000..5ae9a15eaf\n>> --- /dev/null\n>> +++ b/Documentation/technical/bundle-uri.txt\n>> @@ -0,0 +1,119 @@\n>> +Bundle URI Design Notes\n>> +=======================\n>> +\n>> +Protocol\n>> +--------\n>> +\n>> +See `bundle-uri` in the link:protocol-v2.html[protocol-v2]\n>> +documentation for a discussion of the bundle-uri command, and the\n>> +expectations of clients and servers.\n>> +\n>> +This document is a a more general discussion of how the `bundle-uri`\n>> +command fits in with the rest of the git ecosystem, its design goals\n>> +and non-goals, comparison to alternatives etc.\n>> +\n>> +Comparison with Packfile URIs\n>> +-----------------------------\n>> +\n>> +There is a similar \"Packfile URIs\" facility, see the\n>> +link:packfile-uri.html[packfile-uri] documentation for details.\n>> +\n>> +The Packfile URIs facility requires a much closer cooperation between\n>> +CDN and server than the bundle URI facility.\n>> +\n>> +I.e. the server MUST know what objects exist in the packfile URI it's\n>> +pointing to, as well as its pack checksum. Failure to do so will not\n>> +only result in a client error (the packfile hash won't match), but\n>> +even if it got past that would likely result in a corrupt repository\n>> +with tips pointing to unreachable objects.\n>> +\n>> +By comparison the bundle URIs are meant to be a \"dumb\" solution\n>> +friendly to e.g. having a weekly cronjob take a snapshot of a git\n>> +repository, that snapshot being uploaded to a network of FTP mirrors\n>> +(which may be inconsistent or out of date).\n>> +\n>> +The server does not need to know what state the side-channel download\n>> +is at, because the client will first validate it, and then optionally\n>> +negotiate with the server using what it discovers there.\n>> +\n>> +Using the local `transfer.injectBundleURI` configuration variable (see\n>> +linkgit:git-config[1]) the `bundle-uri` mechanism doesn't even need\n>> +the server to support it.\n>\n> One thing I'm not seeing with this doc that I brought up during the\n> packfile URI discussion is that HTTPS is broken for a decent number of\n> Git users, and for them SSH is the only viable option.  This is true for\n> users of certain antivirus programs on Windows, as well as people who\n> have certain corporate proxies in their workplace.  For those people, as\n> soon as the server offers a bundle URI, their connection will stop\n> working.\n>\n> I know that you're probably thinking, \"Gee, how often does that happen?\"\n> but judging by the number of people on StackOverflow, this is actually\n> very common.  The antivirus programs that break Git are actually not\n> uncommon and they are widely deployed on corporate machines, plus the\n> fact that lots of companies sell TLS intercepting proxies, which are\n> almost always broken in this way.  Many of these users don't even know\n> what's going on, so they simply lack the knowledge to take any action or\n> ask their network administrator for a fix.  For them, HTTPS just doesn't\n> work with Git, while it does for a web browser.\n>\n> So we will probably want to make this behavior opt-in with a config\n> option for SSH, or just not available for SSH at all, so that we don't\n> magically break users on upgrade who are relying on the SSH protocol not\n> using HTTPS under the hood[0], especially the users who won't even know\n> what's wrong.\n\nGood point, I think this sort of thing will be a non-issue with\nbundle-uri, because in general it handles any sort of network / fetching\n/ validation failures gracefully. I.e. with these patches you can point\nat a bad URI, broken non-bundle etc. We'll just move on to a full clone.\n\nWhereas with packfile-uri the inline PACK and the URI are things you\nMUST both get, as the provided packfile-uri completes the incomplete\ninline PACK. So once you say that you're willing to accept things over\nhttps, you MUST be able to get that thing.\n\nWe'll still waste a bit of time trying though with bundle-uri. But I\nthink for the common case of bundle-uri helping more than not (which\npresumably, the server operator has tested), it's a better default to\ntry https:// even if the main dialog is over ssh://.\n"}]}