Repository: iterative/dvc

Issue:
address `dvc list` review from Jorge
Click https://github.com/iterative/dvc/pull/3246#pullrequestreview-362991972


Comments:
Awesome! Please also check the latest state of the changes in iterative/dvc.org/pull/967 so both sides are in sync when this is addressed 🙂 
Also, should the file name be `list.py`? its `ls.py` now.
And not sure about the `target` name for the last param... Why not `path` like in `dvc get/import`?
UPDATE: Investigating in https://github.com/iterative/dvc.org/pull/1021#pullrequestreview-368410823
Another question here: is there an easy way to tell which files in the list are tracked by DVC and which ones by Git? This is kind of important for finding paths that can be sent to `dvc import/get`.
@jorgeorpinel  At this moment only coloring is supported as a way of the differentiating types of the files.
You may read more in [the comment](https://github.com/iterative/dvc/issues/3431#issuecomment-593560766)
@JIoJIaJIu I think that link is just somewhat relevant. We have two things to colorize in a different way:

1. DVC tracked files - e.g. we need to use a different color for a directory we did `dvc add data\` with. Or the same for `dvc add data\data.xml`. And we should have a flag (--outs?) to filter DVC "outputs" only.
2. DVC-files - we can use a color scheme for links, for example (since they serve to some extent as a link)
3. Git-traked .dvcignored files.

That link it probably relevant to 3 only, but most likely @jorgeorpinel was asking about 1.


> That link it probably relevant to 3 only

In the comment I provided info about **1** and **2** scenarios, but the main issue relates to **3**, right
Let me summarize it here then

We color output as `out=color`
```
LS_COLORS="out=01;37" dvc list ...
```
will color outs only
OK thanks for the info guys, that's great to know. But since colorizing is not enabled by default (I assume) nor supported in every system, would it make sense to add another way to tell them apart? It can be as simple as a 2nd column in the output that has either `git` or `dvc`, or maybe only `out` for DVC outputs (stuff that you can `dvc import/get`). After all this was one of the main motivations for `dvc list` in the first place.
@jorgeorpinel what is the motivation for you to distinguish DVC-tracked and Git-tracked files by default? The whole point is to make the whole experience with get/import/list the same for all files as much as possible.
True, I forgot you can get/import Git-tracked files now!

Patch:
diff --git a/dvc/command/ls/__init__.py b/dvc/command/ls/__init__.py
--- a/dvc/command/ls/__init__.py
+++ b/dvc/command/ls/__init__.py
@@ -30,7 +30,7 @@ def run(self):
         try:
             entries = Repo.ls(
                 self.args.url,
-                self.args.target,
+                self.args.path,
                 rev=self.args.rev,
                 recursive=self.args.recursive,
                 outs_only=self.args.outs_only,
@@ -45,7 +45,10 @@ def run(self):
 
 
 def add_parser(subparsers, parent_parser):
-    LIST_HELP = "List files and DVC outputs in the repo."
+    LIST_HELP = (
+        "List repository contents, including files"
+        " and directories tracked by DVC and by Git."
+    )
     list_parser = subparsers.add_parser(
         "list",
         parents=[parent_parser],
@@ -53,16 +56,7 @@ def add_parser(subparsers, parent_parser):
         help=LIST_HELP,
         formatter_class=argparse.RawTextHelpFormatter,
     )
-    list_parser.add_argument(
-        "url",
-        help="Supported urls:\n"
-        "/path/to/file\n"
-        "/path/to/directory\n"
-        "C:\\\\path\\to\\file\n"
-        "C:\\\\path\\to\\directory\n"
-        "https://github.com/path/to/repo\n"
-        "git@github.com:path/to/repo.git\n",
-    )
+    list_parser.add_argument("url", help="Location of DVC repository to list")
     list_parser.add_argument(
         "-R",
         "--recursive",
@@ -76,7 +70,7 @@ def add_parser(subparsers, parent_parser):
         "--rev", nargs="?", help="Git revision (e.g. branch, tag, SHA)"
     )
     list_parser.add_argument(
-        "target",
+        "path",
         nargs="?",
         help="Path to directory within the repository to list outputs for",
     )
diff --git a/dvc/exceptions.py b/dvc/exceptions.py
--- a/dvc/exceptions.py
+++ b/dvc/exceptions.py
@@ -293,11 +293,11 @@ def __init__(self, code, reason):
 class PathMissingError(DvcException):
     default_msg = (
         "The path '{}' does not exist in the target repository '{}'"
-        " neither as an output nor a git-handled file."
+        " neither as a DVC output nor as a Git-tracked file."
     )
     default_msg_output_only = (
         "The path '{}' does not exist in the target repository '{}'"
-        " as an output."
+        " as an DVC output."
     )
 
     def __init__(self, path, repo, output_only=False):
diff --git a/dvc/repo/ls.py b/dvc/repo/ls.py
--- a/dvc/repo/ls.py
+++ b/dvc/repo/ls.py
@@ -6,13 +6,13 @@
 
 @staticmethod
 def ls(
-    url, target=None, rev=None, recursive=None, outs_only=False,
+    url, path=None, rev=None, recursive=None, outs_only=False,
 ):
     """Methods for getting files and outputs for the repo.
 
     Args:
         url (str): the repo url
-        target (str, optional): relative path into the repo
+        path (str, optional): relative path into the repo
         rev (str, optional): SHA commit, branch or tag name
         recursive (bool, optional): recursively walk the repo
         outs_only (bool, optional): show only DVC-artifacts
@@ -34,25 +34,25 @@ def ls(
     from dvc.utils import relpath
 
     with external_repo(url, rev) as repo:
-        target_path_info = _get_target_path_info(repo, target)
+        path_info = _get_path_info(repo, path)
         fs_nodes = []
         if isinstance(repo, Repo):
-            fs_nodes.extend(_ls_outs_repo(repo, target_path_info, recursive))
+            fs_nodes.extend(_ls_outs_repo(repo, path_info, recursive))
 
         if not outs_only:
-            fs_nodes.extend(_ls_files_repo(target_path_info, recursive))
+            fs_nodes.extend(_ls_files_repo(path_info, recursive))
 
-        if target and not fs_nodes:
-            raise PathMissingError(target, repo, output_only=outs_only)
+        if path and not fs_nodes:
+            raise PathMissingError(path, repo, output_only=outs_only)
 
         fs_nodes = {n["path_info"]: n for n in fs_nodes}.values()
 
         def get_entry(fs_node):
-            path_info = fs_node["path_info"]
+            node_path_info = fs_node["path_info"]
             path = (
-                path_info.name
-                if path_info == target_path_info
-                else relpath(path_info, target_path_info)
+                node_path_info.name
+                if node_path_info == path_info
+                else relpath(node_path_info, path_info)
             )
             return {
                 "path": path,
@@ -123,12 +123,12 @@ def get_first_segment(out):
     ]
 
 
-def _get_target_path_info(repo, target=None):
+def _get_path_info(repo, path=None):
     from dvc.path_info import PathInfo
 
-    if not target:
+    if not path:
         return PathInfo(repo.root_dir)
-    return PathInfo(repo.root_dir, target)
+    return PathInfo(repo.root_dir, path)
 
 
 def _get_fs_node(path_info, out=None):


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
@@ -135,11 +135,11 @@ def test_ls_repo_outs_only_recursive(tmp_dir, dvc, scm):
     )
 
 
-def test_ls_repo_with_target_dir(tmp_dir, dvc, scm):
+def test_ls_repo_with_path_dir(tmp_dir, dvc, scm):
     tmp_dir.scm_gen(FS_STRUCTURE, commit="init")
     tmp_dir.dvc_gen(DVC_STRUCTURE, commit="dvc")
 
-    files = Repo.ls(fspath(tmp_dir), target="model")
+    files = Repo.ls(fspath(tmp_dir), path="model")
     match_files(
         files,
         (
@@ -151,21 +151,21 @@ def test_ls_repo_with_target_dir(tmp_dir, dvc, scm):
     )
 
 
-def test_ls_repo_with_target_dir_outs_only_empty(tmp_dir, dvc, scm):
+def test_ls_repo_with_path_dir_outs_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")
 
     with pytest.raises(PathMissingError):
-        Repo.ls(fspath(tmp_dir), target="folder", outs_only=True)
+        Repo.ls(fspath(tmp_dir), path="folder", outs_only=True)
 
 
-def test_ls_repo_with_target_subdir(tmp_dir, dvc, scm):
+def test_ls_repo_with_path_subdir(tmp_dir, dvc, scm):
     tmp_dir.scm_gen(FS_STRUCTURE, commit="init")
     tmp_dir.dvc_gen(DVC_STRUCTURE, commit="dvc")
 
-    target = os.path.join("data", "subcontent")
-    files = Repo.ls(fspath(tmp_dir), target)
+    path = os.path.join("data", "subcontent")
+    files = Repo.ls(fspath(tmp_dir), path)
     match_files(
         files,
         (
@@ -176,61 +176,61 @@ def test_ls_repo_with_target_subdir(tmp_dir, dvc, scm):
     )
 
 
-def test_ls_repo_with_target_subdir_outs_only(tmp_dir, dvc, scm):
+def test_ls_repo_with_path_subdir_outs_only(tmp_dir, dvc, scm):
     tmp_dir.scm_gen(FS_STRUCTURE, commit="init")
     tmp_dir.dvc_gen(DVC_STRUCTURE, commit="dvc")
 
-    target = os.path.join("data", "subcontent")
-    files = Repo.ls(fspath(tmp_dir), target, outs_only=True)
+    path = os.path.join("data", "subcontent")
+    files = Repo.ls(fspath(tmp_dir), path, outs_only=True)
     match_files(files, ((("data.xml",), True), (("statistics",), False),))
 
 
-def test_ls_repo_with_target_subdir_outs_only_recursive(tmp_dir, dvc, scm):
+def test_ls_repo_with_path_subdir_outs_only_recursive(tmp_dir, dvc, scm):
     tmp_dir.scm_gen(FS_STRUCTURE, commit="init")
     tmp_dir.dvc_gen(DVC_STRUCTURE, commit="dvc")
 
-    target = os.path.join("data", "subcontent")
-    files = Repo.ls(fspath(tmp_dir), target, outs_only=True, recursive=True)
+    path = os.path.join("data", "subcontent")
+    files = Repo.ls(fspath(tmp_dir), path, outs_only=True, recursive=True)
     match_files(
         files, ((("data.xml",), True), (("statistics", "data.csv"), True),)
     )
 
 
-def test_ls_repo_with_target_file_out(tmp_dir, dvc, scm):
+def test_ls_repo_with_path_file_out(tmp_dir, dvc, scm):
     tmp_dir.scm_gen(FS_STRUCTURE, commit="init")
     tmp_dir.dvc_gen(DVC_STRUCTURE, commit="dvc")
 
-    target = os.path.join("data", "subcontent", "data.xml")
-    files = Repo.ls(fspath(tmp_dir), target)
+    path = os.path.join("data", "subcontent", "data.xml")
+    files = Repo.ls(fspath(tmp_dir), path)
     match_files(files, ((("data.xml",), True),))
 
 
-def test_ls_repo_with_file_target_fs(tmp_dir, dvc, scm):
+def test_ls_repo_with_file_path_fs(tmp_dir, dvc, scm):
     tmp_dir.scm_gen(FS_STRUCTURE, commit="init")
     tmp_dir.dvc_gen(DVC_STRUCTURE, commit="dvc")
 
-    target = "README.md"
-    files = Repo.ls(fspath(tmp_dir), target, recursive=True)
+    path = "README.md"
+    files = Repo.ls(fspath(tmp_dir), path, recursive=True)
     match_files(files, ((("README.md",), False),))
 
 
-def test_ls_repo_with_missed_target(tmp_dir, dvc, scm):
+def test_ls_repo_with_missed_path(tmp_dir, dvc, scm):
     tmp_dir.scm_gen(FS_STRUCTURE, commit="init")
     tmp_dir.dvc_gen(DVC_STRUCTURE, commit="dvc")
 
     with pytest.raises(PathMissingError) as exc_info:
-        Repo.ls(fspath(tmp_dir), target="missed_target")
+        Repo.ls(fspath(tmp_dir), path="missed_path")
     assert not exc_info.value.output_only
 
 
-def test_ls_repo_with_missed_target_outs_only(tmp_dir, dvc, scm):
+def test_ls_repo_with_missed_path_outs_only(tmp_dir, dvc, scm):
     tmp_dir.scm_gen(FS_STRUCTURE, commit="init")
     tmp_dir.dvc_gen(DVC_STRUCTURE, commit="dvc")
 
     with pytest.raises(PathMissingError) as exc_info:
         Repo.ls(
             fspath(tmp_dir),
-            target="missed_target",
+            path="missed_path",
             recursive=True,
             outs_only=True,
         )
@@ -269,19 +269,19 @@ def test_ls_repo_with_removed_dvc_dir_recursive(tmp_dir, dvc, scm):
     )
 
 
-def test_ls_repo_with_removed_dvc_dir_with_target_dir(tmp_dir, dvc, scm):
+def test_ls_repo_with_removed_dvc_dir_with_path_dir(tmp_dir, dvc, scm):
     create_dvc_pipeline(tmp_dir, dvc)
 
-    target = "out"
-    files = Repo.ls(fspath(tmp_dir), target)
+    path = "out"
+    files = Repo.ls(fspath(tmp_dir), path)
     match_files(files, ((("file",), True),))
 
 
-def test_ls_repo_with_removed_dvc_dir_with_target_file(tmp_dir, dvc, scm):
+def test_ls_repo_with_removed_dvc_dir_with_path_file(tmp_dir, dvc, scm):
     create_dvc_pipeline(tmp_dir, dvc)
 
-    target = os.path.join("out", "file")
-    files = Repo.ls(fspath(tmp_dir), target)
+    path = os.path.join("out", "file")
+    files = Repo.ls(fspath(tmp_dir), path)
     match_files(files, ((("file",), True),))
 
 
@@ -348,14 +348,14 @@ def test_ls_remote_git_only_repo_recursive(git_dir):
     )
 
 
-def test_ls_remote_repo_with_target_dir(erepo_dir):
+def test_ls_remote_repo_with_path_dir(erepo_dir):
     with erepo_dir.chdir():
         erepo_dir.scm_gen(FS_STRUCTURE, commit="init")
         erepo_dir.dvc_gen(DVC_STRUCTURE, commit="dvc")
 
     url = "file://{}".format(erepo_dir)
-    target = "model"
-    files = Repo.ls(url, target)
+    path = "model"
+    files = Repo.ls(url, path)
     match_files(
         files,
         (