bulk-checkin: replace fast-import based implementation
[gitweb.git] / t / t9300-fast-import.sh
index 385e78c22c0e00b9e81117a5ed7378298460c47b..438aaf6b14b8ae0cb35739d6006eaf646876107c 100755 (executable)
@@ -7,6 +7,23 @@ test_description='test git fast-import utility'
 . ./test-lib.sh
 . "$TEST_DIRECTORY"/diff-lib.sh ;# test-lib chdir's into trash
 
+# Print $1 bytes from stdin to stdout.
+#
+# This could be written as "head -c $1", but IRIX "head" does not
+# support the -c option.
+head_c () {
+       perl -e '
+               my $len = $ARGV[1];
+               while ($len > 0) {
+                       my $s;
+                       my $nread = sysread(STDIN, $s, $len);
+                       die "cannot read: $!" unless defined($nread);
+                       print $s;
+                       $len -= $nread;
+               }
+       ' - "$1"
+}
+
 file2_data='file2
 second line of EOF'
 
@@ -23,11 +40,26 @@ file5_data='an inline file.
 file6_data='#!/bin/sh
 echo "$@"'
 
+>empty
+
+test_expect_success 'setup: have pipes?' '
+       rm -f frob &&
+       if mkfifo frob
+       then
+               test_set_prereq PIPE
+       fi
+'
+
 ###
 ### series A
 ###
 
 test_tick
+
+test_expect_success 'empty stream succeeds' '
+       git fast-import </dev/null
+'
+
 cat >input <<INPUT_END
 blob
 mark :2
@@ -62,6 +94,12 @@ data <<EOF
 An annotated tag without a tagger
 EOF
 
+tag series-A-blob
+from :3
+data <<EOF
+An annotated tag that annotates a blob.
+EOF
+
 INPUT_END
 test_expect_success \
     'A: create pack from stdin' \
@@ -119,6 +157,18 @@ test_expect_success 'A: verify tag/series-A' '
        test_cmp expect actual
 '
 
+cat >expect <<EOF
+object $(git rev-parse refs/heads/master:file3)
+type blob
+tag series-A-blob
+
+An annotated tag that annotates a blob.
+EOF
+test_expect_success 'A: verify tag/series-A-blob' '
+       git cat-file tag tags/series-A-blob >actual &&
+       test_cmp expect actual
+'
+
 cat >expect <<EOF
 :2 `git rev-parse --verify master:file2`
 :3 `git rev-parse --verify master:file3`
@@ -137,6 +187,55 @@ test_expect_success \
                </dev/null &&
        test_cmp expect marks.new'
 
+test_tick
+new_blob=$(echo testing | git hash-object --stdin)
+cat >input <<INPUT_END
+tag series-A-blob-2
+from $(git rev-parse refs/heads/master:file3)
+data <<EOF
+Tag blob by sha1.
+EOF
+
+blob
+mark :6
+data <<EOF
+testing
+EOF
+
+commit refs/heads/new_blob
+committer  <> 0 +0000
+data 0
+M 644 :6 new_blob
+#pretend we got sha1 from fast-import
+ls "new_blob"
+
+tag series-A-blob-3
+from $new_blob
+data <<EOF
+Tag new_blob.
+EOF
+INPUT_END
+
+cat >expect <<EOF
+object $(git rev-parse refs/heads/master:file3)
+type blob
+tag series-A-blob-2
+
+Tag blob by sha1.
+object $new_blob
+type blob
+tag series-A-blob-3
+
+Tag new_blob.
+EOF
+
+test_expect_success \
+       'A: tag blob by sha1' \
+       'git fast-import <input &&
+       git cat-file tag tags/series-A-blob-2 >actual &&
+       git cat-file tag tags/series-A-blob-3 >>actual &&
+       test_cmp expect actual'
+
 test_tick
 cat >input <<INPUT_END
 commit refs/heads/verify--import-marks
@@ -292,6 +391,105 @@ test_expect_success \
         test `git rev-parse master` = `git rev-parse TEMP_TAG^`'
 rm -f .git/TEMP_TAG
 
+git gc 2>/dev/null >/dev/null
+git prune 2>/dev/null >/dev/null
+
+cat >input <<INPUT_END
+commit refs/heads/empty-committer-1
+committer  <> $GIT_COMMITTER_DATE
+data <<COMMIT
+empty commit
+COMMIT
+INPUT_END
+test_expect_success 'B: accept empty committer' '
+       git fast-import <input &&
+       out=$(git fsck) &&
+       echo "$out" &&
+       test -z "$out"
+'
+git update-ref -d refs/heads/empty-committer-1 || true
+
+git gc 2>/dev/null >/dev/null
+git prune 2>/dev/null >/dev/null
+
+cat >input <<INPUT_END
+commit refs/heads/empty-committer-2
+committer <a@b.com> $GIT_COMMITTER_DATE
+data <<COMMIT
+empty commit
+COMMIT
+INPUT_END
+test_expect_success 'B: accept and fixup committer with no name' '
+       git fast-import <input &&
+       out=$(git fsck) &&
+       echo "$out" &&
+       test -z "$out"
+'
+git update-ref -d refs/heads/empty-committer-2 || true
+
+git gc 2>/dev/null >/dev/null
+git prune 2>/dev/null >/dev/null
+
+cat >input <<INPUT_END
+commit refs/heads/invalid-committer
+committer Name email> $GIT_COMMITTER_DATE
+data <<COMMIT
+empty commit
+COMMIT
+INPUT_END
+test_expect_success 'B: fail on invalid committer (1)' '
+       test_must_fail git fast-import <input
+'
+git update-ref -d refs/heads/invalid-committer || true
+
+cat >input <<INPUT_END
+commit refs/heads/invalid-committer
+committer Name <e<mail> $GIT_COMMITTER_DATE
+data <<COMMIT
+empty commit
+COMMIT
+INPUT_END
+test_expect_success 'B: fail on invalid committer (2)' '
+       test_must_fail git fast-import <input
+'
+git update-ref -d refs/heads/invalid-committer || true
+
+cat >input <<INPUT_END
+commit refs/heads/invalid-committer
+committer Name <email>> $GIT_COMMITTER_DATE
+data <<COMMIT
+empty commit
+COMMIT
+INPUT_END
+test_expect_success 'B: fail on invalid committer (3)' '
+       test_must_fail git fast-import <input
+'
+git update-ref -d refs/heads/invalid-committer || true
+
+cat >input <<INPUT_END
+commit refs/heads/invalid-committer
+committer Name <email $GIT_COMMITTER_DATE
+data <<COMMIT
+empty commit
+COMMIT
+INPUT_END
+test_expect_success 'B: fail on invalid committer (4)' '
+       test_must_fail git fast-import <input
+'
+git update-ref -d refs/heads/invalid-committer || true
+
+cat >input <<INPUT_END
+commit refs/heads/invalid-committer
+committer Name<email> $GIT_COMMITTER_DATE
+data <<COMMIT
+empty commit
+COMMIT
+INPUT_END
+test_expect_success 'B: fail on invalid committer (5)' '
+       test_must_fail git fast-import <input
+'
+git update-ref -d refs/heads/invalid-committer || true
+
 ###
 ### series C
 ###
@@ -321,7 +519,7 @@ test_expect_success \
        'for p in .git/objects/pack/*.pack;do git verify-pack $p||exit;done'
 test_expect_success \
        'C: validate reuse existing blob' \
-       'test $newf = `git rev-parse --verify branch:file2/newf`
+       'test $newf = `git rev-parse --verify branch:file2/newf` &&
         test $oldf = `git rev-parse --verify branch:file2/oldf`'
 
 cat >expect <<EOF
@@ -622,6 +820,18 @@ test_expect_success \
        'test 1 = `git rev-list J | wc -l` &&
         test 0 = `git ls-tree J | wc -l`'
 
+cat >input <<INPUT_END
+reset refs/heads/J2
+
+tag wrong_tag
+from refs/heads/J2
+data <<EOF
+Tag branch that was reset.
+EOF
+INPUT_END
+test_expect_success \
+       'J: tag must fail on empty branch' \
+       'test_must_fail git fast-import <input'
 ###
 ### series K
 ###
@@ -702,6 +912,47 @@ test_expect_success \
         git diff-tree --abbrev --raw L^ L >output &&
         test_cmp expect output'
 
+cat >input <<INPUT_END
+blob
+mark :1
+data <<EOF
+the data
+EOF
+
+commit refs/heads/L2
+committer C O Mitter <committer@example.com> 1112912473 -0700
+data <<COMMIT
+init L2
+COMMIT
+M 644 :1 a/b/c
+M 644 :1 a/b/d
+M 644 :1 a/e/f
+
+commit refs/heads/L2
+committer C O Mitter <committer@example.com> 1112912473 -0700
+data <<COMMIT
+update L2
+COMMIT
+C a g
+C a/e g/b
+M 644 :1 g/b/h
+INPUT_END
+
+cat <<EOF >expect
+g/b/f
+g/b/h
+EOF
+
+test_expect_success \
+    'L: nested tree copy does not corrupt deltas' \
+       'git fast-import <input &&
+       git ls-tree L2 g/b/ >tmp &&
+       cat tmp | cut -f 2 >actual &&
+       test_cmp expect actual &&
+       git fsck `git rev-parse L2`'
+
+git update-ref -d refs/heads/L2
+
 ###
 ### series M
 ###
@@ -874,6 +1125,98 @@ test_expect_success \
         git diff-tree -C --find-copies-harder -r N4^ N4 >actual &&
         compare_diff_raw expect actual'
 
+test_expect_success PIPE 'N: read and copy directory' '
+       cat >expect <<-\EOF
+       :100755 100755 f1fb5da718392694d0076d677d6d0e364c79b0bc f1fb5da718392694d0076d677d6d0e364c79b0bc C100   file2/newf      file3/newf
+       :100644 100644 7123f7f44e39be127c5eb701e5968176ee9d78b1 7123f7f44e39be127c5eb701e5968176ee9d78b1 C100   file2/oldf      file3/oldf
+       EOF
+       git update-ref -d refs/heads/N4 &&
+       rm -f backflow &&
+       mkfifo backflow &&
+       (
+               exec <backflow &&
+               cat <<-EOF &&
+               commit refs/heads/N4
+               committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+               data <<COMMIT
+               copy by tree hash, part 2
+               COMMIT
+
+               from refs/heads/branch^0
+               ls "file2"
+               EOF
+               read mode type tree filename &&
+               echo "M 040000 $tree file3"
+       ) |
+       git fast-import --cat-blob-fd=3 3>backflow &&
+       git diff-tree -C --find-copies-harder -r N4^ N4 >actual &&
+       compare_diff_raw expect actual
+'
+
+test_expect_success PIPE 'N: empty directory reads as missing' '
+       cat <<-\EOF >expect &&
+       OBJNAME
+       :000000 100644 OBJNAME OBJNAME A        unrelated
+       EOF
+       echo "missing src" >expect.response &&
+       git update-ref -d refs/heads/read-empty &&
+       rm -f backflow &&
+       mkfifo backflow &&
+       (
+               exec <backflow &&
+               cat <<-EOF &&
+               commit refs/heads/read-empty
+               committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+               data <<COMMIT
+               read "empty" (missing) directory
+               COMMIT
+
+               M 100644 inline src/greeting
+               data <<BLOB
+               hello
+               BLOB
+               C src/greeting dst1/non-greeting
+               C src/greeting unrelated
+               # leave behind "empty" src directory
+               D src/greeting
+               ls "src"
+               EOF
+               read -r line &&
+               printf "%s\n" "$line" >response &&
+               cat <<-\EOF
+               D dst1
+               D dst2
+               EOF
+       ) |
+       git fast-import --cat-blob-fd=3 3>backflow &&
+       test_cmp expect.response response &&
+       git rev-list read-empty |
+       git diff-tree -r --root --stdin |
+       sed "s/$_x40/OBJNAME/g" >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success \
+       'N: copy root directory by tree hash' \
+       'cat >expect <<-\EOF &&
+       :100755 000000 f1fb5da718392694d0076d677d6d0e364c79b0bc 0000000000000000000000000000000000000000 D      file3/newf
+       :100644 000000 7123f7f44e39be127c5eb701e5968176ee9d78b1 0000000000000000000000000000000000000000 D      file3/oldf
+       EOF
+        root=$(git rev-parse refs/heads/branch^0^{tree}) &&
+        cat >input <<-INPUT_END &&
+       commit refs/heads/N6
+       committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+       data <<COMMIT
+       copy root directory by tree hash
+       COMMIT
+
+       from refs/heads/branch^0
+       M 040000 $root ""
+       INPUT_END
+        git fast-import <input &&
+        git diff-tree -C --find-copies-harder -r N4 N6 >actual &&
+        compare_diff_raw expect actual'
+
 test_expect_success \
        'N: delete directory by copying' \
        'cat >expect <<-\EOF &&
@@ -949,6 +1292,114 @@ test_expect_success \
         git diff-tree -C --find-copies-harder -r N5^^ N5 >actual &&
         compare_diff_raw expect actual'
 
+test_expect_success \
+       'N: reject foo/ syntax' \
+       'subdir=$(git rev-parse refs/heads/branch^0:file2) &&
+        test_must_fail git fast-import <<-INPUT_END
+       commit refs/heads/N5B
+       committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+       data <<COMMIT
+       copy with invalid syntax
+       COMMIT
+
+       from refs/heads/branch^0
+       M 040000 $subdir file3/
+       INPUT_END'
+
+test_expect_success \
+       'N: copy to root by id and modify' \
+       'echo "hello, world" >expect.foo &&
+        echo hello >expect.bar &&
+        git fast-import <<-SETUP_END &&
+       commit refs/heads/N7
+       committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+       data <<COMMIT
+       hello, tree
+       COMMIT
+
+       deleteall
+       M 644 inline foo/bar
+       data <<EOF
+       hello
+       EOF
+       SETUP_END
+
+        tree=$(git rev-parse --verify N7:) &&
+        git fast-import <<-INPUT_END &&
+       commit refs/heads/N8
+       committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+       data <<COMMIT
+       copy to root by id and modify
+       COMMIT
+
+       M 040000 $tree ""
+       M 644 inline foo/foo
+       data <<EOF
+       hello, world
+       EOF
+       INPUT_END
+        git show N8:foo/foo >actual.foo &&
+        git show N8:foo/bar >actual.bar &&
+        test_cmp expect.foo actual.foo &&
+        test_cmp expect.bar actual.bar'
+
+test_expect_success \
+       'N: extract subtree' \
+       'branch=$(git rev-parse --verify refs/heads/branch^{tree}) &&
+        cat >input <<-INPUT_END &&
+       commit refs/heads/N9
+       committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+       data <<COMMIT
+       extract subtree branch:newdir
+       COMMIT
+
+       M 040000 $branch ""
+       C "newdir" ""
+       INPUT_END
+        git fast-import <input &&
+        git diff --exit-code branch:newdir N9'
+
+test_expect_success \
+       'N: modify subtree, extract it, and modify again' \
+       'echo hello >expect.baz &&
+        echo hello, world >expect.qux &&
+        git fast-import <<-SETUP_END &&
+       commit refs/heads/N10
+       committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+       data <<COMMIT
+       hello, tree
+       COMMIT
+
+       deleteall
+       M 644 inline foo/bar/baz
+       data <<EOF
+       hello
+       EOF
+       SETUP_END
+
+        tree=$(git rev-parse --verify N10:) &&
+        git fast-import <<-INPUT_END &&
+       commit refs/heads/N11
+       committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+       data <<COMMIT
+       copy to root by id and modify
+       COMMIT
+
+       M 040000 $tree ""
+       M 100644 inline foo/bar/qux
+       data <<EOF
+       hello, world
+       EOF
+       R "foo" ""
+       C "bar/qux" "bar/quux"
+       INPUT_END
+        git show N11:bar/baz >actual.baz &&
+        git show N11:bar/qux >actual.qux &&
+        git show N11:bar/quux >actual.quux &&
+        test_cmp expect.baz actual.baz &&
+        test_cmp expect.qux actual.qux &&
+        test_cmp expect.qux actual.quux'
+
 ###
 ### series O
 ###
@@ -1536,6 +1987,23 @@ test_expect_success \
        'Q: verify second note for second commit' \
        'git cat-file blob refs/notes/foobar:$commit2 >actual && test_cmp expect actual'
 
+cat >input <<EOF
+reset refs/heads/Q0
+
+commit refs/heads/note-Q0
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+Note for an empty branch.
+COMMIT
+
+N inline refs/heads/Q0
+data <<NOTE
+some note
+NOTE
+EOF
+test_expect_success \
+       'Q: deny note on empty branch' \
+       'test_must_fail git fast-import <input'
 ###
 ### series R (feature and option)
 ###
@@ -1595,6 +2063,108 @@ test_expect_success \
     'cat input | git fast-import --export-marks=other.marks &&
     grep :1 other.marks'
 
+test_expect_success 'R: catch typo in marks file name' '
+       test_must_fail git fast-import --import-marks=nonexistent.marks </dev/null &&
+       echo "feature import-marks=nonexistent.marks" |
+       test_must_fail git fast-import
+'
+
+test_expect_success 'R: import and output marks can be the same file' '
+       rm -f io.marks &&
+       blob=$(echo hi | git hash-object --stdin) &&
+       cat >expect <<-EOF &&
+       :1 $blob
+       :2 $blob
+       EOF
+       git fast-import --export-marks=io.marks <<-\EOF &&
+       blob
+       mark :1
+       data 3
+       hi
+
+       EOF
+       git fast-import --import-marks=io.marks --export-marks=io.marks <<-\EOF &&
+       blob
+       mark :2
+       data 3
+       hi
+
+       EOF
+       test_cmp expect io.marks
+'
+
+test_expect_success 'R: --import-marks=foo --output-marks=foo to create foo fails' '
+       rm -f io.marks &&
+       test_must_fail git fast-import --import-marks=io.marks --export-marks=io.marks <<-\EOF
+       blob
+       mark :1
+       data 3
+       hi
+
+       EOF
+'
+
+test_expect_success 'R: --import-marks-if-exists' '
+       rm -f io.marks &&
+       blob=$(echo hi | git hash-object --stdin) &&
+       echo ":1 $blob" >expect &&
+       git fast-import --import-marks-if-exists=io.marks --export-marks=io.marks <<-\EOF &&
+       blob
+       mark :1
+       data 3
+       hi
+
+       EOF
+       test_cmp expect io.marks
+'
+
+test_expect_success 'R: feature import-marks-if-exists' '
+       rm -f io.marks &&
+       >expect &&
+
+       git fast-import --export-marks=io.marks <<-\EOF &&
+       feature import-marks-if-exists=not_io.marks
+       EOF
+       test_cmp expect io.marks &&
+
+       blob=$(echo hi | git hash-object --stdin) &&
+
+       echo ":1 $blob" >io.marks &&
+       echo ":1 $blob" >expect &&
+       echo ":2 $blob" >>expect &&
+
+       git fast-import --export-marks=io.marks <<-\EOF &&
+       feature import-marks-if-exists=io.marks
+       blob
+       mark :2
+       data 3
+       hi
+
+       EOF
+       test_cmp expect io.marks &&
+
+       echo ":3 $blob" >>expect &&
+
+       git fast-import --import-marks=io.marks \
+                       --export-marks=io.marks <<-\EOF &&
+       feature import-marks-if-exists=not_io.marks
+       blob
+       mark :3
+       data 3
+       hi
+
+       EOF
+       test_cmp expect io.marks &&
+
+       >expect &&
+
+       git fast-import --import-marks-if-exists=not_io.marks \
+                       --export-marks=io.marks <<-\EOF
+       feature import-marks-if-exists=io.marks
+       EOF
+       test_cmp expect io.marks
+'
+
 cat >input << EOF
 feature import-marks=marks.out
 feature export-marks=marks.new
@@ -1606,7 +2176,7 @@ test_expect_success \
     test_cmp marks.out marks.new'
 
 cat >input <<EOF
-feature import-marks=nonexistant.marks
+feature import-marks=nonexistent.marks
 feature export-marks=marks.new
 EOF
 
@@ -1617,7 +2187,7 @@ test_expect_success \
 
 
 cat >input <<EOF
-feature import-marks=nonexistant.marks
+feature import-marks=nonexistent.marks
 feature export-marks=combined.marks
 EOF
 
@@ -1653,6 +2223,250 @@ test_expect_success 'R: feature no-relative-marks should be honoured' '
     test_cmp marks.new non-relative.out
 '
 
+test_expect_success 'R: feature ls supported' '
+       echo "feature ls" |
+       git fast-import
+'
+
+test_expect_success 'R: feature cat-blob supported' '
+       echo "feature cat-blob" |
+       git fast-import
+'
+
+test_expect_success 'R: cat-blob-fd must be a nonnegative integer' '
+       test_must_fail git fast-import --cat-blob-fd=-1 </dev/null
+'
+
+test_expect_success NOT_MINGW 'R: print old blob' '
+       blob=$(echo "yes it can" | git hash-object -w --stdin) &&
+       cat >expect <<-EOF &&
+       ${blob} blob 11
+       yes it can
+
+       EOF
+       echo "cat-blob $blob" |
+       git fast-import --cat-blob-fd=6 6>actual &&
+       test_cmp expect actual
+'
+
+test_expect_success NOT_MINGW 'R: in-stream cat-blob-fd not respected' '
+       echo hello >greeting &&
+       blob=$(git hash-object -w greeting) &&
+       cat >expect <<-EOF &&
+       ${blob} blob 6
+       hello
+
+       EOF
+       git fast-import --cat-blob-fd=3 3>actual.3 >actual.1 <<-EOF &&
+       cat-blob $blob
+       EOF
+       test_cmp expect actual.3 &&
+       test_cmp empty actual.1 &&
+       git fast-import 3>actual.3 >actual.1 <<-EOF &&
+       option cat-blob-fd=3
+       cat-blob $blob
+       EOF
+       test_cmp empty actual.3 &&
+       test_cmp expect actual.1
+'
+
+test_expect_success NOT_MINGW 'R: print new blob' '
+       blob=$(echo "yep yep yep" | git hash-object --stdin) &&
+       cat >expect <<-EOF &&
+       ${blob} blob 12
+       yep yep yep
+
+       EOF
+       git fast-import --cat-blob-fd=6 6>actual <<-\EOF &&
+       blob
+       mark :1
+       data <<BLOB_END
+       yep yep yep
+       BLOB_END
+       cat-blob :1
+       EOF
+       test_cmp expect actual
+'
+
+test_expect_success NOT_MINGW 'R: print new blob by sha1' '
+       blob=$(echo "a new blob named by sha1" | git hash-object --stdin) &&
+       cat >expect <<-EOF &&
+       ${blob} blob 25
+       a new blob named by sha1
+
+       EOF
+       git fast-import --cat-blob-fd=6 6>actual <<-EOF &&
+       blob
+       data <<BLOB_END
+       a new blob named by sha1
+       BLOB_END
+       cat-blob $blob
+       EOF
+       test_cmp expect actual
+'
+
+test_expect_success 'setup: big file' '
+       (
+               echo "the quick brown fox jumps over the lazy dog" >big &&
+               for i in 1 2 3
+               do
+                       cat big big big big >bigger &&
+                       cat bigger bigger bigger bigger >big ||
+                       exit
+               done
+       )
+'
+
+test_expect_success 'R: print two blobs to stdout' '
+       blob1=$(git hash-object big) &&
+       blob1_len=$(wc -c <big) &&
+       blob2=$(echo hello | git hash-object --stdin) &&
+       {
+               echo ${blob1} blob $blob1_len &&
+               cat big &&
+               cat <<-EOF
+
+               ${blob2} blob 6
+               hello
+
+               EOF
+       } >expect &&
+       {
+               cat <<-\END_PART1 &&
+                       blob
+                       mark :1
+                       data <<data_end
+               END_PART1
+               cat big &&
+               cat <<-\EOF
+                       data_end
+                       blob
+                       mark :2
+                       data <<data_end
+                       hello
+                       data_end
+                       cat-blob :1
+                       cat-blob :2
+               EOF
+       } |
+       git fast-import >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success PIPE 'R: copy using cat-file' '
+       expect_id=$(git hash-object big) &&
+       expect_len=$(wc -c <big) &&
+       echo $expect_id blob $expect_len >expect.response &&
+
+       rm -f blobs &&
+       cat >frontend <<-\FRONTEND_END &&
+       #!/bin/sh
+       FRONTEND_END
+
+       mkfifo blobs &&
+       (
+               export GIT_COMMITTER_NAME GIT_COMMITTER_EMAIL GIT_COMMITTER_DATE &&
+               cat <<-\EOF &&
+               feature cat-blob
+               blob
+               mark :1
+               data <<BLOB
+               EOF
+               cat big &&
+               cat <<-\EOF &&
+               BLOB
+               cat-blob :1
+               EOF
+
+               read blob_id type size <&3 &&
+               echo "$blob_id $type $size" >response &&
+               head_c $size >blob <&3 &&
+               read newline <&3 &&
+
+               cat <<-EOF &&
+               commit refs/heads/copied
+               committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+               data <<COMMIT
+               copy big file as file3
+               COMMIT
+               M 644 inline file3
+               data <<BLOB
+               EOF
+               cat blob &&
+               echo BLOB
+       ) 3<blobs |
+       git fast-import --cat-blob-fd=3 3>blobs &&
+       git show copied:file3 >actual &&
+       test_cmp expect.response response &&
+       test_cmp big actual
+'
+
+test_expect_success PIPE 'R: print blob mid-commit' '
+       rm -f blobs &&
+       echo "A blob from _before_ the commit." >expect &&
+       mkfifo blobs &&
+       (
+               exec 3<blobs &&
+               cat <<-EOF &&
+               feature cat-blob
+               blob
+               mark :1
+               data <<BLOB
+               A blob from _before_ the commit.
+               BLOB
+               commit refs/heads/temporary
+               committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+               data <<COMMIT
+               Empty commit
+               COMMIT
+               cat-blob :1
+               EOF
+
+               read blob_id type size <&3 &&
+               head_c $size >actual <&3 &&
+               read newline <&3 &&
+
+               echo
+       ) |
+       git fast-import --cat-blob-fd=3 3>blobs &&
+       test_cmp expect actual
+'
+
+test_expect_success PIPE 'R: print staged blob within commit' '
+       rm -f blobs &&
+       echo "A blob from _within_ the commit." >expect &&
+       mkfifo blobs &&
+       (
+               exec 3<blobs &&
+               cat <<-EOF &&
+               feature cat-blob
+               commit refs/heads/within
+               committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+               data <<COMMIT
+               Empty commit
+               COMMIT
+               M 644 inline within
+               data <<BLOB
+               A blob from _within_ the commit.
+               BLOB
+               EOF
+
+               to_get=$(
+                       echo "A blob from _within_ the commit." |
+                       git hash-object --stdin
+               ) &&
+               echo "cat-blob $to_get" &&
+
+               read blob_id type size <&3 &&
+               head_c $size >actual <&3 &&
+               read newline <&3 &&
+
+               echo deleteall
+       ) |
+       git fast-import --cat-blob-fd=3 3>blobs &&
+       test_cmp expect actual
+'
+
 cat >input << EOF
 option git quiet
 blob
@@ -1661,13 +2475,53 @@ hi
 
 EOF
 
-touch empty
-
 test_expect_success 'R: quiet option results in no stats being output' '
     cat input | git fast-import 2> output &&
     test_cmp empty output
 '
 
+test_expect_success 'R: feature done means terminating "done" is mandatory' '
+       echo feature done | test_must_fail git fast-import &&
+       test_must_fail git fast-import --done </dev/null
+'
+
+test_expect_success 'R: terminating "done" with trailing gibberish is ok' '
+       git fast-import <<-\EOF &&
+       feature done
+       done
+       trailing gibberish
+       EOF
+       git fast-import <<-\EOF
+       done
+       more trailing gibberish
+       EOF
+'
+
+test_expect_success 'R: terminating "done" within commit' '
+       cat >expect <<-\EOF &&
+       OBJID
+       :000000 100644 OBJID OBJID A    hello.c
+       :000000 100644 OBJID OBJID A    hello2.c
+       EOF
+       git fast-import <<-EOF &&
+       commit refs/heads/done-ends
+       committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+       data <<EOT
+       Commit terminated by "done" command
+       EOT
+       M 100644 inline hello.c
+       data <<EOT
+       Hello, world.
+       EOT
+       C hello.c hello2.c
+       done
+       EOF
+       git rev-list done-ends |
+       git diff-tree -r --stdin --root --always |
+       sed -e "s/$_x40/OBJID/g" >actual &&
+       test_cmp expect actual
+'
+
 cat >input <<EOF
 option git non-existing-option
 EOF
@@ -1680,6 +2534,14 @@ test_expect_success 'R: unknown commandline options are rejected' '\
     test_must_fail git fast-import --non-existing-option < /dev/null
 '
 
+test_expect_success 'R: die on invalid option argument' '
+       echo "option git active-branches=-5" |
+       test_must_fail git fast-import &&
+       echo "option git depth=" |
+       test_must_fail git fast-import &&
+       test_must_fail git fast-import --depth="5 elephants" </dev/null
+'
+
 cat >input <<EOF
 option non-existing-vcs non-existing-option
 EOF