Repository: iterative/dvc

Issue:
Plot: link to docs
Link to docs as per @jorgeorpinel review under https://github.com/iterative/dvc/pull/3577, after plot documentation is merged from https://github.com/iterative/dvc.org/pull/1186.

Discussion:

Patch:
diff --git a/dvc/command/plots.py b/dvc/command/plots.py
index c5aa6e424..babacec8a 100644
--- a/dvc/command/plots.py
+++ b/dvc/command/plots.py
@@ -4,6 +4,7 @@ import os
 
 from dvc.command.base import CmdBase, append_doc_link, fix_subparsers
 from dvc.exceptions import DvcException
+from dvc.utils import format_link
 
 logger = logging.getLogger(__name__)
 
@@ -114,7 +115,12 @@ def add_parser(subparsers, parent_parser):
         "--template",
         nargs="?",
         default=None,
-        help="File to be injected with data.",
+        help=(
+            "Special JSON or HTML schema file to inject with the data. "
+            "See {}".format(
+                format_link("https://man.dvc.org/plots#plot-templates")
+            )
+        ),
     )
     plots_show_parser.add_argument(
         "-o", "--out", default=None, help="Destination path to save plots to.",
@@ -167,7 +173,12 @@ def add_parser(subparsers, parent_parser):
         "--template",
         nargs="?",
         default=None,
-        help="File to be injected with data.",
+        help=(
+            "Special JSON or HTML schema to inject with the data. "
+            "See {}".format(
+                format_link("https://man.dvc.org/plots#plot-templates")
+            )
+        ),
     )
     plots_diff_parser.add_argument(
         "--targets",
diff --git a/dvc/output/base.py b/dvc/output/base.py
index fef7e592c..edaf3bdce 100644
--- a/dvc/output/base.py
+++ b/dvc/output/base.py
@@ -32,8 +32,8 @@ class OutputIsNotFileOrDirError(DvcException):
 class OutputAlreadyTrackedError(DvcException):
     def __init__(self, path):
         msg = f""" output '{path}' is already tracked by SCM (e.g. Git).
-    You can remove it from git, then add to DVC.
-        To stop tracking from git:
+    You can remove it from Git, then add to DVC.
+        To stop tracking from Git:
             git rm -r --cached '{path}'
             git commit -m "stop tracking {path}" """
         super().__init__(msg)
diff --git a/dvc/stage/cache.py b/dvc/stage/cache.py
index 7a9bd216b..63c326fab 100644
--- a/dvc/stage/cache.py
+++ b/dvc/stage/cache.py
@@ -80,12 +80,26 @@ class StageCache:
     def _create_stage(self, cache):
         from dvc.stage import create_stage, PipelineStage
 
+        params = []
+        for param in cache.get("params", []):
+            if isinstance(param, str):
+                params.append(param)
+                continue
+
+            assert isinstance(param, dict)
+            assert len(param) == 1
+            path = list(param.keys())[0]
+            params_list = param[path]
+            assert isinstance(params_list, list)
+            params.append(f"{path}:" + ",".join(params_list))
+
         stage = create_stage(
             PipelineStage,
             repo=self.repo,
             path="dvc.yaml",
             cmd=cache["cmd"],
-            deps=[dep["path"] for dep in cache["deps"]],
+            params=params,
+            deps=[dep["path"] for dep in cache.get("deps", [])],
             outs=[out["path"] for out in cache["outs"]],
         )
         StageLoader.fill_from_lock(stage, cache)


Test patch:
diff --git a/tests/unit/stage/test_cache.py b/tests/unit/stage/test_cache.py
new file mode 100644
index 000000000..7b923c630
--- /dev/null
+++ b/tests/unit/stage/test_cache.py
@@ -0,0 +1,106 @@
+import os
+
+
+def test_stage_cache(tmp_dir, dvc, run_copy, mocker):
+    tmp_dir.gen("dep", "dep")
+    tmp_dir.gen(
+        "script.py",
+        (
+            'open("out", "w+").write("out"); '
+            'open("out_no_cache", "w+").write("out_no_cache")'
+        ),
+    )
+    stage = dvc.run(
+        cmd="python script.py",
+        deps=["script.py", "dep"],
+        outs=["out"],
+        outs_no_cache=["out_no_cache"],
+        single_stage=True,
+    )
+
+    with dvc.lock, dvc.state:
+        stage.remove(remove_outs=True, force=True)
+
+    assert not (tmp_dir / "out").exists()
+    assert not (tmp_dir / "out_no_cache").exists()
+    assert not (tmp_dir / "out.dvc").exists()
+
+    cache_dir = os.path.join(
+        dvc.stage_cache.cache_dir,
+        "10",
+        "10b45372fdf4ec14d3f779c5b256378d7a12780e4c7f549a44138e492f098bfe",
+    )
+    cache_file = os.path.join(
+        cache_dir,
+        "bb32e04c6da96a7192513390acedbe4cd6123f8fe5b0ba5fffe39716fe87f6f4",
+    )
+
+    assert os.path.isdir(cache_dir)
+    assert os.listdir(cache_dir) == [os.path.basename(cache_file)]
+    assert os.path.isfile(cache_file)
+
+    run_spy = mocker.patch("dvc.stage.run.cmd_run")
+    checkout_spy = mocker.spy(stage, "checkout")
+    with dvc.lock, dvc.state:
+        stage.run()
+
+    assert not run_spy.called
+    assert checkout_spy.call_count == 1
+
+    assert (tmp_dir / "out").exists()
+    assert (tmp_dir / "out_no_cache").exists()
+    assert (tmp_dir / "out").read_text() == "out"
+    assert (tmp_dir / "out_no_cache").read_text() == "out_no_cache"
+
+
+def test_stage_cache_params(tmp_dir, dvc, run_copy, mocker):
+    tmp_dir.gen("params.yaml", "foo: 1\nbar: 2")
+    tmp_dir.gen("myparams.yaml", "baz: 3\nqux: 4")
+    tmp_dir.gen(
+        "script.py",
+        (
+            'open("out", "w+").write("out"); '
+            'open("out_no_cache", "w+").write("out_no_cache")'
+        ),
+    )
+    stage = dvc.run(
+        cmd="python script.py",
+        params=["foo,bar", "myparams.yaml:baz,qux"],
+        outs=["out"],
+        outs_no_cache=["out_no_cache"],
+        single_stage=True,
+    )
+
+    with dvc.lock, dvc.state:
+        stage.remove(remove_outs=True, force=True)
+
+    assert not (tmp_dir / "out").exists()
+    assert not (tmp_dir / "out_no_cache").exists()
+    assert not (tmp_dir / "out.dvc").exists()
+
+    cache_dir = os.path.join(
+        dvc.stage_cache.cache_dir,
+        "65",
+        "651d0a5b82e05e48b03acf44954f6a8599760e652a143d517a17d1065eca61a1",
+    )
+    cache_file = os.path.join(
+        cache_dir,
+        "2196a5a4dd24c5759437511fcf9d6aa66b259e1dac58e3f212aefd1797a6f114",
+    )
+
+    assert os.path.isdir(cache_dir)
+    assert os.listdir(cache_dir) == [os.path.basename(cache_file)]
+    assert os.path.isfile(cache_file)
+
+    run_spy = mocker.patch("dvc.stage.run.cmd_run")
+    checkout_spy = mocker.spy(stage, "checkout")
+    with dvc.lock, dvc.state:
+        stage.run()
+
+    assert not run_spy.called
+    assert checkout_spy.call_count == 1
+
+    assert (tmp_dir / "out").exists()
+    assert (tmp_dir / "out_no_cache").exists()
+    assert (tmp_dir / "out").read_text() == "out"
+    assert (tmp_dir / "out_no_cache").read_text() == "out_no_cache"
diff --git a/tests/unit/stage/test_stage.py b/tests/unit/stage/test_stage.py
index 05e308aed..1419db418 100644
--- a/tests/unit/stage/test_stage.py
+++ b/tests/unit/stage/test_stage.py
@@ -102,55 +102,3 @@ def test_always_changed(dvc):
     with dvc.lock:
         assert stage.changed()
         assert stage.status()["path"] == ["always changed"]
-
-
-def test_stage_cache(tmp_dir, dvc, run_copy, mocker):
-    tmp_dir.gen("dep", "dep")
-    tmp_dir.gen(
-        "script.py",
-        (
-            'open("out", "w+").write("out"); '
-            'open("out_no_cache", "w+").write("out_no_cache")'
-        ),
-    )
-    stage = dvc.run(
-        cmd="python script.py",
-        deps=["script.py", "dep"],
-        outs=["out"],
-        outs_no_cache=["out_no_cache"],
-        single_stage=True,
-    )
-
-    with dvc.lock, dvc.state:
-        stage.remove(remove_outs=True, force=True)
-
-    assert not (tmp_dir / "out").exists()
-    assert not (tmp_dir / "out_no_cache").exists()
-    assert not (tmp_dir / "out.dvc").exists()
-
-    cache_dir = os.path.join(
-        dvc.stage_cache.cache_dir,
-        "10",
-        "10b45372fdf4ec14d3f779c5b256378d7a12780e4c7f549a44138e492f098bfe",
-    )
-    cache_file = os.path.join(
-        cache_dir,
-        "bb32e04c6da96a7192513390acedbe4cd6123f8fe5b0ba5fffe39716fe87f6f4",
-    )
-
-    assert os.path.isdir(cache_dir)
-    assert os.listdir(cache_dir) == [os.path.basename(cache_file)]
-    assert os.path.isfile(cache_file)
-
-    run_spy = mocker.patch("dvc.stage.run.cmd_run")
-    checkout_spy = mocker.spy(stage, "checkout")
-    with dvc.lock, dvc.state:
-        stage.run()
-
-    assert not run_spy.called
-    assert checkout_spy.call_count == 1
-
-    assert (tmp_dir / "out").exists()
-    assert (tmp_dir / "out_no_cache").exists()
-    assert (tmp_dir / "out").read_text() == "out"
-    assert (tmp_dir / "out_no_cache").read_text() == "out_no_cache"