Skip to content

Commit 05d0dd4

Browse files
committed
Merge branch 'tn/packfile-uri-concurrency' into seen
Concurrent downloads of packfiles via packfile URIs and dumb HTTP have been made safer by avoiding concurrent appends to the staging file. Opening the file in read-write mode and maintaining separate file offsets prevents corruption while preserving resumability. The 'fetch-pack' command has also been updated to tolerate pre-existing '.keep' files. * tn/packfile-uri-concurrency: fetch-pack: accept "pack" output for packfile URIs http: avoid concurrent appends to partial packs http-fetch: correct --index-pack-arg documentation
2 parents 728e180 + 1c26a0b commit 05d0dd4

8 files changed

Lines changed: 344 additions & 46 deletions

File tree

Documentation/git-http-fetch.adoc

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,14 @@ commit-id::
4848
line (which is not expected in
4949
this case), 'git http-fetch' fetches the packfile directly at the given
5050
URL and uses index-pack to generate corresponding .idx and .keep files.
51-
The hash is used to determine the name of the temporary file and is
52-
arbitrary. The output of index-pack is printed to stdout. Requires
53-
--index-pack-args.
51+
The hash is used to determine the name of the temporary file. It need
52+
not be the pack hash, but it must uniquely identify the pack contents
53+
for resumption. The output of index-pack is printed to stdout. Requires
54+
one or more --index-pack-arg options.
5455

55-
--index-pack-args=<args>::
56-
For internal use only. The command to run on the contents of the
57-
downloaded pack. Arguments are URL-encoded separated by spaces.
56+
--index-pack-arg=<arg>::
57+
For internal use only. An argument to the command run on the contents
58+
of the downloaded pack. This option can be specified multiple times.
5859

5960
--recover::
6061
Verify that everything reachable from target is fetched. Used after

fetch-pack.c

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1854,9 +1854,10 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
18541854
}
18551855

18561856
for (i = 0; i < packfile_uris.nr; i++) {
1857+
bool created_keep;
18571858
int j;
18581859
struct child_process cmd = CHILD_PROCESS_INIT;
1859-
char packname[GIT_MAX_HEXSZ + 1];
1860+
char packhash[GIT_MAX_HEXSZ + 1];
18601861
const char *uri = packfile_uris.items[i].string +
18611862
the_hash_algo->hexsz + 1;
18621863

@@ -1874,16 +1875,17 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
18741875
if (start_command(&cmd))
18751876
die("fetch-pack: unable to spawn http-fetch");
18761877

1877-
if (read_in_full(cmd.out, packname, 5) < 0 ||
1878-
memcmp(packname, "keep\t", 5))
1879-
die("fetch-pack: expected keep then TAB at start of http-fetch output");
1878+
if (read_in_full(cmd.out, packhash, 5) != 5 ||
1879+
(memcmp(packhash, "keep\t", 5) &&
1880+
memcmp(packhash, "pack\t", 5)))
1881+
die("fetch-pack: expected pack or keep then TAB at start of http-fetch output");
1882+
created_keep = !memcmp(packhash, "keep\t", 5);
18801883

1881-
if (read_in_full(cmd.out, packname,
1882-
the_hash_algo->hexsz + 1) < 0 ||
1883-
packname[the_hash_algo->hexsz] != '\n')
1884-
die("fetch-pack: expected hash then LF at end of http-fetch output");
1885-
1886-
packname[the_hash_algo->hexsz] = '\0';
1884+
if (read_in_full(cmd.out, packhash,
1885+
the_hash_algo->hexsz + 1) != the_hash_algo->hexsz + 1 ||
1886+
packhash[the_hash_algo->hexsz] != '\n')
1887+
die("fetch-pack: expected hash then LF in http-fetch output");
1888+
packhash[the_hash_algo->hexsz] = '\0';
18871889

18881890
parse_gitmodules_oids(cmd.out, &fsck_options.gitmodules_found);
18891891

@@ -1892,16 +1894,17 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
18921894
if (finish_command(&cmd))
18931895
die("fetch-pack: unable to finish http-fetch");
18941896

1895-
if (memcmp(packfile_uris.items[i].string, packname,
1897+
if (memcmp(packfile_uris.items[i].string, packhash,
18961898
the_hash_algo->hexsz))
18971899
die("fetch-pack: pack downloaded from %s does not match expected hash %.*s",
18981900
uri, (int) the_hash_algo->hexsz,
18991901
packfile_uris.items[i].string);
19001902

1901-
string_list_append_nodup(pack_lockfiles,
1902-
xstrfmt("%s/pack/pack-%s.keep",
1903-
repo_get_object_directory(the_repository),
1904-
packname));
1903+
if (created_keep)
1904+
string_list_append_nodup(pack_lockfiles,
1905+
xstrfmt("%s/pack/pack-%s.keep",
1906+
repo_get_object_directory(the_repository),
1907+
packhash));
19051908
}
19061909
string_list_clear(&packfile_uris, 0);
19071910
strvec_clear(&index_pack_args);

http-fetch.c

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@ static void fetch_single_packfile(struct object_id *packfile_hash,
7070

7171
if (start_active_slot(preq->slot)) {
7272
run_active_slot(preq->slot);
73-
if (results.curl_result != CURLE_OK) {
73+
if (results.curl_result != CURLE_OK &&
74+
results.http_code != 416) {
7475
struct url_info url;
7576
char *nurl = url_normalize(preq->url, &url);
7677
if (!nurl || !git_env_bool("GIT_TRACE_REDACT", 1)) {
@@ -155,7 +156,7 @@ int cmd_main(int argc, const char **argv)
155156

156157
if (packfile) {
157158
if (!index_pack_args.nr)
158-
die(_("the option '%s' requires '%s'"), "--packfile", "--index-pack-args");
159+
die(_("the option '%s' requires '%s'"), "--packfile", "--index-pack-arg");
159160

160161
fetch_single_packfile(&packfile_hash, argv[arg],
161162
index_pack_args.v);
@@ -164,7 +165,7 @@ int cmd_main(int argc, const char **argv)
164165
}
165166

166167
if (index_pack_args.nr)
167-
die(_("the option '%s' requires '%s'"), "--index-pack-args", "--packfile");
168+
die(_("the option '%s' requires '%s'"), "--index-pack-arg", "--packfile");
168169

169170
if (commits_on_stdin) {
170171
commits = walker_targets_stdin(&commit_id, &write_ref);

http-push.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -595,7 +595,8 @@ static void finish_request(struct transfer_request *request)
595595

596596
} else if (request->state == RUN_FETCH_PACKED) {
597597
int fail = 1;
598-
if (request->curl_result != CURLE_OK) {
598+
if (request->curl_result != CURLE_OK &&
599+
request->http_code != 416) {
599600
fprintf(stderr, "Unable to get pack file %s\n%s",
600601
request->url, curl_errorstr);
601602
} else {

http-walker.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -451,7 +451,8 @@ static int http_fetch_pack(struct walker *walker, struct alt_base *repo,
451451

452452
if (start_active_slot(preq->slot)) {
453453
run_active_slot(preq->slot);
454-
if (results.curl_result != CURLE_OK) {
454+
if (results.curl_result != CURLE_OK &&
455+
results.http_code != 416) {
455456
error("Unable to get pack file %s\n%s", preq->url,
456457
curl_errorstr);
457458
goto abort;

http.c

Lines changed: 36 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2690,10 +2690,13 @@ int finish_http_pack_request(struct http_pack_request *preq)
26902690
int tmpfile_fd;
26912691
int ret = 0;
26922692

2693+
/* Another downloader may unlink the staging path while we index it. */
2694+
tmpfile_fd = xdup(fileno(preq->packfile));
26932695
fclose(preq->packfile);
26942696
preq->packfile = NULL;
2695-
2696-
tmpfile_fd = xopen(preq->tmpfile.buf, O_RDONLY);
2697+
if (lseek(tmpfile_fd, 0, SEEK_SET) < 0)
2698+
die_errno("unable to seek local file %s for pack",
2699+
preq->tmpfile.buf);
26972700

26982701
ip.git_cmd = 1;
26992702
ip.in = tmpfile_fd;
@@ -2706,13 +2709,8 @@ int finish_http_pack_request(struct http_pack_request *preq)
27062709
else
27072710
ip.no_stdout = 1;
27082711

2709-
if (run_command(&ip)) {
2712+
if (run_command(&ip))
27102713
ret = -1;
2711-
goto cleanup;
2712-
}
2713-
2714-
cleanup:
2715-
close(tmpfile_fd);
27162714
unlink(preq->tmpfile.buf);
27172715
return ret;
27182716
}
@@ -2740,22 +2738,45 @@ struct http_pack_request *new_http_pack_request(
27402738
struct http_pack_request *new_direct_http_pack_request(
27412739
const unsigned char *packed_git_hash, char *url)
27422740
{
2743-
off_t prev_posn = 0;
2741+
off_t prev_posn;
27442742
struct http_pack_request *preq;
2743+
int fd;
27452744

27462745
CALLOC_ARRAY(preq, 1);
27472746
strbuf_init(&preq->tmpfile, 0);
2748-
27492747
preq->url = url;
27502748

27512749
odb_pack_name(the_repository, &preq->tmpfile, packed_git_hash, "pack");
27522750
strbuf_addstr(&preq->tmpfile, ".temp");
2753-
preq->packfile = fopen(preq->tmpfile.buf, "a");
2754-
if (!preq->packfile) {
2755-
error("Unable to open local file %s for pack",
2756-
preq->tmpfile.buf);
2751+
/*
2752+
* MinGW's non-append O_RDWR open grants FILE_SHARE_DELETE only for an
2753+
* existing file; reopen a newly created file so others may unlink it.
2754+
*/
2755+
for (;;) {
2756+
fd = open(preq->tmpfile.buf, O_RDWR);
2757+
if (fd >= 0 || errno != ENOENT)
2758+
break;
2759+
fd = open(preq->tmpfile.buf, O_RDWR | O_CREAT | O_EXCL, 0666);
2760+
if (fd >= 0) {
2761+
close(fd);
2762+
continue;
2763+
}
2764+
if (errno != EEXIST)
2765+
break;
2766+
}
2767+
if (fd < 0) {
2768+
error_errno("unable to open local file %s for pack",
2769+
preq->tmpfile.buf);
27572770
goto abort;
27582771
}
2772+
prev_posn = lseek(fd, 0, SEEK_END);
2773+
if (prev_posn < 0) {
2774+
error_errno("unable to seek local file %s for pack",
2775+
preq->tmpfile.buf);
2776+
close(fd);
2777+
goto abort;
2778+
}
2779+
preq->packfile = xfdopen(fd, "w");
27592780

27602781
preq->slot = get_active_slot();
27612782
preq->headers = object_request_headers();
@@ -2764,12 +2785,7 @@ struct http_pack_request *new_direct_http_pack_request(
27642785
curl_easy_setopt(preq->slot->curl, CURLOPT_URL, preq->url);
27652786
curl_easy_setopt(preq->slot->curl, CURLOPT_HTTPHEADER, preq->headers);
27662787

2767-
/*
2768-
* If there is data present from a previous transfer attempt,
2769-
* resume where it left off
2770-
*/
2771-
prev_posn = ftello(preq->packfile);
2772-
if (prev_posn>0) {
2788+
if (prev_posn > 0) {
27732789
if (http_is_verbose)
27742790
fprintf(stderr,
27752791
"Resuming fetch of pack %s at byte %"PRIuMAX"\n",

0 commit comments

Comments
 (0)