Repository: iterative/dvc

Issue:
dvc list: Error on empty directory. 
# Bug Report

Got error message on an empty directory, shouldn't it show nothing? like ls command.


<!--
## Issue name

Issue names must follow the pattern `command: description` where the command is the dvc command that you are trying to run. The description should describe the consequence of the bug. 

Example: `repro: doesn't detect input changes`
-->

## Description
![image](https://user-images.githubusercontent.com/6745454/115115559-bfcb1d80-9fc7-11eb-82d4-bb04d0413795.png)
Error when list a empty path, strange behavior.
Might relate to https://github.com/iterative/dvc/blob/daf07451f8e8f3e76a791c696b0ea175e8ed3ac1/dvc/repo/ls.py#L40-L41

<!--
A clear and concise description of what the bug is.
-->

### Reproduce

1. git init
2. dvc init
3. mkdir empty
4. dvc list . empty

<!--
Step list of how to reproduce the bug
-->

<!--
Example:

1. dvc init
2. Copy dataset.zip to the directory
3. dvc add dataset.zip
4. dvc run -d dataset.zip -o model ./train.sh
5. modify dataset.zip
6. dvc repro
-->

### Expected
Show nothing like ls command
![image](https://user-images.githubusercontent.com/6745454/115115651-2a7c5900-9fc8-11eb-9a86-884626d240ea.png)

<!--
A clear and concise description of what you expect to happen.
-->

### Environment information
DVC version: 2.0.17+7e4851
---------------------------------
Platform: Python 3.8.8 on macOS-10.16-x86_64-i386-64bit
Supports: All remotes
Cache types: <https://error.dvc.org/no-dvc-cache>
Caches: local
Remotes: None
Workspace directory: apfs on /dev/disk3s1s1
Repo: dvc, git
<!--
This is required to ensure that we can reproduce the bug.
-->

**Output of `dvc doctor`:**

```console
$ dvc doctor
```

**Additional Information (if any):**

<!--
Please check https://github.com/iterative/dvc/wiki/Debugging-DVC on ways to gather more information regarding the issue.

If applicable, please also provide a `--verbose` output of the command, eg: `dvc add --verbose`.
If the issue is regarding the performance, please attach the profiling information and the benchmark comparisons.
-->



Discussion:
We have already had a test about this.

https://github.com/iterative/dvc/blob/68897aa0def4e509a27d75f2e78106495d08e0c4/tests/func/test_ls.py#L167-L173

and it was introduced from #3246 at the beginning of `dvc list` command.

Resolving patch:
diff --git a/dvc/repo/ls.py b/dvc/repo/ls.py
--- a/dvc/repo/ls.py
+++ b/dvc/repo/ls.py
@@ -1,10 +1,22 @@
 import os
 from itertools import chain
+from typing import TYPE_CHECKING, Optional
 
 from dvc.exceptions import PathMissingError
 
+if TYPE_CHECKING:
+    from dvc.fs.repo import RepoFileSystem
 
-def ls(url, path=None, rev=None, recursive=None, dvc_only=False):
+    from . import Repo
+
+
+def ls(
+    url: str,
+    path: Optional[str] = None,
+    rev: str = None,
+    recursive: bool = None,
+    dvc_only: bool = False,
+):
     """Methods for getting files and outputs for the repo.
 
     Args:
@@ -31,10 +43,7 @@ def ls(url, path=None, rev=None, recursive=None, dvc_only=False):
     with Repo.open(url, rev=rev, subrepos=True, uninitialized=True) as repo:
         path = path or ""
 
-        ret = _ls(repo.repo_fs, path, recursive, dvc_only)
-
-        if path and not ret:
-            raise PathMissingError(path, repo, dvc_only=dvc_only)
+        ret = _ls(repo, path, recursive, dvc_only)
 
         ret_list = []
         for path, info in ret.items():
@@ -44,13 +53,16 @@ def ls(url, path=None, rev=None, recursive=None, dvc_only=False):
         return ret_list
 
 
-def _ls(fs, path, recursive=None, dvc_only=False):
+def _ls(
+    repo: "Repo", path: str, recursive: bool = None, dvc_only: bool = False
+):
+    fs: "RepoFileSystem" = repo.repo_fs
     fs_path = fs.from_os_path(path)
 
     try:
         fs_path = fs.info(fs_path)["name"]
     except FileNotFoundError:
-        return {}
+        raise PathMissingError(path, repo, dvc_only=dvc_only)
 
     infos = {}
     for root, dirs, files in fs.walk(


Test patch:
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
@@ -150,9 +150,20 @@ def test_ls_repo_with_path_dir_dvc_only_empty(tmp_dir, dvc, scm):
     tmp_dir.scm_gen(FS_STRUCTURE, commit="init")
     tmp_dir.dvc_gen(DVC_STRUCTURE, commit="dvc")
     tmp_dir.scm_gen({"folder/.keep": "content"}, commit="add .keep")
+    tmp_dir.scm_gen({"empty_scm_folder/": {}}, commit="add scm empty")
+    tmp_dir.dvc_gen({"empty_dvc_folder": {}}, commit="empty dvc folder")
 
     with pytest.raises(PathMissingError):
-        Repo.ls(os.fspath(tmp_dir), path="folder", dvc_only=True)
+        Repo.ls(os.fspath(tmp_dir), path="not_exist_folder")
+
+    assert Repo.ls(os.fspath(tmp_dir), path="empty_scm_folder") == []
+
+    assert Repo.ls(os.fspath(tmp_dir), path="folder", dvc_only=True) == []
+
+    assert (
+        Repo.ls(os.fspath(tmp_dir), path="empty_dvc_folder", dvc_only=True)
+        == []
+    )
 
 
 def test_ls_repo_with_path_subdir(tmp_dir, dvc, scm):