Skip to content

Commit 472fbef

Browse files
jonathantanmygitster
authored andcommitted
http-fetch: support fetching packfiles by URL
Teach http-fetch the ability to download packfiles directly, given a URL, and to verify them. The http_pack_request suite of functions have been modified to support a NULL target. When target is NULL, the given URL is downloaded directly instead of being treated as the root of a repository. Signed-off-by: Jonathan Tan <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 4d3164c commit 472fbef

File tree

5 files changed

+135
-30
lines changed

5 files changed

+135
-30
lines changed

Documentation/git-http-fetch.txt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ git-http-fetch - Download from a remote Git repository via HTTP
99
SYNOPSIS
1010
--------
1111
[verse]
12-
'git http-fetch' [-c] [-t] [-a] [-d] [-v] [-w filename] [--recover] [--stdin] <commit> <url>
12+
'git http-fetch' [-c] [-t] [-a] [-d] [-v] [-w filename] [--recover] [--stdin | --packfile | <commit>] <url>
1313

1414
DESCRIPTION
1515
-----------
@@ -40,6 +40,12 @@ commit-id::
4040

4141
<commit-id>['\t'<filename-as-in--w>]
4242

43+
--packfile::
44+
Instead of a commit id on the command line (which is not expected in
45+
this case), 'git http-fetch' fetches the packfile directly at the given
46+
URL and uses index-pack to generate corresponding .idx and .keep files.
47+
The output of index-pack is printed to stdout.
48+
4349
--recover::
4450
Verify that everything reachable from target is fetched. Used after
4551
an earlier fetch is interrupted.

http-fetch.c

Lines changed: 51 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
#include "walker.h"
66

77
static const char http_fetch_usage[] = "git http-fetch "
8-
"[-c] [-t] [-a] [-v] [--recover] [-w ref] [--stdin] commit-id url";
8+
"[-c] [-t] [-a] [-v] [--recover] [-w ref] [--stdin | --packfile | commit-id] url";
99

1010
int cmd_main(int argc, const char **argv)
1111
{
@@ -19,6 +19,7 @@ int cmd_main(int argc, const char **argv)
1919
int rc = 0;
2020
int get_verbosely = 0;
2121
int get_recover = 0;
22+
int packfile = 0;
2223

2324
while (arg < argc && argv[arg][0] == '-') {
2425
if (argv[arg][1] == 't') {
@@ -35,43 +36,80 @@ int cmd_main(int argc, const char **argv)
3536
get_recover = 1;
3637
} else if (!strcmp(argv[arg], "--stdin")) {
3738
commits_on_stdin = 1;
39+
} else if (!strcmp(argv[arg], "--packfile")) {
40+
packfile = 1;
3841
}
3942
arg++;
4043
}
41-
if (argc != arg + 2 - commits_on_stdin)
44+
if (argc != arg + 2 - (commits_on_stdin || packfile))
4245
usage(http_fetch_usage);
4346
if (commits_on_stdin) {
4447
commits = walker_targets_stdin(&commit_id, &write_ref);
48+
} else if (packfile) {
49+
/* URL will be set later */
4550
} else {
4651
commit_id = (char **) &argv[arg++];
4752
commits = 1;
4853
}
4954

50-
if (argv[arg])
51-
str_end_url_with_slash(argv[arg], &url);
55+
if (packfile) {
56+
url = xstrdup(argv[arg]);
57+
} else {
58+
if (argv[arg])
59+
str_end_url_with_slash(argv[arg], &url);
60+
}
5261

5362
setup_git_directory();
5463

5564
git_config(git_default_config, NULL);
5665

5766
http_init(NULL, url, 0);
58-
walker = get_http_walker(url);
59-
walker->get_verbosely = get_verbosely;
60-
walker->get_recover = get_recover;
6167

62-
rc = walker_fetch(walker, commits, commit_id, write_ref, url);
68+
if (packfile) {
69+
struct http_pack_request *preq;
70+
struct slot_results results;
71+
int ret;
72+
73+
preq = new_http_pack_request(NULL, url);
74+
if (preq == NULL)
75+
die("couldn't create http pack request");
76+
preq->slot->results = &results;
77+
preq->generate_keep = 1;
78+
79+
if (start_active_slot(preq->slot)) {
80+
run_active_slot(preq->slot);
81+
if (results.curl_result != CURLE_OK) {
82+
die("Unable to get pack file %s\n%s", preq->url,
83+
curl_errorstr);
84+
}
85+
} else {
86+
die("Unable to start request");
87+
}
88+
89+
if ((ret = finish_http_pack_request(preq)))
90+
die("finish_http_pack_request gave result %d", ret);
91+
release_http_pack_request(preq);
92+
rc = 0;
93+
} else {
94+
walker = get_http_walker(url);
95+
walker->get_verbosely = get_verbosely;
96+
walker->get_recover = get_recover;
97+
98+
rc = walker_fetch(walker, commits, commit_id, write_ref, url);
6399

64-
if (commits_on_stdin)
65-
walker_targets_free(commits, commit_id, write_ref);
100+
if (commits_on_stdin)
101+
walker_targets_free(commits, commit_id, write_ref);
66102

67-
if (walker->corrupt_object_found) {
68-
fprintf(stderr,
103+
if (walker->corrupt_object_found) {
104+
fprintf(stderr,
69105
"Some loose object were found to be corrupt, but they might be just\n"
70106
"a false '404 Not Found' error message sent with incorrect HTTP\n"
71107
"status code. Suggest running 'git fsck'.\n");
108+
}
109+
110+
walker_free(walker);
72111
}
73112

74-
walker_free(walker);
75113
http_cleanup();
76114

77115
free(url);

http.c

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2208,30 +2208,40 @@ int finish_http_pack_request(struct http_pack_request *preq)
22082208
int tmpfile_fd;
22092209
int ret = 0;
22102210

2211-
close_pack_index(p);
2211+
if (p)
2212+
close_pack_index(p);
22122213

22132214
fclose(preq->packfile);
22142215
preq->packfile = NULL;
22152216

2216-
lst = preq->lst;
2217-
while (*lst != p)
2218-
lst = &((*lst)->next);
2219-
*lst = (*lst)->next;
2217+
if (p) {
2218+
lst = preq->lst;
2219+
while (*lst != p)
2220+
lst = &((*lst)->next);
2221+
*lst = (*lst)->next;
2222+
}
22202223

22212224
tmpfile_fd = xopen(preq->tmpfile.buf, O_RDONLY);
22222225

22232226
argv_array_push(&ip.args, "index-pack");
22242227
argv_array_push(&ip.args, "--stdin");
22252228
ip.git_cmd = 1;
22262229
ip.in = tmpfile_fd;
2227-
ip.no_stdout = 1;
2230+
if (preq->generate_keep) {
2231+
argv_array_pushf(&ip.args, "--keep=git %"PRIuMAX,
2232+
(uintmax_t)getpid());
2233+
ip.out = 0;
2234+
} else {
2235+
ip.no_stdout = 1;
2236+
}
22282237

22292238
if (run_command(&ip)) {
22302239
ret = -1;
22312240
goto cleanup;
22322241
}
22332242

2234-
install_packed_git(the_repository, p);
2243+
if (p)
2244+
install_packed_git(the_repository, p);
22352245
cleanup:
22362246
close(tmpfile_fd);
22372247
unlink(preq->tmpfile.buf);
@@ -2249,12 +2259,24 @@ struct http_pack_request *new_http_pack_request(
22492259
strbuf_init(&preq->tmpfile, 0);
22502260
preq->target = target;
22512261

2252-
end_url_with_slash(&buf, base_url);
2253-
strbuf_addf(&buf, "objects/pack/pack-%s.pack",
2254-
sha1_to_hex(target->sha1));
2255-
preq->url = strbuf_detach(&buf, NULL);
2262+
if (target) {
2263+
end_url_with_slash(&buf, base_url);
2264+
strbuf_addf(&buf, "objects/pack/pack-%s.pack",
2265+
sha1_to_hex(target->sha1));
2266+
preq->url = strbuf_detach(&buf, NULL);
2267+
} else {
2268+
preq->url = xstrdup(base_url);
2269+
}
2270+
2271+
if (target) {
2272+
strbuf_addf(&preq->tmpfile, "%s.temp",
2273+
sha1_pack_name(target->sha1));
2274+
} else {
2275+
strbuf_addf(&preq->tmpfile, "%s/pack/pack-", get_object_directory());
2276+
strbuf_addstr_urlencode(&preq->tmpfile, base_url, 1);
2277+
strbuf_addstr(&preq->tmpfile, ".temp");
2278+
}
22562279

2257-
strbuf_addf(&preq->tmpfile, "%s.temp", sha1_pack_name(target->sha1));
22582280
preq->packfile = fopen(preq->tmpfile.buf, "a");
22592281
if (!preq->packfile) {
22602282
error("Unable to open local file %s for pack",
@@ -2278,7 +2300,8 @@ struct http_pack_request *new_http_pack_request(
22782300
if (http_is_verbose)
22792301
fprintf(stderr,
22802302
"Resuming fetch of pack %s at byte %"PRIuMAX"\n",
2281-
sha1_to_hex(target->sha1), (uintmax_t)prev_posn);
2303+
target ? sha1_to_hex(target->sha1) : base_url,
2304+
(uintmax_t)prev_posn);
22822305
http_opt_request_remainder(preq->slot->curl, prev_posn);
22832306
}
22842307

http.h

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -210,12 +210,21 @@ struct http_pack_request {
210210
struct active_request_slot *slot;
211211

212212
/*
213-
* After calling new_http_pack_request(), point lst to the head of the
213+
* After calling new_http_pack_request(), if fetching a pack that
214+
* http_get_info_packs() told us about, point lst to the head of the
214215
* pack list that target is in. finish_http_pack_request() will remove
215216
* target from lst and call install_packed_git() on target.
216217
*/
217218
struct packed_git **lst;
218219

220+
/*
221+
* If this is true, finish_http_pack_request() will pass "--keep" to
222+
* index-pack, resulting in the creation of a keep file, and will not
223+
* suppress its stdout (that is, the "keep\t<hash>\n" line will be
224+
* printed to stdout).
225+
*/
226+
unsigned generate_keep : 1;
227+
219228
/*
220229
* State managed by functions in http.c.
221230
*/
@@ -224,8 +233,12 @@ struct http_pack_request {
224233
};
225234

226235
/*
227-
* target must be an element in a pack list obtained from
228-
* http_get_info_packs().
236+
* If fetching a pack that http_get_info_packs() told us about, set target to
237+
* an element in a pack list obtained from http_get_info_packs(). The actual
238+
* URL fetched will be base_url followed by a suffix with the hash of the pack.
239+
*
240+
* Otherwise, set target to NULL. The actual URL fetched will be base_url
241+
* itself.
229242
*/
230243
extern struct http_pack_request *new_http_pack_request(
231244
struct packed_git *target, const char *base_url);

t/t5550-http-fetch-dumb.sh

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,23 @@ test_expect_success 'fetch packed objects' '
199199
git clone $HTTPD_URL/dumb/repo_pack.git
200200
'
201201

202+
test_expect_success 'http-fetch --packfile' '
203+
git init packfileclient &&
204+
p=$(cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git && ls objects/pack/pack-*.pack) &&
205+
git -C packfileclient http-fetch --packfile "$HTTPD_URL"/dumb/repo_pack.git/$p >out &&
206+
207+
# Ensure that the expected files are generated
208+
grep "^keep.[0-9a-f]\{16,\}$" out &&
209+
cut -c6- out >packhash &&
210+
test -e "packfileclient/.git/objects/pack/pack-$(cat packhash).pack" &&
211+
test -e "packfileclient/.git/objects/pack/pack-$(cat packhash).idx" &&
212+
test -e "packfileclient/.git/objects/pack/pack-$(cat packhash).keep" &&
213+
214+
# Ensure that it has the HEAD of repo_pack, at least
215+
HASH=$(git -C "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git rev-parse HEAD) &&
216+
git -C packfileclient cat-file -e "$HASH"
217+
'
218+
202219
test_expect_success 'fetch notices corrupt pack' '
203220
cp -R "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git "$HTTPD_DOCUMENT_ROOT_PATH"/repo_bad1.git &&
204221
(cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_bad1.git &&
@@ -214,6 +231,14 @@ test_expect_success 'fetch notices corrupt pack' '
214231
)
215232
'
216233

234+
test_expect_success 'http-fetch --packfile with corrupt pack' '
235+
rm -rf packfileclient &&
236+
git init packfileclient &&
237+
p=$(cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_bad1.git && ls objects/pack/pack-*.pack) &&
238+
test_must_fail git -C packfileclient http-fetch --packfile \
239+
"$HTTPD_URL"/dumb/repo_bad1.git/$p
240+
'
241+
217242
test_expect_success 'fetch notices corrupt idx' '
218243
cp -R "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git "$HTTPD_DOCUMENT_ROOT_PATH"/repo_bad2.git &&
219244
(cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_bad2.git &&

0 commit comments

Comments
 (0)