fast-import: allow unquoted empty path for root

Ever since filerename was added in f39a946a1f (Support wholesale
directory renames in fast-import, 2007-07-09) and filecopy in b6f3481bb4
(Teach fast-import to recursively copy files/directories, 2007-07-15),
both have produced an error when the destination path is empty. Later,
when support for targeting the root directory with an empty string was
added in 2794ad5244 (fast-import: Allow filemodify to set the root,
2010-10-10), this had the effect of allowing the quoted empty string
(`""`), but forbidding its unquoted variant (``). This seems to have
been intended as simple data validation for parsing two paths, rather
than a syntax restriction, because it was not extended to the other
operations.

All other occurrences of paths (in filemodify, filedelete, the source of
filecopy and filerename, and ls) allow both.

For most of this feature's lifetime, the documentation has not
prescribed the use of quoted empty strings. In e5959106d6
(Documentation/fast-import: put explanation of M 040000 <dataref> "" in
context, 2011-01-15), its documentation was changed from “`<path>` may
also be an empty string (`""`) to specify the root of the tree” to “The
root of the tree can be represented by an empty string as `<path>`”.

Thus, we should assume that some front-ends have depended on this
behavior.

Remove this restriction for the destination paths of filecopy and
filerename and change tests targeting the root to test `""` and ``.

Signed-off-by: Thalia Archibald <thalia@archibald.dev>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Thalia Archibald 2024-04-14 01:11:52 +00:00 committed by Junio C Hamano
parent 5733f894d7
commit b5062f752e
2 changed files with 190 additions and 176 deletions

View file

@ -2423,9 +2423,6 @@ static void file_change_cr(const char *p, struct branch *b, int rename)
strbuf_reset(&source); strbuf_reset(&source);
parse_path_space(&source, p, &p, "source"); parse_path_space(&source, p, &p, "source");
if (!*p)
die("Missing dest: %s", command_buf.buf);
strbuf_reset(&dest); strbuf_reset(&dest);
parse_path_eol(&dest, p, "dest"); parse_path_eol(&dest, p, "dest");

View file

@ -1059,30 +1059,33 @@ test_expect_success 'M: rename subdirectory to new subdirectory' '
compare_diff_raw expect actual compare_diff_raw expect actual
' '
test_expect_success 'M: rename root to subdirectory' ' for root in '""' ''
cat >input <<-INPUT_END && do
commit refs/heads/M4 test_expect_success "M: rename root ($root) to subdirectory" '
committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE cat >input <<-INPUT_END &&
data <<COMMIT commit refs/heads/M4
rename root committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
COMMIT data <<COMMIT
rename root
COMMIT
from refs/heads/M2^0 from refs/heads/M2^0
R "" sub R $root sub
INPUT_END INPUT_END
cat >expect <<-EOF && cat >expect <<-EOF &&
:100644 100644 $oldf $oldf R100 file2/oldf sub/file2/oldf :100644 100644 $oldf $oldf R100 file2/oldf sub/file2/oldf
:100755 100755 $f4id $f4id R100 file4 sub/file4 :100755 100755 $f4id $f4id R100 file4 sub/file4
:100755 100755 $newf $newf R100 i/am/new/to/you sub/i/am/new/to/you :100755 100755 $newf $newf R100 i/am/new/to/you sub/i/am/new/to/you
:100755 100755 $f6id $f6id R100 newdir/exec.sh sub/newdir/exec.sh :100755 100755 $f6id $f6id R100 newdir/exec.sh sub/newdir/exec.sh
:100644 100644 $f5id $f5id R100 newdir/interesting sub/newdir/interesting :100644 100644 $f5id $f5id R100 newdir/interesting sub/newdir/interesting
EOF EOF
git fast-import <input && git fast-import <input &&
git diff-tree -M -r M4^ M4 >actual && git diff-tree -M -r M4^ M4 >actual &&
compare_diff_raw expect actual compare_diff_raw expect actual
' '
done
### ###
### series N ### series N
@ -1259,49 +1262,52 @@ test_expect_success PIPE 'N: empty directory reads as missing' '
test_cmp expect actual test_cmp expect actual
' '
test_expect_success 'N: copy root directory by tree hash' ' for root in '""' ''
cat >expect <<-EOF && do
:100755 000000 $newf $zero D file3/newf test_expect_success "N: copy root ($root) by tree hash" '
:100644 000000 $oldf $zero D file3/oldf cat >expect <<-EOF &&
EOF :100755 000000 $newf $zero D file3/newf
root=$(git rev-parse refs/heads/branch^0^{tree}) && :100644 000000 $oldf $zero D file3/oldf
cat >input <<-INPUT_END && EOF
commit refs/heads/N6 root_tree=$(git rev-parse refs/heads/branch^0^{tree}) &&
committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE cat >input <<-INPUT_END &&
data <<COMMIT commit refs/heads/N6
copy root directory by tree hash committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
COMMIT data <<COMMIT
copy root directory by tree hash
COMMIT
from refs/heads/branch^0 from refs/heads/branch^0
M 040000 $root "" M 040000 $root_tree $root
INPUT_END INPUT_END
git fast-import <input && git fast-import <input &&
git diff-tree -C --find-copies-harder -r N4 N6 >actual && git diff-tree -C --find-copies-harder -r N4 N6 >actual &&
compare_diff_raw expect actual compare_diff_raw expect actual
' '
test_expect_success 'N: copy root by path' ' test_expect_success "N: copy root ($root) by path" '
cat >expect <<-EOF && cat >expect <<-EOF &&
:100755 100755 $newf $newf C100 file2/newf oldroot/file2/newf :100755 100755 $newf $newf C100 file2/newf oldroot/file2/newf
:100644 100644 $oldf $oldf C100 file2/oldf oldroot/file2/oldf :100644 100644 $oldf $oldf C100 file2/oldf oldroot/file2/oldf
:100755 100755 $f4id $f4id C100 file4 oldroot/file4 :100755 100755 $f4id $f4id C100 file4 oldroot/file4
:100755 100755 $f6id $f6id C100 newdir/exec.sh oldroot/newdir/exec.sh :100755 100755 $f6id $f6id C100 newdir/exec.sh oldroot/newdir/exec.sh
:100644 100644 $f5id $f5id C100 newdir/interesting oldroot/newdir/interesting :100644 100644 $f5id $f5id C100 newdir/interesting oldroot/newdir/interesting
EOF EOF
cat >input <<-INPUT_END && cat >input <<-INPUT_END &&
commit refs/heads/N-copy-root-path commit refs/heads/N-copy-root-path
committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
data <<COMMIT data <<COMMIT
copy root directory by (empty) path copy root directory by (empty) path
COMMIT COMMIT
from refs/heads/branch^0 from refs/heads/branch^0
C "" oldroot C $root oldroot
INPUT_END INPUT_END
git fast-import <input && git fast-import <input &&
git diff-tree -C --find-copies-harder -r branch N-copy-root-path >actual && git diff-tree -C --find-copies-harder -r branch N-copy-root-path >actual &&
compare_diff_raw expect actual compare_diff_raw expect actual
' '
done
test_expect_success 'N: delete directory by copying' ' test_expect_success 'N: delete directory by copying' '
cat >expect <<-\EOF && cat >expect <<-\EOF &&
@ -1431,98 +1437,102 @@ test_expect_success 'N: reject foo/ syntax in ls argument' '
INPUT_END INPUT_END
' '
test_expect_success 'N: copy to root by id and modify' ' for root in '""' ''
echo "hello, world" >expect.foo && do
echo hello >expect.bar && test_expect_success "N: copy to root ($root) by id and modify" '
git fast-import <<-SETUP_END && echo "hello, world" >expect.foo &&
commit refs/heads/N7 echo hello >expect.bar &&
committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE git fast-import <<-SETUP_END &&
data <<COMMIT commit refs/heads/N7
hello, tree committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
COMMIT data <<COMMIT
hello, tree
COMMIT
deleteall deleteall
M 644 inline foo/bar M 644 inline foo/bar
data <<EOF data <<EOF
hello hello
EOF EOF
SETUP_END SETUP_END
tree=$(git rev-parse --verify N7:) && tree=$(git rev-parse --verify N7:) &&
git fast-import <<-INPUT_END && git fast-import <<-INPUT_END &&
commit refs/heads/N8 commit refs/heads/N8
committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
data <<COMMIT data <<COMMIT
copy to root by id and modify copy to root by id and modify
COMMIT COMMIT
M 040000 $tree "" M 040000 $tree $root
M 644 inline foo/foo M 644 inline foo/foo
data <<EOF data <<EOF
hello, world hello, world
EOF EOF
INPUT_END INPUT_END
git show N8:foo/foo >actual.foo && git show N8:foo/foo >actual.foo &&
git show N8:foo/bar >actual.bar && git show N8:foo/bar >actual.bar &&
test_cmp expect.foo actual.foo && test_cmp expect.foo actual.foo &&
test_cmp expect.bar actual.bar test_cmp expect.bar actual.bar
' '
test_expect_success 'N: extract subtree' ' test_expect_success "N: extract subtree to the root ($root)" '
branch=$(git rev-parse --verify refs/heads/branch^{tree}) && branch=$(git rev-parse --verify refs/heads/branch^{tree}) &&
cat >input <<-INPUT_END && cat >input <<-INPUT_END &&
commit refs/heads/N9 commit refs/heads/N9
committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
data <<COMMIT data <<COMMIT
extract subtree branch:newdir extract subtree branch:newdir
COMMIT COMMIT
M 040000 $branch "" M 040000 $branch $root
C "newdir" "" C "newdir" $root
INPUT_END INPUT_END
git fast-import <input && git fast-import <input &&
git diff --exit-code branch:newdir N9 git diff --exit-code branch:newdir N9
' '
test_expect_success 'N: modify subtree, extract it, and modify again' ' test_expect_success "N: modify subtree, extract it to the root ($root), and modify again" '
echo hello >expect.baz && echo hello >expect.baz &&
echo hello, world >expect.qux && echo hello, world >expect.qux &&
git fast-import <<-SETUP_END && git fast-import <<-SETUP_END &&
commit refs/heads/N10 commit refs/heads/N10
committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
data <<COMMIT data <<COMMIT
hello, tree hello, tree
COMMIT COMMIT
deleteall deleteall
M 644 inline foo/bar/baz M 644 inline foo/bar/baz
data <<EOF data <<EOF
hello hello
EOF EOF
SETUP_END SETUP_END
tree=$(git rev-parse --verify N10:) && tree=$(git rev-parse --verify N10:) &&
git fast-import <<-INPUT_END && git fast-import <<-INPUT_END &&
commit refs/heads/N11 commit refs/heads/N11
committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
data <<COMMIT data <<COMMIT
copy to root by id and modify copy to root by id and modify
COMMIT COMMIT
M 040000 $tree "" M 040000 $tree $root
M 100644 inline foo/bar/qux M 100644 inline foo/bar/qux
data <<EOF data <<EOF
hello, world hello, world
EOF EOF
R "foo" "" R "foo" $root
C "bar/qux" "bar/quux" C "bar/qux" "bar/quux"
INPUT_END INPUT_END
git show N11:bar/baz >actual.baz && git show N11:bar/baz >actual.baz &&
git show N11:bar/qux >actual.qux && git show N11:bar/qux >actual.qux &&
git show N11:bar/quux >actual.quux && git show N11:bar/quux >actual.quux &&
test_cmp expect.baz actual.baz && test_cmp expect.baz actual.baz &&
test_cmp expect.qux actual.qux && test_cmp expect.qux actual.qux &&
test_cmp expect.qux actual.quux' test_cmp expect.qux actual.quux
'
done
### ###
### series O ### series O
@ -3067,6 +3077,7 @@ test_expect_success 'S: ls with garbage after sha1 must fail' '
# There are two sorts of ways a path can be parsed, depending on whether it is # There are two sorts of ways a path can be parsed, depending on whether it is
# the last field on the line. Additionally, ls without a <dataref> has a special # the last field on the line. Additionally, ls without a <dataref> has a special
# case. Test every occurrence of <path> in the grammar against every error case. # case. Test every occurrence of <path> in the grammar against every error case.
# Paths for the root (empty strings) are tested elsewhere.
# #
# #
@ -3321,16 +3332,19 @@ test_path_eol_quoted_fail 'ls (without dataref in commit)' 'ls ' path
### ###
# Setup is carried over from series S. # Setup is carried over from series S.
test_expect_success 'T: ls root tree' ' for root in '""' ''
sed -e "s/Z\$//" >expect <<-EOF && do
040000 tree $(git rev-parse S^{tree}) Z test_expect_success "T: ls root ($root) tree" '
EOF sed -e "s/Z\$//" >expect <<-EOF &&
sha1=$(git rev-parse --verify S) && 040000 tree $(git rev-parse S^{tree}) Z
git fast-import --import-marks=marks <<-EOF >actual && EOF
ls $sha1 "" sha1=$(git rev-parse --verify S) &&
EOF git fast-import --import-marks=marks <<-EOF >actual &&
test_cmp expect actual ls $sha1 $root
' EOF
test_cmp expect actual
'
done
test_expect_success 'T: delete branch' ' test_expect_success 'T: delete branch' '
git branch to-delete && git branch to-delete &&
@ -3432,30 +3446,33 @@ test_expect_success 'U: validate directory delete result' '
compare_diff_raw expect actual compare_diff_raw expect actual
' '
test_expect_success 'U: filedelete root succeeds' ' for root in '""' ''
cat >input <<-INPUT_END && do
commit refs/heads/U test_expect_success "U: filedelete root ($root) succeeds" '
committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE cat >input <<-INPUT_END &&
data <<COMMIT commit refs/heads/U-delete-root
must succeed committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
COMMIT data <<COMMIT
from refs/heads/U^0 must succeed
D "" COMMIT
from refs/heads/U^0
D $root
INPUT_END INPUT_END
git fast-import <input git fast-import <input
' '
test_expect_success 'U: validate root delete result' ' test_expect_success "U: validate root ($root) delete result" '
cat >expect <<-EOF && cat >expect <<-EOF &&
:100644 000000 $f7id $ZERO_OID D hello.c :100644 000000 $f7id $ZERO_OID D hello.c
EOF EOF
git diff-tree -M -r U^1 U >actual && git diff-tree -M -r U U-delete-root >actual &&
compare_diff_raw expect actual compare_diff_raw expect actual
' '
done
### ###
### series V (checkpoint) ### series V (checkpoint)