From: David Barr <hidden> Date: 2016-06-15 22:53:14
On Thu, Mar 8, 2012 at 11:46 AM, David Barr [off-list ref] wrote:
Hi Andrew,
On Thu, Mar 8, 2012 at 10:13 AM, Andrew Sayers
[off-list ref] wrote:
quoted
Here's a bug with svn-fe that I stumbled over while snorkelling through
repo madness. I've tested it with the version of svn-fe in git.git's
master branch.
Copying the root directory to a sub-directory (e.g. doing `svn cp .
trunk` to standardise your layout) doesn't correctly initialise the new
directory.
This issue sounds very familiar, I wonder if there's an existing test
or pending patch for it? Maybe Dmitry or Jonathan can recall.
I've stepped through the reproduction and the bug seems to arise when
the following command is sent to git-fast-import:
'ls' SP ':1' SP LF
The expected output in this example is:
'400000' SP 'tree' SP 'dd59323fe27c5647cb7ef15ce4637faae199c5f0' HT LF
The actual output is:
'missing' SP LF
--
David Barr
From: David Barr <hidden> Date: 2016-06-15 22:53:14
There is a pathological Subversion operation that svn-fe handles
incorrectly due to an unexpected response from fast-import:
svn cp $SVN_ROOT $SVN_ROOT/subdirectory
When the following command is sent to fast-import:
'ls' SP ':1' SP LF
The expected output is:
'040000' SP 'tree' SP <dataref> HT LF
The actual output is:
'missing' SP LF
This is because tree_content_get() is called but expects a non-empty
path. Instead, copy the root entry and force the mode to S_IFDIR.
Reported-by: Andrew Sayers <redacted>
Signed-off-by: David Barr <redacted>
---
fast-import.c | 7 ++++++-
t/t9300-fast-import.sh | 31 +++++++++++++++++++++++++++++++
2 files changed, 37 insertions(+), 1 deletion(-)
From: Jonathan Nieder <hidden> Date: 2016-06-15 22:53:14
(+cc: Sverre)
David Barr wrote:
When the following command is sent to fast-import:
'ls' SP ':1' SP LF
The expected output is:
'040000' SP 'tree' SP <dataref> HT LF
The actual output is:
'missing' SP LF
This is because tree_content_get() is called but expects a non-empty
path. Instead, copy the root entry and force the mode to S_IFDIR.
Reported-by: Andrew Sayers <redacted>
Signed-off-by: David Barr <redacted>
For what it's worth,
Acked-by: Jonathan Nieder <redacted>
Thanks very much for taking care of it.
[Subject: fast-import: fix ls command with empty path]
I would s/fix/accept/ to be more precise about the nature of the
breakage. (In other words: rather than mishandling ls with an empty
path, fast-import was not handling it at all.)
[...]
If this special case were implemented in tree_content_get(), we would
get support for paths with a trailing '/' (allows useless
incompatibility, bad) and support for requests like
C "" some/subdir
(good). What do you think?
-- >8 --
Subject: fast-import: allow filecopy to copy from root
Some subversion users apparently use "svn copy $SVN_ROOT
$SVN_ROOT/subdirectory" from time to time. svn-fe handles this fine
already since it translates the subversion copy instruction to
ls ""
M 040000 <returned tree name> subdirectory
We can easily imagine an alternate importer that would write
C "" subdirectory
instead, so handle that, too.
A naive implementation would also mean gaining support for copies
where the source has a trailing '/', as in
C onedir/ anotherdir
but in the spirit of 34215783 (fast-import: tighten M 040000 syntax,
2010-10-17), this patch is careful to reject that syntax to avoid
making it too easy for frontends to introduce unnecessary
incompatibilities with git fast-import 1.7.8 and older and other
fast-import consumers.
Signed-off-by: Jonathan Nieder <redacted>
---
fast-import.c | 32 ++++++++++++++-----------
t/t9300-fast-import.sh | 62 +++++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 79 insertions(+), 15 deletions(-)
@@ -1636,6 +1636,10 @@ static int tree_content_get(unsignedinti,n;structtree_entry*e;+if(!*p){+e=root;+gotolast_component;+}slash1=strchr(p,'/');if(slash1)n=slash1-p;
@@ -1648,14 +1652,10 @@ static int tree_content_get(for(i=0;i<t->entry_count;i++){e=t->entries[i];if(e->name->str_len==n&&!strncmp_icase(p,e->name->str_dat,n)){-if(!slash1){-memcpy(leaf,e,sizeof(*leaf));-if(e->tree&&is_null_sha1(e->versions[1].sha1))-leaf->tree=dup_tree_content(e->tree);-else-leaf->tree=NULL;-return1;-}+if(!slash1)+gotolast_component;+if(!slash1[1])/* paths with trailing '/' do not match */+return0;if(!S_ISDIR(e->versions[1].mode))return0;if(!e->tree)
@@ -1664,6 +1664,14 @@ static int tree_content_get(}}return0;++last_component:+memcpy(leaf,e,sizeof(*leaf));+if(e->tree&&is_null_sha1(e->versions[1].sha1))+leaf->tree=dup_tree_content(e->tree);+else+leaf->tree=NULL;+return1;}staticintupdate_branch(structbranch*b)
@@ -3005,6 +3013,7 @@ static void parse_ls(struct branch *b)structobject_entry*e=parse_treeish_dataref(&p);root=new_tree_entry();hashcpy(root->versions[1].sha1,e->idx.sha1);+root->versions[1].mode=S_IFDIR;load_tree(root);if(*p++!=' ')die("Missing space after tree-ish: %s",command_buf.buf);
On Thu, Mar 8, 2012 at 01:09, Jonathan Nieder [off-list ref] wrote:
quoted
[Subject: fast-import: fix ls command with empty path]
I would s/fix/accept/ to be more precise about the nature of the
breakage. (In other words: rather than mishandling ls with an empty
path, fast-import was not handling it at all.)
From: Jonathan Nieder <hidden> Date: 2016-06-15 22:53:15
David Barr wrote:
When the following command is sent to fast-import:
'ls' SP ':1' SP LF
The expected output is:
'040000' SP 'tree' SP <dataref> HT LF
The actual output is:
'missing' SP LF
This is because tree_content_get() is called but expects a non-empty
path. Instead, copy the root entry and force the mode to S_IFDIR.
Thanks again for your help. I've pushed the following changes to
git://repo.or.cz/git/jrn.git fast-import-pu
Testing, review, and improvements welcome.
David Barr (1):
fast-import: teach ls command to accept empty path
Jonathan Nieder (1):
fast-import: plug leak of dirty trees in 'ls' command
fast-import.c | 19 +++++++++-
t/t9300-fast-import.sh | 95 ++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 113 insertions(+), 1 deletion(-)
From: Jonathan Nieder <hidden> Date: 2016-06-15 22:53:15
When the named directory has changed since it was last written to
pack, "tree_content_get" makes a deep copy of the list of tree entries
which we forgot to free.
This memory leak has been present since the "ls" command was
introduced in v1.7.5-rc0~3^2~33 (fast-import: add 'ls' command,
2010-12-02).
Signed-off-by: Jonathan Nieder <redacted>
---
After rediscovering this, I found [1] which mentions the same bug.
[1] http://thread.gmane.org/gmane.comp.version-control.git/178007/focus=178044
fast-import.c | 2 ++
1 file changed, 2 insertions(+)
From: Jonathan Nieder <hidden> Date: 2016-06-15 22:53:15
From: David Barr <redacted>
There is a pathological Subversion operation that svn-fe handles
incorrectly due to an unexpected response from fast-import:
svn cp $SVN_ROOT $SVN_ROOT/subdirectory
When the following command is sent to fast-import:
'ls' SP ':1' SP LF
The expected output is:
'040000' SP 'tree' SP <dataref> HT LF
The actual output is:
'missing' SP LF
This is because tree_content_get() is called but expects a non-empty
path. Instead, copy the root entry.
[jn: using a deep copy; w/ more tests]
[jn: with a fix from Dmitry to fully initialize root->versions[0]
and versions[1] now that root can be passed to store_tree]
Reported-by: Andrew Sayers <redacted>
Signed-off-by: David Barr <redacted>
Signed-off-by: Dmitry Ivankov <redacted>
Signed-off-by: Jonathan Nieder <redacted>
---
fast-import.c | 17 ++++++++-
t/t9300-fast-import.sh | 95 ++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 111 insertions(+), 1 deletion(-)
@@ -1400,6 +1400,101 @@ test_expect_success \test_cmpexpect.quxactual.qux&&test_cmpexpect.quxactual.quux'+test_expect_success'N: root of unborn branch reads as present and empty''+empty_tree=$(gitmktree</dev/null)&&+echo"040000 tree $empty_tree ">expect&&+gitfast-import--cat-blob-fd=33>actual<<-EOF&&+commitrefs/heads/N-empty+committer$GIT_COMMITTER_NAME<$GIT_COMMITTER_EMAIL>$GIT_COMMITTER_DATE+data<<COMMIT+reademptyrootdirectoryvials+COMMIT++ls""+EOF+test_cmpexpectactual+'++test_expect_success'N: empty root reads as present and empty''+empty_tree=$(gitmktree</dev/null)&&+echo"040000 tree $empty_tree ">expect&&+echoempty>msg&&+cmit=$(gitcommit-tree"$empty_tree"-prefs/heads/branch^0<msg)&&+gitfast-import--cat-blob-fd=33>actual<<-EOF&&+commitrefs/heads/N-empty-existing+committer$GIT_COMMITTER_NAME<$GIT_COMMITTER_EMAIL>$GIT_COMMITTER_DATE+data<<COMMIT+reademptyrootdirectoryvials+COMMIT++ls""+EOF+test_cmpexpectactual+'++test_expect_success'N: "ls" command can read subdir of named tree''+branch_cmit=$(gitrev-parse--verifyrefs/heads/branch^0)&&+subdir_tree=$(gitrev-parse$branch_cmit:newdir)&&+echo"040000 tree $subdir_tree newdir">expect&&+gitfast-import--cat-blob-fd=33>actual<<-EOF&&+commitrefs/heads/N-subdir-of-named-tree+committer$GIT_COMMITTER_NAME<$GIT_COMMITTER_EMAIL>$GIT_COMMITTER_DATE+data<<COMMIT+readfromcommitwithls+COMMIT++ls$branch_cmit"newdir"+EOF+test_cmpexpectactual+'++test_expect_success'N: "ls" command can read root of named commit''+branch_cmit=$(gitrev-parse--verifyrefs/heads/branch^0)&&+branch_tree=$(gitrev-parse--verify$branch_cmit^{tree})&&+echo"040000 tree $branch_tree ">expect&&+gitfast-import--cat-blob-fd=33>actual<<-EOF&&+commitrefs/heads/N-root-of-named-tree+committer$GIT_COMMITTER_NAME<$GIT_COMMITTER_EMAIL>$GIT_COMMITTER_DATE+data<<COMMIT+readrootdirectoryofcommitwithls+COMMIT++ls$branch_cmit""+EOF+test_cmpexpectactual+'++test_expect_successPIPE'N: read and copy root''+cat>expect<<-\EOF&&+:100755100755f1fb5da718392694d0076d677d6d0e364c79b0bcf1fb5da718392694d0076d677d6d0e364c79b0bcC100file2/newffile3/file2/newf+:1006441006447123f7f44e39be127c5eb701e5968176ee9d78b17123f7f44e39be127c5eb701e5968176ee9d78b1C100file2/oldffile3/file2/oldf+:10075510075585df50785d62d3b05ab03d9cbf7e4a0b4944973085df50785d62d3b05ab03d9cbf7e4a0b49449730C100file4file3/file4+:100755100755e74b7d465e52746be2b4bae983670711e6e66657e74b7d465e52746be2b4bae983670711e6e66657C100newdir/exec.shfile3/newdir/exec.sh+:100644100644fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791C100newdir/interestingfile3/newdir/interesting+EOF+gitupdate-ref-drefs/heads/N12&&+rm-fbackflow&&+mkfifobackflow&&+(+exec<backflow&&+cat<<-EOF&&+commitrefs/heads/N12+committer$GIT_COMMITTER_NAME<$GIT_COMMITTER_EMAIL>$GIT_COMMITTER_DATE+data<<COMMIT+copyrootdirectorybytreehashreadvials+COMMIT++fromrefs/heads/branch^0+ls""+EOF+readmodetypetreefilename&&+echo"M 040000 $tree file3"+)|+gitfast-import--cat-blob-fd=33>backflow&&+gitdiff-tree-C--find-copies-harder-rN12^N12>actual&&+compare_diff_rawexpectactual+'+###### series O###
From: David Barr <hidden> Date: 2016-06-15 22:53:15
On Fri, Mar 9, 2012 at 7:33 AM, Jonathan Nieder [off-list ref] wrote:
From: David Barr <redacted>
There is a pathological Subversion operation that svn-fe handles
incorrectly due to an unexpected response from fast-import:
svn cp $SVN_ROOT $SVN_ROOT/subdirectory
When the following command is sent to fast-import:
'ls' SP ':1' SP LF
The expected output is:
'040000' SP 'tree' SP <dataref> HT LF
The actual output is:
'missing' SP LF
This is because tree_content_get() is called but expects a non-empty
path. Instead, copy the root entry.
[jn: using a deep copy; w/ more tests]
[jn: with a fix from Dmitry to fully initialize root->versions[0]
and versions[1] now that root can be passed to store_tree]
Reported-by: Andrew Sayers <redacted>
Signed-off-by: David Barr <redacted>
Signed-off-by: Dmitry Ivankov <redacted>
Signed-off-by: Jonathan Nieder <redacted>
---
+ /*
+ * store_tree scribbles over version[0] in leaf.tree's
+ * entries, so we need a deep copy.
+ */
+ if (root->tree && is_null_sha1(root->versions[1].sha1))
+ leaf.tree = dup_tree_content(root->tree);
Is it ok to call store_tree(root)? If so, could we not introduce a
pointer rather than a deep copy?
From: Jonathan Nieder <hidden> Date: 2016-06-15 22:53:15
David Barr wrote:
On Fri, Mar 9, 2012 at 7:33 AM, Jonathan Nieder [off-list ref] wrote:
quoted
+ /*
+ * store_tree scribbles over version[0] in leaf.tree's
+ * entries, so we need a deep copy.
+ */
+ if (root->tree && is_null_sha1(root->versions[1].sha1))
+ leaf.tree = dup_tree_content(root->tree);
Is it ok to call store_tree(root)?
Yes.
If so, could we not introduce a
pointer rather than a deep copy?
If using 'ls' with an empty path after dirtying the root tree is
common, then that would work as an optimization. The fussy bit is
making sure the call to
release_tree_content_recursive(leaf.tree);
is skipped in this case and not skipped when tree_content_get() made a
copy. That is, something like this (patch against fast-import-pu on
repo.or.cz/git/jrn.git):
-- >8 --
From: David Barr <redacted>
Subject: fast-import: optimize 'ls' command with empty path to avoid a copy
fast-import's "ls" command normally copies a tree (implicitly, by
calling tree_content_get) before passing it to store_tree. Otherwise:
- after versions[0] is overwritten by versions[1] in child
directories, it would be impossible to rebuild the tree object for
version 0 of the current tree, so parse_ls would need to
hashcpy(leaf.versions[0].sha1, leaf.versions[1].sha1)
so version 0 points to a tree that can be rebuilt.
- in turn, that would make it impossible to rebuild the tree object
for version 0 of the parent tree. And so on.
The above considerations do not apply when the tree we are examining
with 'ls' has no parent. Avoid a copy in that case.
Signed-off-by: Jonathan Nieder <redacted>
---
fast-import.c | 22 +++++++++++++++-------
1 file changed, 15 insertions(+), 7 deletions(-)
From: Jonathan Nieder <hidden> Date: 2016-06-15 22:53:16
In the same spirit as v1.7.4-rc0~177 (fast-import: Allow filemodify to
set the root, 2010-10-10), teach the 'ls' and 'C' commands the
following syntax:
ls ""
ls <dataref> ""
C "" <path>
All three are requests to read from the directory at the top of the
hierarchy.
The potential usefulness of this extension was discovered by using
svn-fe to import from a repository whose history included a
pathological Subversion operation:
svn cp $SVN_ROOT $SVN_ROOT/subdirectory
Since v1.7.10-rc0~118^2~4^2~5^2~4 (vcs-svn: eliminate repo_tree
structure, 2010-12-10) svn-fe handles this by sending the command
'ls :1 ' to fast-import, expecting output in the form
'040000' SP 'tree' SP <dataref> HT LF
describing the toplevel directory so it can be copied. After this
patch, the import works, with no modification to svn-fe needed.
Subtleties:
The 'ls <dataref> ""' command involves printing the makeshift "root"
tree that represents <dataref>, so we need to initialize its mode.
The 'C "" <path>' command needs to be careful not to copy an empty
tree to a subdirectory, as explained in v1.7.4~2^2~2^2 (fast-import:
treat filemodify with empty tree as delete, 2011-01-27).
Based on a patch by David Barr that made the same change at the
parse_ls level. David's tests were carried over and some new ones
added.
Reported-by: Andrew Sayers <redacted>
Signed-off-by: David Barr <redacted>
Signed-off-by: Jonathan Nieder <redacted>
Improved-by: Dmitry Ivankov [off-list ref]
---
Ok, here's a patch for the svn-fe bug that I could live with.
It has a semantic conflict with the fast-import-ls-fixes series that
I sent separately, which is fixed by adding
if (!slash1[1])
die("Empty path component found in input");
after
if (!slash1)
goto last_component;
and removing the now-useless
if (!n)
die("Empty path component found in input");
I'll send a fixup patch as a reply, for squashing into the merge or
this patch, whichever is the first commit that contains both topics.
fast-import.c | 43 ++++++++----
t/t9010-svn-fe.sh | 69 +++++++++++++++++++
t/t9300-fast-import.sh | 174 ++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 272 insertions(+), 14 deletions(-)
@@ -1495,6 +1498,15 @@ static int tree_content_set(if(!slash1&&!S_ISDIR(mode)&&subtree)die("Non-directories cannot have subtrees");+/* Git does not track empty directories. */+if(S_ISDIR(mode)){+if((is_null_sha1(sha1)&&!subtree->entry_count)+||!memcmp(sha1,EMPTY_TREE_SHA1_BIN,20)){+tree_content_remove(root,p,NULL);+return1;+}+}+if(!root->tree)load_tree(root);t=root->tree;
@@ -1636,6 +1648,11 @@ static int tree_content_get(unsignedinti,n;structtree_entry*e;+if(!*p){+e=root;+gotolast_component;+}+slash1=strchr(p,'/');if(slash1)n=slash1-p;
@@ -1648,14 +1665,8 @@ static int tree_content_get(for(i=0;i<t->entry_count;i++){e=t->entries[i];if(e->name->str_len==n&&!strncmp_icase(p,e->name->str_dat,n)){-if(!slash1){-memcpy(leaf,e,sizeof(*leaf));-if(e->tree&&is_null_sha1(e->versions[1].sha1))-leaf->tree=dup_tree_content(e->tree);-else-leaf->tree=NULL;-return1;-}+if(!slash1)+gotolast_component;if(!S_ISDIR(e->versions[1].mode))return0;if(!e->tree)
@@ -1664,6 +1675,14 @@ static int tree_content_get(}}return0;++last_component:+memcpy(leaf,e,sizeof(*leaf));+if(e->tree&&is_null_sha1(e->versions[1].sha1))+leaf->tree=dup_tree_content(e->tree);+else+leaf->tree=NULL;+return1;}staticintupdate_branch(structbranch*b)
@@ -2256,12 +2275,6 @@ static void file_change_m(struct branch *b)p=uq.buf;}-/* Git does not track empty, non-toplevel directories. */-if(S_ISDIR(mode)&&!memcmp(sha1,EMPTY_TREE_SHA1_BIN,20)&&*p){-tree_content_remove(&b->branch_tree,p,NULL);-return;-}-if(S_ISGITLINK(mode)){if(inline_data)die("Git links cannot be specified 'inline': %s",
@@ -271,6 +271,75 @@ test_expect_success PIPE 'directory with files' 'test_cmphidirectory/file2'+test_expect_successPIPE'copy from root to directory''+reinit_git&&+echohello>hello&&+hello_blob=$(githash-object-w-tblobhello)&&+subtree=$(+echo"100644 blob $hello_blob README.txt"|+gitmktree+)&&+expect=$(+gitmktree<<-EOF+100644blob$hello_blobREADME.txt+040000tree$subtreetrunk+EOF+)&&++{+properties\+svn:authorauthor@example.com\+svn:date"2012-10-10T00:01:003.000000Z"\+svn:log"created README.txt"&&+echoPROPS-END+}>r1.props&&+{+properties\+svn:authorauthor@example.com\+svn:date"2012-10-10T00:02:005.000000Z"\+svn:log"created trunk"&&+echoPROPS-END+}>r2.props&&+{+cat<<-EOF&&+SVN-fs-dump-format-version:3++Revision-number:1+EOF+echoProp-content-length:$(wc-c<r1.props)&&+echoContent-length:$(wc-c<r1.props)&&+echo&&+catr1.props&&+cat<<-\EOF&&++Node-path:README.txt+Node-kind:file+Node-action:add+EOF+text_no_propshello&&+echoRevision-number:2+echoProp-content-length:$(wc-c<r2.props)&&+echoContent-length:$(wc-c<r2.props)&&+echo&&+catr2.props&&+sed-e"s/X\$//"<<-\EOF++Node-path:trunk+Node-kind:dir+Node-action:add+Node-copyfrom-rev:1+Node-copyfrom-path:X+Prop-content-length:10+Content-length:10++PROPS-END+EOF+}>copy-root.dump&&+try_dumpcopy-root.dump&&++gitdiff-tree--exit-code$expectHEAD+'+ test_expect_successPIPE'branch name with backslash''reinit_git&&sort<<-\EOF>expect.branch-files&&
@@ -1047,6 +1047,49 @@ test_expect_success \gitdiff-tree-C--find-copies-harder-rN1^N1>actual&&compare_diff_rawexpectactual'+test_tick+cat>input<<INPUT_END+commitrefs/heads/N-root-to-subdir+committer$GIT_COMMITTER_NAME<$GIT_COMMITTER_EMAIL>$GIT_COMMITTER_DATE+data<<COMMIT+copytosubdir+COMMIT++fromrefs/heads/branch^0+C""subdir++INPUT_END++cat>expect<<\EOF+:100755100755f1fb5da718392694d0076d677d6d0e364c79b0bcf1fb5da718392694d0076d677d6d0e364c79b0bcC100file2/newfsubdir/file2/newf+:1006441006447123f7f44e39be127c5eb701e5968176ee9d78b17123f7f44e39be127c5eb701e5968176ee9d78b1C100file2/oldfsubdir/file2/oldf+:10075510075585df50785d62d3b05ab03d9cbf7e4a0b4944973085df50785d62d3b05ab03d9cbf7e4a0b49449730C100file4subdir/file4+:100755100755e74b7d465e52746be2b4bae983670711e6e66657e74b7d465e52746be2b4bae983670711e6e66657C100newdir/exec.shsubdir/newdir/exec.sh+:100644100644fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791C100newdir/interestingsubdir/newdir/interesting+EOF+test_expect_success\+'N: copy with empty source path'\+'gitfast-import<input&&+gitdiff-tree-C-C-r--no-commit-idN-root-to-subdir>actual&&+compare_diff_rawexpectactual'++test_tick+cat>input<<INPUT_END+commitrefs/heads/N-unquoted-root-to-subdir+committer$GIT_COMMITTER_NAME<$GIT_COMMITTER_EMAIL>$GIT_COMMITTER_DATE+data<<COMMIT+increasenesting+COMMIT++fromrefs/heads/branch^0+Csubdir++INPUT_END+test_expect_success\+'N: copy with unquoted empty source path'\+'gitfast-import<input&&+gitdiff--exit-codeN-root-to-subdirN-unquoted-root-to-subdir'+ cat>input<<INPUT_END commitrefs/heads/N2 committer$GIT_COMMITTER_NAME<$GIT_COMMITTER_EMAIL>$GIT_COMMITTER_DATE
@@ -1400,6 +1443,137 @@ test_expect_success \test_cmpexpect.quxactual.qux&&test_cmpexpect.quxactual.quux'+test_expect_success'N: root of unborn branch reads as present and empty''+empty_tree=$(gitmktree</dev/null)&&+echo"040000 tree $empty_tree ">expect&&+gitfast-import--cat-blob-fd=33>actual<<-EOF&&+commitrefs/heads/N-empty+committer$GIT_COMMITTER_NAME<$GIT_COMMITTER_EMAIL>$GIT_COMMITTER_DATE+data<<COMMIT+reademptyrootdirectoryvials+COMMIT++ls""+EOF+test_cmpexpectactual+'++test_expect_success'N: copying unborn branch root has no effect''+empty_tree=$(gitmktree</dev/null)&&+echotree$empty_tree>expect&&+gitfast-import<<-EOF&&+commitrefs/heads/N-copy-unborn+committer$GIT_COMMITTER_NAME<$GIT_COMMITTER_EMAIL>$GIT_COMMITTER_DATE+data<<COMMIT+copyemptyrootdirectory+COMMIT++C""subdir+EOF+gitcat-filecommitN-copy-unborn>cmit&&+head-n1cmit>actual&&+test_cmpexpectactual+'++test_expect_success'N: empty root reads as present and empty''+empty_tree=$(gitmktree</dev/null)&&+echo"040000 tree $empty_tree ">expect&&+echoempty>msg&&+cmit=$(gitcommit-tree"$empty_tree"-prefs/heads/branch^0<msg)&&+gitfast-import--cat-blob-fd=33>actual<<-EOF&&+commitrefs/heads/N-empty-existing+committer$GIT_COMMITTER_NAME<$GIT_COMMITTER_EMAIL>$GIT_COMMITTER_DATE+data<<COMMIT+reademptyrootdirectoryvials+COMMIT++ls""+EOF+test_cmpexpectactual+'++test_expect_success'N: copying empty root has no effect''+empty_tree=$(gitmktree</dev/null)&&+echotree$empty_tree>expect&&+echoempty>msg&&+cmit=$(gitcommit-tree"$empty_tree"-prefs/heads/branch^0<msg)&&+gitfast-import<<-EOF&&+commitrefs/heads/N-copy-empty+committer$GIT_COMMITTER_NAME<$GIT_COMMITTER_EMAIL>$GIT_COMMITTER_DATE+data<<COMMIT+copyemptyrootdirectory+COMMIT++C""subdir+EOF+gitcat-filecommitN-copy-empty>cmit&&+head-n1cmit>actual&&+test_cmpexpectactual+'++test_expect_success'N: "ls" command can read subdir of named tree''+branch_cmit=$(gitrev-parse--verifyrefs/heads/branch^0)&&+subdir_tree=$(gitrev-parse$branch_cmit:newdir)&&+echo"040000 tree $subdir_tree newdir">expect&&+gitfast-import--cat-blob-fd=33>actual<<-EOF&&+commitrefs/heads/N-subdir-of-named-tree+committer$GIT_COMMITTER_NAME<$GIT_COMMITTER_EMAIL>$GIT_COMMITTER_DATE+data<<COMMIT+readfromcommitwithls+COMMIT++ls$branch_cmit"newdir"+EOF+test_cmpexpectactual+'++test_expect_success'N: "ls" command can read root of named commit''+branch_cmit=$(gitrev-parse--verifyrefs/heads/branch^0)&&+branch_tree=$(gitrev-parse--verify$branch_cmit^{tree})&&+echo"040000 tree $branch_tree ">expect&&+gitfast-import--cat-blob-fd=33>actual<<-EOF&&+commitrefs/heads/N-root-of-named-tree+committer$GIT_COMMITTER_NAME<$GIT_COMMITTER_EMAIL>$GIT_COMMITTER_DATE+data<<COMMIT+readrootdirectoryofcommitwithls+COMMIT++ls$branch_cmit""+EOF+test_cmpexpectactual+'++test_expect_successPIPE'N: read and copy root''+cat>expect<<-\EOF&&+:100755100755f1fb5da718392694d0076d677d6d0e364c79b0bcf1fb5da718392694d0076d677d6d0e364c79b0bcC100file2/newffile3/file2/newf+:1006441006447123f7f44e39be127c5eb701e5968176ee9d78b17123f7f44e39be127c5eb701e5968176ee9d78b1C100file2/oldffile3/file2/oldf+:10075510075585df50785d62d3b05ab03d9cbf7e4a0b4944973085df50785d62d3b05ab03d9cbf7e4a0b49449730C100file4file3/file4+:100755100755e74b7d465e52746be2b4bae983670711e6e66657e74b7d465e52746be2b4bae983670711e6e66657C100newdir/exec.shfile3/newdir/exec.sh+:100644100644fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791C100newdir/interestingfile3/newdir/interesting+EOF+gitupdate-ref-drefs/heads/N12&&+rm-fbackflow&&+mkfifobackflow&&+(+exec<backflow&&+cat<<-EOF&&+commitrefs/heads/N12+committer$GIT_COMMITTER_NAME<$GIT_COMMITTER_EMAIL>$GIT_COMMITTER_DATE+data<<COMMIT+copyrootdirectorybytreehashreadvials+COMMIT++fromrefs/heads/branch^0+ls""+EOF+readmodetypetreefilename&&+echo"M 040000 $tree file3"+)|+gitfast-import--cat-blob-fd=33>backflow&&+gitdiff-tree-C--find-copies-harder-rN12^N12>actual&&+compare_diff_rawexpectactual+'+###### series O###
@@ -1658,8 +1658,6 @@ static int tree_content_get(n=slash1-p;elsen=strlen(p);-if(!n)-die("Empty path component found in input");if(!root->tree)load_tree(root);
@@ -1669,6 +1667,8 @@ static int tree_content_get(if(e->name->str_len==n&&!strncmp_icase(p,e->name->str_dat,n)){if(!slash1)gotolast_component;+if(!slash1[1])+die("Empty path component found in input");if(!S_ISDIR(e->versions[1].mode))return0;if(!e->tree)