We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN 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.
-->


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `dvc/repo/ls.py`
Content:
```
1 import os
2 from itertools import chain
3 
4 from dvc.exceptions import PathMissingError
5 
6 
7 def ls(url, path=None, rev=None, recursive=None, dvc_only=False):
8     """Methods for getting files and outputs for the repo.
9 
10     Args:
11         url (str): the repo url
12         path (str, optional): relative path into the repo
13         rev (str, optional): SHA commit, branch or tag name
14         recursive (bool, optional): recursively walk the repo
15         dvc_only (bool, optional): show only DVC-artifacts
16 
17     Returns:
18         list of `entry`
19 
20     Notes:
21         `entry` is a dictionary with structure
22         {
23             "path": str,
24             "isout": bool,
25             "isdir": bool,
26             "isexec": bool,
27         }
28     """
29     from . import Repo
30 
31     with Repo.open(url, rev=rev, subrepos=True, uninitialized=True) as repo:
32         path = path or ""
33 
34         ret = _ls(repo.repo_fs, path, recursive, dvc_only)
35 
36         if path and not ret:
37             raise PathMissingError(path, repo, dvc_only=dvc_only)
38 
39         ret_list = []
40         for path, info in ret.items():
41             info["path"] = path
42             ret_list.append(info)
43         ret_list.sort(key=lambda f: f["path"])
44         return ret_list
45 
46 
47 def _ls(fs, path, recursive=None, dvc_only=False):
48     fs_path = fs.from_os_path(path)
49 
50     try:
51         fs_path = fs.info(fs_path)["name"]
52     except FileNotFoundError:
53         return {}
54 
55     infos = {}
56     for root, dirs, files in fs.walk(
57         fs_path, dvcfiles=True, dvc_only=dvc_only
58     ):
59         entries = chain(files, dirs) if not recursive else files
60 
61         for entry in entries:
62             entry_fs_path = fs.path.join(root, entry)
63             relparts = fs.path.relparts(entry_fs_path, fs_path)
64             name = os.path.join(*relparts)
65             infos[name] = fs.info(entry_fs_path)
66 
67         if not recursive:
68             break
69 
70     if not infos and fs.isfile(fs_path):
71         infos[os.path.basename(path)] = fs.info(fs_path)
72 
73     ret = {}
74     for name, info in infos.items():
75         dvc_info = info.get("dvc_info", {})
76         if dvc_info.get("outs") or not dvc_only:
77             ret[name] = {
78                 "isout": dvc_info.get("isout", False),
79                 "isdir": info["type"] == "directory",
80                 "isexec": info.get("isexec", False),
81             }
82 
83     return ret
84
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

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 @@
     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 @@
         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(

{"golden_diff": "diff --git a/dvc/repo/ls.py b/dvc/repo/ls.py\n--- a/dvc/repo/ls.py\n+++ b/dvc/repo/ls.py\n@@ -1,10 +1,22 @@\n import os\n from itertools import chain\n+from typing import TYPE_CHECKING, Optional\n \n from dvc.exceptions import PathMissingError\n \n+if TYPE_CHECKING:\n+    from dvc.fs.repo import RepoFileSystem\n \n-def ls(url, path=None, rev=None, recursive=None, dvc_only=False):\n+    from . import Repo\n+\n+\n+def ls(\n+    url: str,\n+    path: Optional[str] = None,\n+    rev: str = None,\n+    recursive: bool = None,\n+    dvc_only: bool = False,\n+):\n     \"\"\"Methods for getting files and outputs for the repo.\n \n     Args:\n@@ -31,10 +43,7 @@\n     with Repo.open(url, rev=rev, subrepos=True, uninitialized=True) as repo:\n         path = path or \"\"\n \n-        ret = _ls(repo.repo_fs, path, recursive, dvc_only)\n-\n-        if path and not ret:\n-            raise PathMissingError(path, repo, dvc_only=dvc_only)\n+        ret = _ls(repo, path, recursive, dvc_only)\n \n         ret_list = []\n         for path, info in ret.items():\n@@ -44,13 +53,16 @@\n         return ret_list\n \n \n-def _ls(fs, path, recursive=None, dvc_only=False):\n+def _ls(\n+    repo: \"Repo\", path: str, recursive: bool = None, dvc_only: bool = False\n+):\n+    fs: \"RepoFileSystem\" = repo.repo_fs\n     fs_path = fs.from_os_path(path)\n \n     try:\n         fs_path = fs.info(fs_path)[\"name\"]\n     except FileNotFoundError:\n-        return {}\n+        raise PathMissingError(path, repo, dvc_only=dvc_only)\n \n     infos = {}\n     for root, dirs, files in fs.walk(\n", "issue": "dvc list: Error on empty directory. \n# Bug Report\r\n\r\nGot error message on an empty directory, shouldn't it show nothing? like ls command.\r\n\r\n\r\n<!--\r\n## Issue name\r\n\r\nIssue 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. \r\n\r\nExample: `repro: doesn't detect input changes`\r\n-->\r\n\r\n## Description\r\n![image](https://user-images.githubusercontent.com/6745454/115115559-bfcb1d80-9fc7-11eb-82d4-bb04d0413795.png)\r\nError when list a empty path, strange behavior.\r\nMight relate to https://github.com/iterative/dvc/blob/daf07451f8e8f3e76a791c696b0ea175e8ed3ac1/dvc/repo/ls.py#L40-L41\r\n\r\n<!--\r\nA clear and concise description of what the bug is.\r\n-->\r\n\r\n### Reproduce\r\n\r\n1. git init\r\n2. dvc init\r\n3. mkdir empty\r\n4. dvc list . empty\r\n\r\n<!--\r\nStep list of how to reproduce the bug\r\n-->\r\n\r\n<!--\r\nExample:\r\n\r\n1. dvc init\r\n2. Copy dataset.zip to the directory\r\n3. dvc add dataset.zip\r\n4. dvc run -d dataset.zip -o model ./train.sh\r\n5. modify dataset.zip\r\n6. dvc repro\r\n-->\r\n\r\n### Expected\r\nShow nothing like ls command\r\n![image](https://user-images.githubusercontent.com/6745454/115115651-2a7c5900-9fc8-11eb-9a86-884626d240ea.png)\r\n\r\n<!--\r\nA clear and concise description of what you expect to happen.\r\n-->\r\n\r\n### Environment information\r\nDVC version: 2.0.17+7e4851\r\n---------------------------------\r\nPlatform: Python 3.8.8 on macOS-10.16-x86_64-i386-64bit\r\nSupports: All remotes\r\nCache types: <https://error.dvc.org/no-dvc-cache>\r\nCaches: local\r\nRemotes: None\r\nWorkspace directory: apfs on /dev/disk3s1s1\r\nRepo: dvc, git\r\n<!--\r\nThis is required to ensure that we can reproduce the bug.\r\n-->\r\n\r\n**Output of `dvc doctor`:**\r\n\r\n```console\r\n$ dvc doctor\r\n```\r\n\r\n**Additional Information (if any):**\r\n\r\n<!--\r\nPlease check https://github.com/iterative/dvc/wiki/Debugging-DVC on ways to gather more information regarding the issue.\r\n\r\nIf applicable, please also provide a `--verbose` output of the command, eg: `dvc add --verbose`.\r\nIf the issue is regarding the performance, please attach the profiling information and the benchmark comparisons.\r\n-->\r\n\n", "before_files": [{"content": "import os\nfrom itertools import chain\n\nfrom dvc.exceptions import PathMissingError\n\n\ndef ls(url, path=None, rev=None, recursive=None, dvc_only=False):\n    \"\"\"Methods for getting files and outputs for the repo.\n\n    Args:\n        url (str): the repo url\n        path (str, optional): relative path into the repo\n        rev (str, optional): SHA commit, branch or tag name\n        recursive (bool, optional): recursively walk the repo\n        dvc_only (bool, optional): show only DVC-artifacts\n\n    Returns:\n        list of `entry`\n\n    Notes:\n        `entry` is a dictionary with structure\n        {\n            \"path\": str,\n            \"isout\": bool,\n            \"isdir\": bool,\n            \"isexec\": bool,\n        }\n    \"\"\"\n    from . import Repo\n\n    with Repo.open(url, rev=rev, subrepos=True, uninitialized=True) as repo:\n        path = path or \"\"\n\n        ret = _ls(repo.repo_fs, path, recursive, dvc_only)\n\n        if path and not ret:\n            raise PathMissingError(path, repo, dvc_only=dvc_only)\n\n        ret_list = []\n        for path, info in ret.items():\n            info[\"path\"] = path\n            ret_list.append(info)\n        ret_list.sort(key=lambda f: f[\"path\"])\n        return ret_list\n\n\ndef _ls(fs, path, recursive=None, dvc_only=False):\n    fs_path = fs.from_os_path(path)\n\n    try:\n        fs_path = fs.info(fs_path)[\"name\"]\n    except FileNotFoundError:\n        return {}\n\n    infos = {}\n    for root, dirs, files in fs.walk(\n        fs_path, dvcfiles=True, dvc_only=dvc_only\n    ):\n        entries = chain(files, dirs) if not recursive else files\n\n        for entry in entries:\n            entry_fs_path = fs.path.join(root, entry)\n            relparts = fs.path.relparts(entry_fs_path, fs_path)\n            name = os.path.join(*relparts)\n            infos[name] = fs.info(entry_fs_path)\n\n        if not recursive:\n            break\n\n    if not infos and fs.isfile(fs_path):\n        infos[os.path.basename(path)] = fs.info(fs_path)\n\n    ret = {}\n    for name, info in infos.items():\n        dvc_info = info.get(\"dvc_info\", {})\n        if dvc_info.get(\"outs\") or not dvc_only:\n            ret[name] = {\n                \"isout\": dvc_info.get(\"isout\", False),\n                \"isdir\": info[\"type\"] == \"directory\",\n                \"isexec\": info.get(\"isexec\", False),\n            }\n\n    return ret\n", "path": "dvc/repo/ls.py"}], "after_files": [{"content": "import os\nfrom itertools import chain\nfrom typing import TYPE_CHECKING, Optional\n\nfrom dvc.exceptions import PathMissingError\n\nif TYPE_CHECKING:\n    from dvc.fs.repo import RepoFileSystem\n\n    from . import Repo\n\n\ndef ls(\n    url: str,\n    path: Optional[str] = None,\n    rev: str = None,\n    recursive: bool = None,\n    dvc_only: bool = False,\n):\n    \"\"\"Methods for getting files and outputs for the repo.\n\n    Args:\n        url (str): the repo url\n        path (str, optional): relative path into the repo\n        rev (str, optional): SHA commit, branch or tag name\n        recursive (bool, optional): recursively walk the repo\n        dvc_only (bool, optional): show only DVC-artifacts\n\n    Returns:\n        list of `entry`\n\n    Notes:\n        `entry` is a dictionary with structure\n        {\n            \"path\": str,\n            \"isout\": bool,\n            \"isdir\": bool,\n            \"isexec\": bool,\n        }\n    \"\"\"\n    from . import Repo\n\n    with Repo.open(url, rev=rev, subrepos=True, uninitialized=True) as repo:\n        path = path or \"\"\n\n        ret = _ls(repo, path, recursive, dvc_only)\n\n        ret_list = []\n        for path, info in ret.items():\n            info[\"path\"] = path\n            ret_list.append(info)\n        ret_list.sort(key=lambda f: f[\"path\"])\n        return ret_list\n\n\ndef _ls(\n    repo: \"Repo\", path: str, recursive: bool = None, dvc_only: bool = False\n):\n    fs: \"RepoFileSystem\" = repo.repo_fs\n    fs_path = fs.from_os_path(path)\n\n    try:\n        fs_path = fs.info(fs_path)[\"name\"]\n    except FileNotFoundError:\n        raise PathMissingError(path, repo, dvc_only=dvc_only)\n\n    infos = {}\n    for root, dirs, files in fs.walk(\n        fs_path, dvcfiles=True, dvc_only=dvc_only\n    ):\n        entries = chain(files, dirs) if not recursive else files\n\n        for entry in entries:\n            entry_fs_path = fs.path.join(root, entry)\n            relparts = fs.path.relparts(entry_fs_path, fs_path)\n            name = os.path.join(*relparts)\n            infos[name] = fs.info(entry_fs_path)\n\n        if not recursive:\n            break\n\n    if not infos and fs.isfile(fs_path):\n        infos[os.path.basename(path)] = fs.info(fs_path)\n\n    ret = {}\n    for name, info in infos.items():\n        dvc_info = info.get(\"dvc_info\", {})\n        if dvc_info.get(\"outs\") or not dvc_only:\n            ret[name] = {\n                \"isout\": dvc_info.get(\"isout\", False),\n                \"isdir\": info[\"type\"] == \"directory\",\n                \"isexec\": info.get(\"isexec\", False),\n            }\n\n    return ret\n", "path": "dvc/repo/ls.py"}]}