diff --git a/dvc/external_repo.py b/dvc/external_repo.py
--- a/dvc/external_repo.py
+++ b/dvc/external_repo.py
@@ -72,6 +72,9 @@ class BaseExternalRepo:
 
     _local_cache = None
 
+    def __str__(self):
+        return self.url
+
     @property
     def local_cache(self):
         if hasattr(self, "cache"):
diff --git a/dvc/repo/tree.py b/dvc/repo/tree.py
--- a/dvc/repo/tree.py
+++ b/dvc/repo/tree.py
@@ -44,6 +44,7 @@ def _is_cached(out):
         return outs
 
     def _get_granular_checksum(self, path, out, remote=None):
+        assert isinstance(path, PathInfo)
         if not self.fetch and not self.stream:
             raise FileNotFoundError
         dir_cache = out.get_dir_cache(remote=remote)
@@ -128,7 +129,7 @@ def isdir(self, path):
         # for dir checksum, we need to check if this is a file inside the
         # directory
         try:
-            self._get_granular_checksum(path, out)
+            self._get_granular_checksum(path_info, out)
             return False
         except FileNotFoundError:
             return True

Inconsistency with dvc and git files. Will fix. Thanks for the report! :pray: 
diff --git a/tests/func/test_ls.py b/tests/func/test_ls.py
--- a/tests/func/test_ls.py
+++ b/tests/func/test_ls.py
@@ -472,3 +472,33 @@ def test_ls_granular(erepo_dir):
         {"isout": False, "isdir": False, "isexec": False, "path": "2"},
         {"isout": False, "isdir": True, "isexec": False, "path": "subdir"},
     ]
+
+
+@pytest.mark.parametrize("use_scm", [True, False])
+def test_ls_target(erepo_dir, use_scm):
+    with erepo_dir.chdir():
+        gen = erepo_dir.scm_gen if use_scm else erepo_dir.dvc_gen
+        gen(
+            {
+                "dir": {
+                    "1": "1",
+                    "2": "2",
+                    "subdir": {"foo": "foo", "bar": "bar"},
+                }
+            },
+            commit="create dir",
+        )
+
+    def _ls(path):
+        return Repo.ls(os.fspath(erepo_dir), path)
+
+    assert _ls(os.path.join("dir", "1")) == [
+        {"isout": False, "isdir": False, "isexec": False, "path": "1"}
+    ]
+    assert _ls(os.path.join("dir", "subdir", "foo")) == [
+        {"isout": False, "isdir": False, "isexec": False, "path": "foo"}
+    ]
+    assert _ls(os.path.join("dir", "subdir")) == [
+        {"isdir": False, "isexec": 0, "isout": False, "path": "bar"},
+        {"isdir": False, "isexec": 0, "isout": False, "path": "foo"},
+    ]
diff --git a/tests/func/test_tree.py b/tests/func/test_tree.py
--- a/tests/func/test_tree.py
+++ b/tests/func/test_tree.py
@@ -211,7 +211,7 @@ def test_repotree_cache_save(tmp_dir, dvc, scm, erepo_dir, local_cloud):
     # into dvc.cache, not fetched or streamed from a remote
     tree = RepoTree(erepo_dir.dvc, stream=True)
     expected = [
-        tree.get_file_hash(erepo_dir / path)
+        tree.get_file_hash(PathInfo(erepo_dir / path))
         for path in ("dir/bar", "dir/subdir/foo")
     ]
 

list: granularity not working [qa]
## Bug Report

```console
λ dvc list https://github.com/iterative/example-get-started data/
.gitignore
data.xml
data.xml.dvc
features
prepared

λ dvc list https://github.com/iterative/example-get-started data/data.xml.dvc
data.xml.dvc

λ dvc list https://github.com/iterative/example-get-started data/features
test.pkl
train.pkl

λ dvc list https://github.com/iterative/example-get-started data/features/test.pkl
ERROR: failed to list 'https://github.com/iterative/example-get-started' - The path 'data/features/test.pkl' does not exist in the target repository 'ExternalRepo: 'C:\Users\poj12\AppData\Local\Temp\tmpo4ck0n0advc-clone'' neither as a DVC output nor as a Git-tracked file.
```
> Works for get and import.

- [ ] Also, the error message could be friendlier (the part about `target repository 'ExternalRepo...`).

### Please provide information about your setup

**Output of `dvc version`:**

```console
$ dvc version

```

**Additional Information (if any):**

If applicable, please also provide a `--verbose` output of the command, eg: `dvc add --verbose`.


tests/func/test_ls.py::test_ls_target[False]
tests/func/test_ls.py::test_ls_repo tests/func/test_ls.py::test_ls_repo_with_color tests/func/test_ls.py::test_ls_repo_recursive tests/func/test_ls.py::test_ls_repo_dvc_only_recursive tests/func/test_ls.py::test_ls_repo_with_path_dir tests/func/test_ls.py::test_ls_repo_with_path_dir_dvc_only_empty tests/func/test_ls.py::test_ls_repo_with_path_subdir tests/func/test_ls.py::test_ls_repo_with_path_subdir_dvc_only tests/func/test_ls.py::test_ls_repo_with_path_subdir_dvc_only_recursive tests/func/test_ls.py::test_ls_repo_with_path_file_out tests/func/test_ls.py::test_ls_repo_with_file_path_fs tests/func/test_ls.py::test_ls_repo_with_missed_path tests/func/test_ls.py::test_ls_repo_with_missed_path_dvc_only tests/func/test_ls.py::test_ls_repo_with_removed_dvc_dir tests/func/test_ls.py::test_ls_repo_with_removed_dvc_dir_recursive tests/func/test_ls.py::test_ls_repo_with_removed_dvc_dir_with_path_dir tests/func/test_ls.py::test_ls_repo_with_removed_dvc_dir_with_path_file tests/func/test_ls.py::test_ls_remote_repo tests/func/test_ls.py::test_ls_remote_repo_recursive tests/func/test_ls.py::test_ls_remote_git_only_repo_recursive tests/func/test_ls.py::test_ls_remote_repo_with_path_dir tests/func/test_ls.py::test_ls_remote_repo_with_rev tests/func/test_ls.py::test_ls_remote_repo_with_rev_recursive tests/func/test_ls.py::test_ls_not_existed_url tests/func/test_ls.py::test_ls_shows_pipeline_tracked_outs tests/func/test_ls.py::test_ls_granular tests/func/test_ls.py::test_ls_target[True] tests/func/test_tree.py::TestWorkingTree::test_exists tests/func/test_tree.py::TestWorkingTree::test_isdir tests/func/test_tree.py::TestWorkingTree::test_isfile tests/func/test_tree.py::TestWorkingTree::test_open tests/func/test_tree.py::TestGitTree::test_exists tests/func/test_tree.py::TestGitTree::test_isdir tests/func/test_tree.py::TestGitTree::test_isfile tests/func/test_tree.py::TestGitTree::test_open tests/func/test_tree.py::TestGitSubmoduleTree::test_exists tests/func/test_tree.py::TestGitSubmoduleTree::test_isdir tests/func/test_tree.py::TestGitSubmoduleTree::test_isfile tests/func/test_tree.py::TestGitSubmoduleTree::test_open tests/func/test_tree.py::TestWalkInNoSCM::test tests/func/test_tree.py::TestWalkInNoSCM::test_subdir tests/func/test_tree.py::TestWalkInGit::test_branch tests/func/test_tree.py::TestWalkInGit::test_nobranch tests/func/test_tree.py::test_repotree_walk_fetch tests/func/test_tree.py::test_repotree_cache_save tests/func/test_tree.py::test_cleantree_subrepo