Repository: iterative/dvc

Issue:
import/get only downloads dvc-tracked files on a directory that contains mixed git+dvc tracked files
Could be reproduced with:
```python
def test_import_mixed_dirs(tmp_dir, erepo_dir):
    erepo_dir.gen({"dir": {"foo": "foo", "bar": "bar"}})
    with (erepo_dir / "dir").chdir():
        erepo_dir.dvc.add("foo")

    erepo_dir.scm.add(["dir/foo.dvc", "dir/bar"])
    erepo_dir.scm.commit("add dir")

    from dvc.repo import Repo

    Repo.get(os.fspath(erepo_dir), "dir")
    assert (tmp_dir / "dir").read_text() == {"foo": "foo", "bar": "bar"}
```

_Originally posted by @skshetry in https://github.com/iterative/dvc/pull/4486#discussion_r481095202_

Fixed in `1.7.0`.
Affected versions: 1.6.4+


Discussion:
Or, a script:
```console
cd "$(mktemp -d)"
git init && dvc init
git add -A
git commit -m "init"
mkdir dir
echo "foo" > dir/foo
echo "bar" > dir/bar
dvc add dir/foo
git add -A
git commit -m "second commit"
dvc get . dir --out=out
ls out
```

Expected:
```console
foo bar
```

Actual:
```console
foo
```
Thank you @skshetry ! Will look into this ASAP.

Resolving patch:
diff --git a/dvc/tree/repo.py b/dvc/tree/repo.py
--- a/dvc/tree/repo.py
+++ b/dvc/tree/repo.py
@@ -316,11 +316,8 @@ def get_dir_hash(self, path_info, **kwargs):
         if not self.exists(path_info):
             raise FileNotFoundError
         _, dvc_tree = self._get_tree_pair(path_info)
-        if dvc_tree and dvc_tree.exists(path_info):
-            try:
-                return dvc_tree.get_dir_hash(path_info, **kwargs)
-            except OutputNotFoundError:
-                pass
+        if dvc_tree and dvc_tree.isdvc(path_info):
+            return dvc_tree.get_dir_hash(path_info, **kwargs)
         return super().get_dir_hash(path_info, **kwargs)
 
     def get_file_hash(self, path_info):


Test patch:
diff --git a/tests/func/test_get.py b/tests/func/test_get.py
--- a/tests/func/test_get.py
+++ b/tests/func/test_get.py
@@ -252,3 +252,16 @@ def test_get_pipeline_tracked_outs(
     with git_dir.chdir():
         Repo.get("file:///{}".format(os.fspath(tmp_dir)), "bar", out="baz")
         assert (git_dir / "baz").read_text() == "foo"
+
+
+def test_get_mixed_dir(tmp_dir, erepo_dir):
+    with erepo_dir.chdir():
+        erepo_dir.dvc_gen(os.path.join("dir", "foo"), "foo", commit="foo")
+        erepo_dir.scm_gen(os.path.join("dir", "bar"), "bar", commit="bar")
+
+    Repo.get(os.fspath(erepo_dir), "dir")
+    assert (tmp_dir / "dir").read_text() == {
+        ".gitignore": "/foo\n",
+        "foo": "foo",
+        "bar": "bar",
+    }
diff --git a/tests/func/test_import.py b/tests/func/test_import.py
--- a/tests/func/test_import.py
+++ b/tests/func/test_import.py
@@ -368,3 +368,16 @@ def test_local_import(tmp_dir, dvc, scm):
     tmp_dir.dvc_gen("foo", "foo", commit="init")
     (tmp_dir / "outdir").mkdir()
     dvc.imp(".", "foo", out="outdir")
+
+
+def test_import_mixed_dir(tmp_dir, dvc, erepo_dir):
+    with erepo_dir.chdir():
+        erepo_dir.dvc_gen(os.path.join("dir", "foo"), "foo", commit="foo")
+        erepo_dir.scm_gen(os.path.join("dir", "bar"), "bar", commit="bar")
+
+    dvc.imp(os.fspath(erepo_dir), "dir")
+    assert (tmp_dir / "dir").read_text() == {
+        ".gitignore": "/foo\n",
+        "foo": "foo",
+        "bar": "bar",
+    }
diff --git a/tests/unit/tree/test_repo.py b/tests/unit/tree/test_repo.py
--- a/tests/unit/tree/test_repo.py
+++ b/tests/unit/tree/test_repo.py
@@ -147,6 +147,34 @@ def test_walk(tmp_dir, dvc, dvcfiles, extra_expected):
     assert len(actual) == len(expected)
 
 
+def test_walk_mixed_dir(tmp_dir, scm, dvc):
+    tmp_dir.gen({"dir": {"foo": "foo", "bar": "bar"}})
+    tmp_dir.dvc.add(os.path.join("dir", "foo"))
+    tmp_dir.scm.add(
+        [
+            os.path.join("dir", "bar"),
+            os.path.join("dir", ".gitignore"),
+            os.path.join("dir", "foo.dvc"),
+        ]
+    )
+    tmp_dir.scm.commit("add dir")
+
+    tree = RepoTree(dvc)
+
+    expected = [
+        str(PathInfo("dir") / "foo"),
+        str(PathInfo("dir") / "bar"),
+        str(PathInfo("dir") / ".gitignore"),
+    ]
+    actual = []
+    for root, dirs, files in tree.walk("dir"):
+        for entry in dirs + files:
+            actual.append(os.path.join(root, entry))
+
+    assert set(actual) == set(expected)
+    assert len(actual) == len(expected)
+
+
 def test_walk_onerror(tmp_dir, dvc):
     def onerror(exc):
         raise exc
@@ -401,6 +429,25 @@ def test_get_hash_cached_granular(tmp_dir, dvc, mocker):
     assert dvc_tree_spy.called
 
 
+def test_get_hash_mixed_dir(tmp_dir, scm, dvc):
+    tmp_dir.gen({"dir": {"foo": "foo", "bar": "bar"}})
+    tmp_dir.dvc.add(os.path.join("dir", "foo"))
+    tmp_dir.scm.add(
+        [
+            os.path.join("dir", "bar"),
+            os.path.join("dir", ".gitignore"),
+            os.path.join("dir", "foo.dvc"),
+        ]
+    )
+    tmp_dir.scm.commit("add dir")
+
+    tree = RepoTree(dvc)
+    with dvc.state:
+        actual = tree.get_hash(PathInfo(tmp_dir) / "dir")
+    expected = HashInfo("md5", "e1d9e8eae5374860ae025ec84cfd85c7.dir")
+    assert actual == expected
+
+
 @pytest.mark.parametrize("traverse_subrepos", [True, False])
 def test_walk_nested_subrepos(tmp_dir, dvc, scm, traverse_subrepos):
     # generate a dvc and fs structure, with suffix based on repo's basename