293
git -C packfileclient cat-file -e "$HASH"
294
'
295
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 "HTTP/[0-9.]* 416" 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
+ open(my $in, "<:raw", $packfile) or die "open $packfile: $!";
414
+ my $pack = do { local $/; <$in> };
415
+ close($in) or die "close $packfile: $!";
416
+ my $server = IO::Socket::INET->new(LocalAddr => "127.0.0.1",
417
+ LocalPort => 0, Proto => "tcp", Listen => 2, ReuseAddr => 1)
418
+ or die "listen: $!";
419
+
420
+ sub signal_ready {
421
+ my ($file, $value) = @_;
422
+ open(my $out, ">", $file) or die "open $file: $!";
423
+ print $out "$value\n" or die "write $file: $!";
424
+ close($out) or die "close $file: $!";
425
+ }
426
+
427
+ sub write_all {
428
+ my ($out, $data) = @_;
429
+ my $offset = 0;
430
+ while ($offset < length($data)) {
431
+ my $written = syswrite($out, $data,
432
+ length($data) - $offset, $offset);
433
+ defined($written) && $written or die "write response: $!";
434
+ $offset += $written;
435
+ }
436
+ }
437
+
438
+ sub start_response {
439
+ my $out = $server->accept() or die "accept: $!";
440
+ <$out> or die "read request: $!";
441
+ my $start = 0;
442
+ while (<$out>) {
443
+ last if /^\r?\n$/;
444
+ $start = $1 if /^Range: bytes=(\d+)-/i;
445
+ }
446
+ $start < length($pack) or die "invalid range $start";
447
+ my $length = length($pack) - $start;
448
+ my $middle = int($length / 2);
449
+ my $status = $start ? "206 Partial Content" : "200 OK";
450
+ my $headers = "HTTP/1.1 $status\r\n" .
451
+ "Content-Length: $length\r\n" .
452
+ ($start ? "Content-Range: bytes $start-" .
453
+ (length($pack) - 1) . "/" . length($pack) . "\r\n" : "") .
454
+ "Connection: close\r\n\r\n";
455
+ write_all($out, $headers);
456
+ write_all($out, substr($pack, $start, $middle));
457
+ return ($out, $start + $middle);
458
+ }
459
+
460
+ signal_ready($server_ready, $server->sockport());
461
+ my ($first, $first_pos) = start_response();
462
+ signal_ready($first_ready, "ready");
463
+ my ($second, $second_pos) = start_response();
464
+ write_all($first, substr($pack, $first_pos));
465
+ write_all($second, substr($pack, $second_pos));
466
+ close($first) or die "close first response: $!";
467
+ close($second) or die "close second response: $!";
468
+ EOF
469
+ {
470
+ (
471
+ if ! "$TRASH_DIRECTORY/slow-pack-server" "$pack" \
472
+ "$TRASH_DIRECTORY/server-ready" \
473
+ "$TRASH_DIRECTORY/first-ready"
474
+ then
475
+ echo failed >"$TRASH_DIRECTORY/server-ready" &&
476
+ echo failed >"$TRASH_DIRECTORY/first-ready" &&
477
+ exit 1
478
+ fi
479
+ ) >server.log 2>&1 &
480
+ server_pid=$!
481
+ } &&
482
+ test_when_finished "
483
+ kill $server_pid 2>/dev/null || :
484
+ wait $server_pid 2>/dev/null || :
485
+ exec 7>&-
486
+ exec 8>&-
487
+ rm -f server-ready first-ready slow-pack-server
488
+ " &&
489
+ read port <&7 &&
490
+ url="http://127.0.0.1:$port/pack" &&
491
+ {
492
+ (
493
+ if ! GIT_TRACE_CURL="$TRASH_DIRECTORY/overlap-first.trace" \
494
+ GIT_TRACE_CURL_NO_DATA=1 \
495
+ git -C packfileclient-overlap http-fetch --packfile="$packhash" \
496
+ --index-pack-arg=index-pack \
497
+ --index-pack-arg=--stdin --index-pack-arg=--keep \
498
+ "$url" >first.out
499
+ then
500
+ echo failed >"$TRASH_DIRECTORY/first-ready" &&
501
+ exit 1
502
+ fi
503
+ ) &
504
+ first_pid=$!
505
+ } &&
506
+ test_when_finished "
507
+ kill $first_pid 2>/dev/null || :
508
+ wait $first_pid 2>/dev/null || :
509
+ " &&
510
+ read ready <&8 &&
511
+ test "$ready" = ready &&
512
+ test_path_is_file "$tmpfile" &&
513
+ test -s "$tmpfile" &&
514
+ {
515
+ GIT_TRACE_CURL="$TRASH_DIRECTORY/overlap-second.trace" \
516
+ GIT_TRACE_CURL_NO_DATA=1 \
517
+ git -C packfileclient-overlap http-fetch --packfile="$packhash" \
518
+ --index-pack-arg=index-pack \
519
+ --index-pack-arg=--stdin --index-pack-arg=--keep \
520
+ "$url" >second.out &
521
+ second_pid=$!
522
+ } &&
523
+ test_when_finished "
524
+ kill $second_pid 2>/dev/null || :
525
+ wait $second_pid 2>/dev/null || :
526
+ " &&
527
+ wait "$server_pid" &&
528
+ wait "$first_pid" &&
529
+ wait "$second_pid" &&
530
+ test_grep "HTTP/[0-9.]* 200" overlap-first.trace &&
531
+ test_grep "Range: bytes=[1-9][0-9]*-" overlap-second.trace &&
532
+ test_grep "HTTP/[0-9.]* 206" overlap-second.trace &&
533
+ printf "keep\t%s\npack\t%s\n" "$packhash" "$packhash" | sort >expect &&
534
+ sort first.out second.out >actual &&
535
+ test_cmp expect actual &&
536
+ test_path_is_missing "$tmpfile" &&
537
+ git -C packfileclient-overlap cat-file -e "$blob"
538
+'
539
+
540
test_expect_success 'fetch notices corrupt pack' '
541
cp -R "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git "$HTTPD_DOCUMENT_ROOT_PATH"/repo_bad1.git &&
542
(cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_bad1.git &&