@@ -293,6 +293,256 @@ test_expect_success 'http-fetch --packfile' '
293293 git -C packfileclient cat-file -e "$HASH"
294294'
295295
296+ test_expect_success ' http-fetch --packfile resumes a partial download' '
297+ git init packfileclient-resume &&
298+ p=$(cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git &&
299+ ls objects/pack/pack-*.pack) &&
300+ tmpfile="packfileclient-resume/.git/objects/pack/pack-$ARBITRARY.pack.temp" &&
301+ test_copy_bytes 64 <"$HTTPD_DOCUMENT_ROOT_PATH/repo_pack.git/$p" >"$tmpfile" &&
302+ GIT_TRACE_CURL="$TRASH_DIRECTORY/resume.trace" \
303+ git -C packfileclient-resume http-fetch --packfile="$ARBITRARY" \
304+ --index-pack-arg=index-pack --index-pack-arg=--stdin \
305+ --index-pack-arg=--keep \
306+ "$HTTPD_URL/dumb/repo_pack.git/$p" >out &&
307+ test_grep "Range: bytes=64-" resume.trace &&
308+ test_path_is_missing "$tmpfile" &&
309+ git -C packfileclient-resume cat-file -e "$HASH"
310+ '
311+
312+ test_expect_success ' http-fetch --packfile permits unlink while indexing' '
313+ git init packfileclient-unlink &&
314+ p=$(cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git &&
315+ ls objects/pack/pack-*.pack) &&
316+ tmpfile="packfileclient-unlink/.git/objects/pack/pack-$ARBITRARY.pack.temp" &&
317+ write_script git-unlink-index-pack <<-\EOF &&
318+ test -f "$GIT_TEST_PACK_TEMP" || exit 1
319+ rm "$GIT_TEST_PACK_TEMP" || exit 1
320+ exec git index-pack "$@"
321+ EOF
322+ test_when_finished "rm -f git-unlink-index-pack" &&
323+ PATH="$TRASH_DIRECTORY:$PATH" \
324+ GIT_TEST_PACK_TEMP="$TRASH_DIRECTORY/$tmpfile" \
325+ git -C packfileclient-unlink http-fetch --packfile="$ARBITRARY" \
326+ --index-pack-arg=unlink-index-pack \
327+ --index-pack-arg=--stdin --index-pack-arg=--keep \
328+ "$HTTPD_URL/dumb/repo_pack.git/$p" >out &&
329+ test_path_is_missing "$tmpfile" &&
330+ git -C packfileclient-unlink cat-file -e "$HASH"
331+ '
332+
333+ test_expect_success PIPE ' concurrent http-fetch --packfile accepts a complete partial' '
334+ git init packfileclient-concurrent &&
335+ p=$(cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git &&
336+ ls objects/pack/pack-*.pack) &&
337+ packhash=$(basename "$p" .pack) &&
338+ packhash=${packhash#pack-} &&
339+ tmpfile="packfileclient-concurrent/.git/objects/pack/pack-$packhash.pack.temp" &&
340+ test_copy_bytes 64 <"$HTTPD_DOCUMENT_ROOT_PATH/repo_pack.git/$p" >"$tmpfile" &&
341+ mkfifo first-ready first-continue &&
342+ exec 8<>first-ready &&
343+ exec 9<>first-continue &&
344+ write_script git-wait-index-pack <<-\EOF &&
345+ echo ready >"$GIT_TEST_WAIT_READY" &&
346+ read continue <"$GIT_TEST_WAIT_CONTINUE" &&
347+ exec git index-pack "$@"
348+ EOF
349+ {
350+ (
351+ if ! PATH="$TRASH_DIRECTORY:$PATH" \
352+ GIT_TEST_WAIT_READY="$TRASH_DIRECTORY/first-ready" \
353+ GIT_TEST_WAIT_CONTINUE="$TRASH_DIRECTORY/first-continue" \
354+ GIT_TRACE_CURL="$TRASH_DIRECTORY/first.trace" \
355+ git -C packfileclient-concurrent http-fetch --packfile="$packhash" \
356+ --index-pack-arg=wait-index-pack \
357+ --index-pack-arg=--stdin --index-pack-arg=--keep \
358+ "$HTTPD_URL/dumb/repo_pack.git/$p" >first.out
359+ then
360+ echo failed >"$TRASH_DIRECTORY/first-ready" &&
361+ exit 1
362+ fi
363+ ) &
364+ first_pid=$!
365+ } &&
366+ test_when_finished "
367+ echo continue >&9
368+ kill $first_pid 2>/dev/null || :
369+ wait $first_pid 2>/dev/null || :
370+ exec 8>&-
371+ exec 9>&-
372+ rm -f first-ready first-continue git-wait-index-pack
373+ " &&
374+ read ready <&8 &&
375+ test "$ready" = ready &&
376+ GIT_TRACE_CURL="$TRASH_DIRECTORY/second.trace" \
377+ git -C packfileclient-concurrent http-fetch --packfile="$packhash" \
378+ --index-pack-arg=index-pack \
379+ --index-pack-arg=--stdin --index-pack-arg=--keep \
380+ "$HTTPD_URL/dumb/repo_pack.git/$p" >second.out &&
381+ echo continue >&9 &&
382+ wait "$first_pid" &&
383+ printf "pack\t%s\n" "$packhash" >expect &&
384+ test_cmp expect first.out &&
385+ printf "keep\t%s\n" "$packhash" >expect &&
386+ test_cmp expect second.out &&
387+ test_grep "Range: bytes=64-" first.trace &&
388+ test_grep "Range: bytes=[0-9]*-" second.trace &&
389+ test_grep "416 Requested Range Not Satisfiable" second.trace &&
390+ test_path_is_missing "$tmpfile" &&
391+ git -C packfileclient-concurrent cat-file -e "$HASH"
392+ '
393+
394+ test_expect_success PERL,PIPE ' concurrent http-fetch --packfile cannot corrupt an overlapping download' '
395+ git init packfileclient-overlap &&
396+ blob=$(test-tool genrandom pack-overlap 2m |
397+ git -C "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git \
398+ hash-object -w --stdin) &&
399+ packhash=$(printf "%s\n" "$blob" |
400+ git -C "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git \
401+ pack-objects "$TRASH_DIRECTORY/overlap-pack") &&
402+ pack="$TRASH_DIRECTORY/overlap-pack-$packhash.pack" &&
403+ tmpfile="packfileclient-overlap/.git/objects/pack/pack-$packhash.pack.temp" &&
404+ mkfifo server-ready first-ready &&
405+ exec 7<>server-ready &&
406+ exec 8<>first-ready &&
407+ write_script slow-pack-server "$PERL_PATH" <<-\EOF &&
408+ use strict;
409+ use warnings;
410+ use IO::Socket::INET;
411+
412+ my ($packfile, $server_ready, $first_ready) = @ARGV;
413+ my $completed = 0;
414+ END {
415+ if (!$completed) {
416+ signal_ready($server_ready, "failed");
417+ signal_ready($first_ready, "failed");
418+ }
419+ }
420+
421+ $SIG{ALRM} = sub { die "timed out serving concurrent pack requests\n" };
422+ alarm 60;
423+
424+ open(my $in, "<:raw", $packfile) or die "open $packfile: $!";
425+ my $pack = do { local $/; <$in> };
426+ close($in) or die "close $packfile: $!";
427+ my $server = IO::Socket::INET->new(LocalAddr => "127.0.0.1",
428+ LocalPort => 0, Proto => "tcp", Listen => 2, ReuseAddr => 1)
429+ or die "listen: $!";
430+
431+ sub signal_ready {
432+ my ($file, $value) = @_;
433+ open(my $out, ">", $file) or die "open $file: $!";
434+ print $out "$value\n" or die "write $file: $!";
435+ close($out) or die "close $file: $!";
436+ }
437+
438+ sub write_all {
439+ my ($out, $data) = @_;
440+ my $offset = 0;
441+ while ($offset < length($data)) {
442+ my $written = syswrite($out, $data,
443+ length($data) - $offset, $offset);
444+ defined($written) && $written or die "write response: $!";
445+ $offset += $written;
446+ }
447+ }
448+
449+ sub start_response {
450+ my $out = $server->accept() or die "accept: $!";
451+ <$out> or die "read request: $!";
452+ my $start = 0;
453+ while (<$out>) {
454+ last if /^\r?\n$/;
455+ $start = $1 if /^Range: bytes=(\d+)-/i;
456+ }
457+ $start < length($pack) or die "invalid range $start";
458+ my $length = length($pack) - $start;
459+ my $middle = int($length / 2);
460+ my $status = $start ? "206 Partial Content" : "200 OK";
461+ my $headers = "HTTP/1.1 $status\r\n" .
462+ "Content-Length: $length\r\n" .
463+ ($start ? "Content-Range: bytes $start-" .
464+ (length($pack) - 1) . "/" . length($pack) . "\r\n" : "") .
465+ "Connection: close\r\n\r\n";
466+ write_all($out, $headers);
467+ write_all($out, substr($pack, $start, $middle));
468+ return ($out, $start + $middle);
469+ }
470+
471+ signal_ready($server_ready, $server->sockport());
472+ my ($first, $first_pos) = start_response();
473+ signal_ready($first_ready, "ready");
474+ my ($second, $second_pos) = start_response();
475+ write_all($first, substr($pack, $first_pos));
476+ write_all($second, substr($pack, $second_pos));
477+ close($first) or die "close first response: $!";
478+ close($second) or die "close second response: $!";
479+ $completed = 1;
480+ alarm 0;
481+ EOF
482+ {
483+ "$TRASH_DIRECTORY/slow-pack-server" "$pack" \
484+ "$TRASH_DIRECTORY/server-ready" \
485+ "$TRASH_DIRECTORY/first-ready" >server.log 2>&1 &
486+ server_pid=$!
487+ } &&
488+ test_when_finished "
489+ kill $server_pid 2>/dev/null || :
490+ wait $server_pid 2>/dev/null || :
491+ exec 7>&-
492+ exec 8>&-
493+ rm -f server-ready first-ready slow-pack-server
494+ " &&
495+ read port <&7 &&
496+ url="http://127.0.0.1:$port/pack" &&
497+ {
498+ (
499+ if ! GIT_TRACE_CURL="$TRASH_DIRECTORY/overlap-first.trace" \
500+ GIT_TRACE_CURL_NO_DATA=1 \
501+ git -C packfileclient-overlap http-fetch --packfile="$packhash" \
502+ --index-pack-arg=index-pack \
503+ --index-pack-arg=--stdin --index-pack-arg=--keep \
504+ "$url" >first.out
505+ then
506+ echo failed >"$TRASH_DIRECTORY/first-ready" &&
507+ exit 1
508+ fi
509+ ) &
510+ first_pid=$!
511+ } &&
512+ test_when_finished "
513+ kill $first_pid 2>/dev/null || :
514+ wait $first_pid 2>/dev/null || :
515+ " &&
516+ read ready <&8 &&
517+ test "$ready" = ready &&
518+ test_path_is_file "$tmpfile" &&
519+ test -s "$tmpfile" &&
520+ {
521+ GIT_TRACE_CURL="$TRASH_DIRECTORY/overlap-second.trace" \
522+ GIT_TRACE_CURL_NO_DATA=1 \
523+ git -C packfileclient-overlap http-fetch --packfile="$packhash" \
524+ --index-pack-arg=index-pack \
525+ --index-pack-arg=--stdin --index-pack-arg=--keep \
526+ "$url" >second.out &
527+ second_pid=$!
528+ } &&
529+ test_when_finished "
530+ kill $second_pid 2>/dev/null || :
531+ wait $second_pid 2>/dev/null || :
532+ " &&
533+ wait "$second_pid" &&
534+ wait "$first_pid" &&
535+ wait "$server_pid" &&
536+ test_grep "HTTP/[0-9.]* 200" overlap-first.trace &&
537+ test_grep "Range: bytes=[1-9][0-9]*-" overlap-second.trace &&
538+ test_grep "HTTP/[0-9.]* 206" overlap-second.trace &&
539+ printf "keep\t%s\npack\t%s\n" "$packhash" "$packhash" | sort >expect &&
540+ sort first.out second.out >actual &&
541+ test_cmp expect actual &&
542+ test_path_is_missing "$tmpfile" &&
543+ git -C packfileclient-overlap cat-file -e "$blob"
544+ '
545+
296546test_expect_success ' fetch notices corrupt pack' '
297547 cp -R "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git "$HTTPD_DOCUMENT_ROOT_PATH"/repo_bad1.git &&
298548 (cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_bad1.git &&
0 commit comments