clone: add --bundle-uri option

Cloning a remote repository is one of the most expensive operations in Git. The server can spend a lot of CPU time generating a pack-file for the client's request. The amount of data can clog the network for a long time, and the Git protocol is not resumable. For users with poor network connections or are located far away from the origin server, this can be especially painful. Add a new '--bundle-uri' option to 'git clone' to bootstrap a clone from a bundle. If the user is aware of a bundle server, then they can tell Git to bootstrap the new repository with these bundles before fetching the remaining objects from the origin server. Reviewed-by: Josh Steadmon <steadmon@google.com> Signed-off-by: Derrick Stolee <derrickstolee@github.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Derrick Stolee committed Aug 9, 2022 at 13:11 UTC 55568919616429fbc209880cf189a3adaceb6093
3 files changed +57
Documentation/git-clone.txt
+6
@@ -323,6 +323,12 @@ or `--mirror` is given)
323 for `host.xz:foo/.git`). Cloning into an existing directory
324 is only allowed if the directory is empty.
325
326 +--bundle-uri=<uri>::
327 + Before fetching from the remote, fetch a bundle from the given
328 + `<uri>` and unbundle the data into the local repository. The refs
329 + in the bundle will be stored under the hidden `refs/bundle/*`
330 + namespace.
331 +
332 :git-clone: 1
333 include::urls.txt[]
334
builtin/clone.c
+15
@@ -34,6 +34,7 @@
34 #include "list-objects-filter-options.h"
35 #include "hook.h"
36 #include "bundle.h"
37 +#include "bundle-uri.h"
38
39 /*
40 * Overall FIXMEs:
@@ -77,6 +78,7 @@ static int option_filter_submodules = -1; /* unspecified */
78 static int config_filter_submodules = -1; /* unspecified */
79 static struct string_list server_options = STRING_LIST_INIT_NODUP;
80 static int option_remote_submodules;
81 +static const char *bundle_uri;
82
83 static int recurse_submodules_cb(const struct option *opt,
84 const char *arg, int unset)
@@ -160,6 +162,8 @@ static struct option builtin_clone_options[] = {
162 N_("any cloned submodules will use their remote-tracking branch")),
163 OPT_BOOL(0, "sparse", &option_sparse_checkout,
164 N_("initialize sparse-checkout file to include only files at root")),
165 + OPT_STRING(0, "bundle-uri", &bundle_uri,
166 + N_("uri"), N_("a URI for downloading bundles before fetching from origin remote")),
167 OPT_END()
168 };
169
@@ -1232,6 +1236,17 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
1236 if (transport->smart_options && !deepen && !filter_options.choice)
1237 transport->smart_options->check_self_contained_and_connected = 1;
1238
1239 + /*
1240 + * Before fetching from the remote, download and install bundle
1241 + * data from the --bundle-uri option.
1242 + */
1243 + if (bundle_uri) {
1244 + /* At this point, we need the_repository to match the cloned repo. */
1245 + repo_init(the_repository, git_dir, work_tree);
1246 + if (fetch_bundle_uri(the_repository, bundle_uri))
1247 + warning(_("failed to fetch objects from bundle URI '%s'"),
1248 + bundle_uri);
1249 + }
1250
1251 strvec_push(&transport_ls_refs_options.ref_prefixes, "HEAD");
1252 refspec_ref_prefixes(&remote->fetch,
t/t5558-clone-bundle-uri.sh new
+36
@@ -0,0 +1,36 @@
1 +#!/bin/sh
2 +
3 +test_description='test fetching bundles with --bundle-uri'
4 +
5 +. ./test-lib.sh
6 +
7 +test_expect_success 'fail to clone from non-existent file' '
8 + test_when_finished rm -rf test &&
9 + git clone --bundle-uri="$(pwd)/does-not-exist" . test 2>err &&
10 + grep "failed to download bundle from URI" err
11 +'
12 +
13 +test_expect_success 'fail to clone from non-bundle file' '
14 + test_when_finished rm -rf test &&
15 + echo bogus >bogus &&
16 + git clone --bundle-uri="$(pwd)/bogus" . test 2>err &&
17 + grep "is not a bundle" err
18 +'
19 +
20 +test_expect_success 'create bundle' '
21 + git init clone-from &&
22 + git -C clone-from checkout -b topic &&
23 + test_commit -C clone-from A &&
24 + test_commit -C clone-from B &&
25 + git -C clone-from bundle create B.bundle topic
26 +'
27 +
28 +test_expect_success 'clone with path bundle' '
29 + git clone --bundle-uri="clone-from/B.bundle" \
30 + clone-from clone-path &&
31 + git -C clone-path rev-parse refs/bundles/topic >actual &&
32 + git -C clone-from rev-parse topic >expect &&
33 + test_cmp expect actual
34 +'
35 +
36 +test_done