diff --git a/dvc/command/pipeline.py b/dvc/command/pipeline.py index e6a012a0e..4b2893adc 100644 --- a/dvc/command/pipeline.py +++ b/dvc/command/pipeline.py @@ -39,34 +39,37 @@ class CmdPipelineShow(CmdBase): target_stage = Stage.load(self.repo, target) G = get_pipeline(self.repo.pipelines, target_stage) - nodes = [] - for stage in networkx.preorder_nodes(G, target_stage): + nodes = set() + for stage in networkx.dfs_preorder_nodes(G, target_stage): if commands: if stage.cmd is None: continue - nodes.append(stage.cmd) + nodes.add(stage.cmd) elif outs: for out in stage.outs: - nodes.append(str(out)) + nodes.add(str(out)) for dep in stage.deps: - nodes.append(str(dep)) + nodes.add(str(dep)) else: - nodes.append(stage.relpath) + nodes.add(stage.relpath) edges = [] - for from_stage, to_stage in networkx.dfs_edges(G, target_stage): - if commands: - if to_stage.cmd is None: - continue - edges.append((from_stage.cmd, to_stage.cmd)) - elif outs: - for from_dep in from_stage.deps: - for from_out in from_stage.outs: - edges.append((str(from_out), str(from_dep))) - else: - edges.append((from_stage.relpath, to_stage.relpath)) - return nodes, edges, networkx.is_tree(G) + if outs and not commands: + for stage in networkx.dfs_preorder_nodes(G, target_stage): + for dep in stage.deps: + for out in stage.outs: + edges.append((str(out), str(dep))) + else: + for from_stage, to_stage in networkx.dfs_edges(G, target_stage): + if commands: + if to_stage.cmd is None: + continue + edges.append((from_stage.cmd, to_stage.cmd)) + else: + edges.append((from_stage.relpath, to_stage.relpath)) + + return list(nodes), edges, networkx.is_tree(G) def _show_ascii(self, target, commands, outs): from dvc.dagascii import draw diff --git a/tests/func/test_pipeline.py b/tests/func/test_pipeline.py index d6420a417..c88406e57 100644 --- a/tests/func/test_pipeline.py +++ b/tests/func/test_pipeline.py @@ -1,6 +1,7 @@ import logging from dvc.main import main +from dvc.command.pipeline import CmdPipelineShow from tests.basic_env import TestDvc from tests.func.test_repro import TestRepro from tests.func.test_repro import TestReproChangedDeepData @@ -98,6 +99,29 @@ class TestPipelineShow(TestRepro): self.assertEqual(ret, 0) +def test_disconnected_stage(tmp_dir, dvc, caplog): + tmp_dir.dvc_gen({"base": "base"}) + + dvc.add("base") + dvc.run(deps=["base"], outs=["derived1"], cmd="echo derived1 > derived1") + dvc.run(deps=["base"], outs=["derived2"], cmd="echo derived2 > derived2") + final_stage = dvc.run( + deps=["derived1"], outs=["final"], cmd="echo final > final" + ) + + args = ["pipeline", "show", "--outs", "--dot", final_stage.path] + with tmp_dir.chdir(): + command = CmdPipelineShow(args) + # Need to test __build_graph directly + nodes, edges, is_tree = command._CmdPipelineShow__build_graph( + final_stage.path, commands=False, outs=True + ) + + assert set(nodes) == set(["final", "derived1", "base"]) + assert edges == [("final", "derived1"), ("derived1", "base")] + assert is_tree is True + + def test_print_locked_stages(tmp_dir, dvc, caplog): tmp_dir.dvc_gen({"foo": "foo content", "bar": "bar content"}) dvc.lock_stage("foo.dvc")