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);
parse_path_space(&source, p, &p, "source");
if (!*p)
die("Missing dest: %s", command_buf.buf);
strbuf_reset(&dest);
parse_path_eol(&dest, p, "dest");

View File

@ -1059,7 +1059,9 @@ test_expect_success 'M: rename subdirectory to new subdirectory' '
compare_diff_raw expect actual
'
test_expect_success 'M: rename root to subdirectory' '
for root in '""' ''
do
test_expect_success "M: rename root ($root) to subdirectory" '
cat >input <<-INPUT_END &&
commit refs/heads/M4
committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
@ -1068,7 +1070,7 @@ test_expect_success 'M: rename root to subdirectory' '
COMMIT
from refs/heads/M2^0
R "" sub
R $root sub
INPUT_END
@ -1082,7 +1084,8 @@ test_expect_success 'M: rename root to subdirectory' '
git fast-import <input &&
git diff-tree -M -r M4^ M4 >actual &&
compare_diff_raw expect actual
'
'
done
###
### series N
@ -1259,12 +1262,14 @@ test_expect_success PIPE 'N: empty directory reads as missing' '
test_cmp expect actual
'
test_expect_success 'N: copy root directory by tree hash' '
for root in '""' ''
do
test_expect_success "N: copy root ($root) by tree hash" '
cat >expect <<-EOF &&
:100755 000000 $newf $zero D file3/newf
:100644 000000 $oldf $zero D file3/oldf
EOF
root=$(git rev-parse refs/heads/branch^0^{tree}) &&
root_tree=$(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
@ -1273,14 +1278,14 @@ test_expect_success 'N: copy root directory by tree hash' '
COMMIT
from refs/heads/branch^0
M 040000 $root ""
M 040000 $root_tree $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: copy root by path' '
test_expect_success "N: copy root ($root) by path" '
cat >expect <<-EOF &&
:100755 100755 $newf $newf C100 file2/newf oldroot/file2/newf
:100644 100644 $oldf $oldf C100 file2/oldf oldroot/file2/oldf
@ -1296,12 +1301,13 @@ test_expect_success 'N: copy root by path' '
COMMIT
from refs/heads/branch^0
C "" oldroot
C $root oldroot
INPUT_END
git fast-import <input &&
git diff-tree -C --find-copies-harder -r branch N-copy-root-path >actual &&
compare_diff_raw expect actual
'
'
done
test_expect_success 'N: delete directory by copying' '
cat >expect <<-\EOF &&
@ -1431,7 +1437,9 @@ test_expect_success 'N: reject foo/ syntax in ls argument' '
INPUT_END
'
test_expect_success 'N: copy to root by id and modify' '
for root in '""' ''
do
test_expect_success "N: copy to root ($root) by id and modify" '
echo "hello, world" >expect.foo &&
echo hello >expect.bar &&
git fast-import <<-SETUP_END &&
@ -1456,7 +1464,7 @@ test_expect_success 'N: copy to root by id and modify' '
copy to root by id and modify
COMMIT
M 040000 $tree ""
M 040000 $tree $root
M 644 inline foo/foo
data <<EOF
hello, world
@ -1466,9 +1474,9 @@ test_expect_success 'N: copy to root by id and modify' '
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' '
test_expect_success "N: extract subtree to the root ($root)" '
branch=$(git rev-parse --verify refs/heads/branch^{tree}) &&
cat >input <<-INPUT_END &&
commit refs/heads/N9
@ -1477,14 +1485,14 @@ test_expect_success 'N: extract subtree' '
extract subtree branch:newdir
COMMIT
M 040000 $branch ""
C "newdir" ""
M 040000 $branch $root
C "newdir" $root
INPUT_END
git fast-import <input &&
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, world >expect.qux &&
git fast-import <<-SETUP_END &&
@ -1509,12 +1517,12 @@ test_expect_success 'N: modify subtree, extract it, and modify again' '
copy to root by id and modify
COMMIT
M 040000 $tree ""
M 040000 $tree $root
M 100644 inline foo/bar/qux
data <<EOF
hello, world
EOF
R "foo" ""
R "foo" $root
C "bar/qux" "bar/quux"
INPUT_END
git show N11:bar/baz >actual.baz &&
@ -1522,7 +1530,9 @@ test_expect_success 'N: modify subtree, extract it, and modify again' '
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'
test_cmp expect.qux actual.quux
'
done
###
### 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
# 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.
# 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.
test_expect_success 'T: ls root tree' '
for root in '""' ''
do
test_expect_success "T: ls root ($root) tree" '
sed -e "s/Z\$//" >expect <<-EOF &&
040000 tree $(git rev-parse S^{tree}) Z
EOF
sha1=$(git rev-parse --verify S) &&
git fast-import --import-marks=marks <<-EOF >actual &&
ls $sha1 ""
ls $sha1 $root
EOF
test_cmp expect actual
'
'
done
test_expect_success 'T: delete branch' '
git branch to-delete &&
@ -3432,30 +3446,33 @@ test_expect_success 'U: validate directory delete result' '
compare_diff_raw expect actual
'
test_expect_success 'U: filedelete root succeeds' '
for root in '""' ''
do
test_expect_success "U: filedelete root ($root) succeeds" '
cat >input <<-INPUT_END &&
commit refs/heads/U
commit refs/heads/U-delete-root
committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
data <<COMMIT
must succeed
COMMIT
from refs/heads/U^0
D ""
D $root
INPUT_END
git fast-import <input
'
'
test_expect_success 'U: validate root delete result' '
test_expect_success "U: validate root ($root) delete result" '
cat >expect <<-EOF &&
:100644 000000 $f7id $ZERO_OID D hello.c
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
'
'
done
###
### series V (checkpoint)