Skip to content

Commit 6ba7107

Browse files
committed
Merge branch 'ds/bundle-uri-clone' into seen
* ds/bundle-uri-clone: clone: --bundle-uri cannot be combined with --depth bundle-uri: add support for http(s):// and file:// clone: add --bundle-uri option bundle-uri: create basic file-copy logic remote-curl: add 'get' capability
2 parents b9ec207 + 1e3255b commit 6ba7107

File tree

10 files changed

+375
-0
lines changed

10 files changed

+375
-0
lines changed

Documentation/git-clone.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,13 @@ or `--mirror` is given)
323323
for `host.xz:foo/.git`). Cloning into an existing directory
324324
is only allowed if the directory is empty.
325325

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. This option is incompatible with `--depth`,
331+
`--shallow-since`, and `--shallow-exclude`.
332+
326333
:git-clone: 1
327334
include::urls.txt[]
328335

Documentation/gitremote-helpers.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,9 @@ Supported commands: 'list', 'import'.
168168
Can guarantee that when a clone is requested, the received
169169
pack is self contained and is connected.
170170

171+
'get'::
172+
Can use the 'get' command to download a file from a given URI.
173+
171174
If a helper advertises 'connect', Git will use it if possible and
172175
fall back to another capability if the helper requests so when
173176
connecting (see the 'connect' command under COMMANDS).
@@ -418,6 +421,12 @@ Supported if the helper has the "connect" capability.
418421
+
419422
Supported if the helper has the "stateless-connect" capability.
420423

424+
'get' <uri> <path>::
425+
Downloads the file from the given `<uri>` to the given `<path>`. If
426+
`<path>.temp` exists, then Git assumes that the `.temp` file is a
427+
partial download from a previous attempt and will resume the
428+
download from that position.
429+
421430
If a fatal error occurs, the program writes the error message to
422431
stderr and exits. The caller should expect that a suitable error
423432
message has been printed if the child closes the connection without

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -906,6 +906,7 @@ LIB_OBJS += blob.o
906906
LIB_OBJS += bloom.o
907907
LIB_OBJS += branch.o
908908
LIB_OBJS += bulk-checkin.o
909+
LIB_OBJS += bundle-uri.o
909910
LIB_OBJS += bundle.o
910911
LIB_OBJS += cache-tree.o
911912
LIB_OBJS += cbtree.o

builtin/clone.c

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
#include "list-objects-filter-options.h"
3535
#include "hook.h"
3636
#include "bundle.h"
37+
#include "bundle-uri.h"
3738

3839
/*
3940
* Overall FIXMEs:
@@ -77,6 +78,7 @@ static int option_filter_submodules = -1; /* unspecified */
7778
static int config_filter_submodules = -1; /* unspecified */
7879
static struct string_list server_options = STRING_LIST_INIT_NODUP;
7980
static int option_remote_submodules;
81+
static const char *bundle_uri;
8082

8183
static int recurse_submodules_cb(const struct option *opt,
8284
const char *arg, int unset)
@@ -160,6 +162,8 @@ static struct option builtin_clone_options[] = {
160162
N_("any cloned submodules will use their remote-tracking branch")),
161163
OPT_BOOL(0, "sparse", &option_sparse_checkout,
162164
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")),
163167
OPT_END()
164168
};
165169

@@ -933,6 +937,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
933937
option_no_checkout = 1;
934938
}
935939

940+
if (bundle_uri && deepen)
941+
die(_("--bundle-uri is incompatible with --depth, --shallow-since, and --shallow-exclude"));
942+
936943
repo_name = argv[0];
937944

938945
path = get_repo_path(repo_name, &is_bundle);
@@ -1232,6 +1239,17 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
12321239
if (transport->smart_options && !deepen && !filter_options.choice)
12331240
transport->smart_options->check_self_contained_and_connected = 1;
12341241

1242+
/*
1243+
* Before fetching from the remote, download and install bundle
1244+
* data from the --bundle-uri option.
1245+
*/
1246+
if (bundle_uri) {
1247+
/* At this point, we need the_repository to match the cloned repo. */
1248+
repo_init(the_repository, git_dir, work_tree);
1249+
if (fetch_bundle_uri(the_repository, bundle_uri))
1250+
warning(_("failed to fetch objects from bundle URI '%s'"),
1251+
bundle_uri);
1252+
}
12351253

12361254
strvec_push(&transport_ls_refs_options.ref_prefixes, "HEAD");
12371255
refspec_ref_prefixes(&remote->fetch,

bundle-uri.c

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
#include "cache.h"
2+
#include "bundle-uri.h"
3+
#include "bundle.h"
4+
#include "object-store.h"
5+
#include "refs.h"
6+
#include "run-command.h"
7+
8+
static int find_temp_filename(struct strbuf *name)
9+
{
10+
int fd;
11+
/*
12+
* Find a temporary filename that is available. This is briefly
13+
* racy, but unlikely to collide.
14+
*/
15+
fd = odb_mkstemp(name, "bundles/tmp_uri_XXXXXX");
16+
if (fd < 0) {
17+
warning(_("failed to create temporary file"));
18+
return -1;
19+
}
20+
21+
close(fd);
22+
unlink(name->buf);
23+
return 0;
24+
}
25+
26+
static int download_https_uri_to_file(const char *file, const char *uri)
27+
{
28+
int result = 0;
29+
struct child_process cp = CHILD_PROCESS_INIT;
30+
FILE *child_in = NULL, *child_out = NULL;
31+
struct strbuf line = STRBUF_INIT;
32+
int found_get = 0;
33+
34+
strvec_pushl(&cp.args, "git-remote-https", "origin", uri, NULL);
35+
cp.in = -1;
36+
cp.out = -1;
37+
38+
if (start_command(&cp))
39+
return 1;
40+
41+
child_in = fdopen(cp.in, "w");
42+
if (!child_in) {
43+
result = 1;
44+
goto cleanup;
45+
}
46+
47+
child_out = fdopen(cp.out, "r");
48+
if (!child_out) {
49+
result = 1;
50+
goto cleanup;
51+
}
52+
53+
fprintf(child_in, "capabilities\n");
54+
fflush(child_in);
55+
56+
while (!strbuf_getline(&line, child_out)) {
57+
if (!line.len)
58+
break;
59+
if (!strcmp(line.buf, "get"))
60+
found_get = 1;
61+
}
62+
strbuf_release(&line);
63+
64+
if (!found_get) {
65+
result = error(_("insufficient capabilities"));
66+
goto cleanup;
67+
}
68+
69+
fprintf(child_in, "get %s %s\n\n", uri, file);
70+
71+
cleanup:
72+
if (child_in)
73+
fclose(child_in);
74+
if (finish_command(&cp))
75+
return 1;
76+
if (child_out)
77+
fclose(child_out);
78+
return result;
79+
}
80+
81+
static int copy_uri_to_file(const char *filename, const char *uri)
82+
{
83+
const char *out;
84+
85+
if (skip_prefix(uri, "https:", &out) ||
86+
skip_prefix(uri, "http:", &out))
87+
return download_https_uri_to_file(filename, uri);
88+
89+
if (!skip_prefix(uri, "file://", &out))
90+
out = uri;
91+
92+
/* Copy as a file */
93+
return copy_file(filename, out, 0);
94+
}
95+
96+
static int unbundle_from_file(struct repository *r, const char *file)
97+
{
98+
int result = 0;
99+
int bundle_fd;
100+
struct bundle_header header = BUNDLE_HEADER_INIT;
101+
struct string_list_item *refname;
102+
struct strbuf bundle_ref = STRBUF_INIT;
103+
size_t bundle_prefix_len;
104+
105+
if ((bundle_fd = read_bundle_header(file, &header)) < 0)
106+
return 1;
107+
108+
if ((result = unbundle(r, &header, bundle_fd, NULL)))
109+
return 1;
110+
111+
/*
112+
* Convert all refs/heads/ from the bundle into refs/bundles/
113+
* in the local repository.
114+
*/
115+
strbuf_addstr(&bundle_ref, "refs/bundles/");
116+
bundle_prefix_len = bundle_ref.len;
117+
118+
for_each_string_list_item(refname, &header.references) {
119+
struct object_id *oid = refname->util;
120+
struct object_id old_oid;
121+
const char *branch_name;
122+
int has_old;
123+
124+
if (!skip_prefix(refname->string, "refs/heads/", &branch_name))
125+
continue;
126+
127+
strbuf_setlen(&bundle_ref, bundle_prefix_len);
128+
strbuf_addstr(&bundle_ref, branch_name);
129+
130+
has_old = !read_ref(bundle_ref.buf, &old_oid);
131+
update_ref("fetched bundle", bundle_ref.buf, oid,
132+
has_old ? &old_oid : NULL,
133+
REF_SKIP_OID_VERIFICATION,
134+
UPDATE_REFS_MSG_ON_ERR);
135+
}
136+
137+
bundle_header_release(&header);
138+
return result;
139+
}
140+
141+
int fetch_bundle_uri(struct repository *r, const char *uri)
142+
{
143+
int result = 0;
144+
struct strbuf filename = STRBUF_INIT;
145+
146+
if ((result = find_temp_filename(&filename)))
147+
goto cleanup;
148+
149+
if ((result = copy_uri_to_file(filename.buf, uri))) {
150+
warning(_("failed to download bundle from URI '%s'"), uri);
151+
goto cleanup;
152+
}
153+
154+
if ((result = !is_bundle(filename.buf, 0))) {
155+
warning(_("file at URI '%s' is not a bundle"), uri);
156+
goto cleanup;
157+
}
158+
159+
if ((result = unbundle_from_file(r, filename.buf))) {
160+
warning(_("failed to unbundle bundle from URI '%s'"), uri);
161+
goto cleanup;
162+
}
163+
164+
cleanup:
165+
unlink(filename.buf);
166+
strbuf_release(&filename);
167+
return result;
168+
}

bundle-uri.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#ifndef BUNDLE_URI_H
2+
#define BUNDLE_URI_H
3+
4+
struct repository;
5+
6+
/**
7+
* Fetch data from the given 'uri' and unbundle the bundle data found
8+
* based on that information.
9+
*
10+
* Returns non-zero if no bundle information is found at the given 'uri'.
11+
*/
12+
int fetch_bundle_uri(struct repository *r, const char *uri);
13+
14+
#endif

remote-curl.c

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1286,6 +1286,33 @@ static void parse_fetch(struct strbuf *buf)
12861286
strbuf_reset(buf);
12871287
}
12881288

1289+
static void parse_get(struct strbuf *buf)
1290+
{
1291+
struct strbuf url = STRBUF_INIT;
1292+
struct strbuf path = STRBUF_INIT;
1293+
const char *p, *space;
1294+
1295+
if (!skip_prefix(buf->buf, "get ", &p))
1296+
die(_("http transport does not support %s"), buf->buf);
1297+
1298+
space = strchr(p, ' ');
1299+
1300+
if (!space)
1301+
die(_("protocol error: expected '<url> <path>', missing space"));
1302+
1303+
strbuf_add(&url, p, space - p);
1304+
strbuf_addstr(&path, space + 1);
1305+
1306+
if (http_get_file(url.buf, path.buf, NULL))
1307+
die(_("failed to download file at URL '%s'"), url.buf);
1308+
1309+
strbuf_release(&url);
1310+
strbuf_release(&path);
1311+
printf("\n");
1312+
fflush(stdout);
1313+
strbuf_reset(buf);
1314+
}
1315+
12891316
static int push_dav(int nr_spec, const char **specs)
12901317
{
12911318
struct child_process child = CHILD_PROCESS_INIT;
@@ -1564,9 +1591,14 @@ int cmd_main(int argc, const char **argv)
15641591
printf("unsupported\n");
15651592
fflush(stdout);
15661593

1594+
} else if (skip_prefix(buf.buf, "get ", &arg)) {
1595+
parse_get(&buf);
1596+
fflush(stdout);
1597+
15671598
} else if (!strcmp(buf.buf, "capabilities")) {
15681599
printf("stateless-connect\n");
15691600
printf("fetch\n");
1601+
printf("get\n");
15701602
printf("option\n");
15711603
printf("push\n");
15721604
printf("check-connectivity\n");

t/t5557-http-get.sh

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#!/bin/sh
2+
3+
test_description='test downloading a file by URL'
4+
5+
. ./test-lib.sh
6+
7+
. "$TEST_DIRECTORY"/lib-httpd.sh
8+
start_httpd
9+
10+
test_expect_success 'get by URL: 404' '
11+
url="$HTTPD_URL/none.txt" &&
12+
cat >input <<-EOF &&
13+
capabilities
14+
get $url file1
15+
EOF
16+
17+
test_must_fail git remote-http $url <input 2>err &&
18+
test_path_is_missing file1 &&
19+
grep "failed to download file at URL" err &&
20+
rm file1.temp
21+
'
22+
23+
test_expect_success 'get by URL: 200' '
24+
echo data >"$HTTPD_DOCUMENT_ROOT_PATH/exists.txt" &&
25+
26+
url="$HTTPD_URL/exists.txt" &&
27+
cat >input <<-EOF &&
28+
capabilities
29+
get $url file2
30+
31+
EOF
32+
33+
GIT_TRACE2_PERF=1 git remote-http $url <input &&
34+
test_cmp "$HTTPD_DOCUMENT_ROOT_PATH/exists.txt" file2
35+
'
36+
37+
test_done

0 commit comments

Comments
 (0)