Repository: iterative/dvc

Issue:
Add command to present locked DVC stages
I wish that I could get a list of locked DVC stages in my DVC pipeline,
Something with a simular output as given by `grep -r 'locked: true'` *.dvc. See also https://discordapp.com/channels/485586884165107732/485596304961962003/565456202541301770

Possible DVC command could be:
* `dvc lock --list`
* `dvc pipeline show --locked`

I prefer the feature is added to the `dvc pipeline` command, because the current purpose of it is to present the pipeline, not edit it. Where as the `dvc lock` command edits the pipeline.

Aother posible solution is to present the locked flag by default with the `dvc pipeline` command, thus the current output
```
$ dvc pipeline show output.dvc
raw.dvc
data.dvc
output.dvc
```
could be changed to 
```
$ dvc pipeline show output.dvc
raw.dvc (locked)
data.dvc
output.dvc
```
if the `raw.dvc` stage is locked.

 


Discussion:
Working on this. Will post updates here. 

Expect a PR soon
@rpip Thank you very much!
Should this also display the lock files when printing commands and output files? If that's the case, then I think `dvc pipeline show --locked` is the best approach as it works nicely with the existing options.

Alternatively, we could indicate the lock status by default for `dvc pipeline show`.

@rpip Agreed, `dvc pipeline show` with an explicit `--locked` is a good way to start. We will be able to make it enabled by default later after that if we wish to. :slightly_smiling_face: 
@rpip, Hmm, could you give some detailed examples of the possible commands and their output? 
The tricky thing here is to keep the output easy to understand. A stage can be locked, but is it possible to lock a dependency or output? 
I would say no for dependencies, but as an output file (in DVC only) can com from one DVC stage, one could say it is indirectly locked by its stage.🤔
All clear now - thanks @PeterFogh.

For now, it'd be `dvc pipeline show` with an explicit `--locked` argument. 
This basically filters/displays only the locked stages:

```
$ dvc pipeline show output.dvc --locked
raw.dvc
```

Later, when `--locked` is enabled by default it would look like this as proposed in the ticket:

```
$ dvc pipeline show output.dvc
raw.dvc (locked)
data.dvc
output.dvc
```
We can also add some colors to the terminal output to highlight the differences more
@rpip, awesome plan and handling of the feature👍 

Resolving patch:
diff --git a/dvc/command/pipeline.py b/dvc/command/pipeline.py
--- a/dvc/command/pipeline.py
+++ b/dvc/command/pipeline.py
@@ -14,7 +14,7 @@
 
 
 class CmdPipelineShow(CmdBase):
-    def _show(self, target, commands, outs):
+    def _show(self, target, commands, outs, locked):
         import networkx
         from dvc.stage import Stage
 
@@ -22,8 +22,12 @@ def _show(self, target, commands, outs):
         G = self.repo.graph()[0]
         stages = networkx.get_node_attributes(G, "stage")
         node = os.path.relpath(stage.path, self.repo.root_dir)
+        nodes = networkx.dfs_postorder_nodes(G, node)
 
-        for n in networkx.dfs_postorder_nodes(G, node):
+        if locked:
+            nodes = [n for n in nodes if stages[n].locked]
+
+        for n in nodes:
             if commands:
                 logger.info(stages[n].cmd)
             elif outs:
@@ -122,7 +126,7 @@ def __write_dot(self, target, commands, outs, filename):
         simple_g.add_edges_from(edges)
         write_dot(simple_g, filename)
 
-    def run(self, unlock=False):
+    def run(self):
         if not self.args.targets:
             self.args.targets = self.default_targets
 
@@ -144,7 +148,12 @@ def run(self, unlock=False):
                         target, self.args.commands, self.args.outs
                     )
                 else:
-                    self._show(target, self.args.commands, self.args.outs)
+                    self._show(
+                        target,
+                        self.args.commands,
+                        self.args.outs,
+                        self.args.locked,
+                    )
             except DvcException:
                 msg = "failed to show pipeline for '{}'".format(target)
                 logger.exception(msg)
@@ -208,6 +217,13 @@ def add_parser(subparsers, parent_parser):
         default=False,
         help="Print output files instead of paths to DVC files.",
     )
+    pipeline_show_parser.add_argument(
+        "-l",
+        "--locked",
+        action="store_true",
+        default=False,
+        help="Print locked DVC stages",
+    )
     pipeline_show_parser.add_argument(
         "--ascii",
         action="store_true",


Test patch:
diff --git a/tests/test_pipeline.py b/tests/test_pipeline.py
--- a/tests/test_pipeline.py
+++ b/tests/test_pipeline.py
@@ -1,3 +1,5 @@
+import logging
+
 from dvc.main import main
 
 from tests.basic_env import TestDvc
@@ -150,6 +152,19 @@ def test_non_existing(self):
         ret = main(["pipeline", "show", "non-existing"])
         self.assertNotEqual(ret, 0)
 
+    def test_print_locked_stages(self):
+        self.dvc.add("foo")
+        self.dvc.add("bar")
+        self.dvc.lock_stage("foo.dvc")
+
+        self._caplog.clear()
+        with self._caplog.at_level(logging.INFO, logger="dvc"):
+            ret = main(["pipeline", "show", "foo.dvc", "--locked"])
+            self.assertEqual(ret, 0)
+
+        assert "foo.dvc" in self._caplog.text
+        assert "bar.dvc" not in self._caplog.text
+
 
 class TestPipelineShowDeep(TestReproChangedDeepData):
     def setUp(self):