diff --git a/dvc/command/pipeline.py b/dvc/command/pipeline.py index 401ef4dce..f6b33e9b2 100644 --- a/dvc/command/pipeline.py +++ b/dvc/command/pipeline.py @@ -71,23 +71,47 @@ class CmdPipelineShow(CmdBase): else: edges.append((from_stage.relpath, to_stage.relpath)) - return nodes, edges + return nodes, edges, networkx.is_tree(G) def _show_ascii(self, target, commands, outs): from dvc.dagascii import draw - nodes, edges = self.__build_graph(target, commands, outs) + nodes, edges, _ = self.__build_graph(target, commands, outs) if not nodes: return draw(nodes, edges) + def _show_dependencies_tree(self, target, commands, outs): + from treelib import Tree + + nodes, edges, is_tree = self.__build_graph(target, commands, outs) + if not nodes: + return + if not is_tree: + raise DvcException( + "DAG is not a tree, can not print it in tree-structure way, " + "please use --ascii instead" + ) + + tree = Tree() + tree.create_node(target, target) # Root node + observe_list = [target] + while len(observe_list) > 0: + current_root = observe_list[0] + for edge in edges: + if edge[0] == current_root: + tree.create_node(edge[1], edge[1], parent=current_root) + observe_list.append(edge[1]) + observe_list.pop(0) + tree.show() + def __write_dot(self, target, commands, outs, filename): import networkx from networkx.drawing.nx_pydot import write_dot - _, edges = self.__build_graph(target, commands, outs) + _, edges, _ = self.__build_graph(target, commands, outs) edges = [edge[::-1] for edge in edges] simple_g = networkx.DiGraph() @@ -111,6 +135,10 @@ class CmdPipelineShow(CmdBase): self.args.outs, self.args.dot, ) + elif self.args.tree: + self._show_dependencies_tree( + target, self.args.commands, self.args.outs + ) else: self._show(target, self.args.commands, self.args.outs) except DvcException: @@ -185,6 +213,12 @@ def add_parser(subparsers, parent_parser): pipeline_show_parser.add_argument( "--dot", help="Write DAG in .dot format." ) + pipeline_show_parser.add_argument( + "--tree", + action="store_true", + default=False, + help="Output DAG as Dependencies Tree.", + ) pipeline_show_parser.add_argument( "targets", nargs="*", help="DVC files. 'Dvcfile' by default." ) diff --git a/requirements.txt b/requirements.txt index 0eb848d45..80c639d86 100644 --- a/requirements.txt +++ b/requirements.txt @@ -25,3 +25,4 @@ pydot>=1.2.4 asciimatics>=1.10.0 distro>=1.3.0 appdirs>=1.4.3 +treelib>=1.5.5 diff --git a/setup.py b/setup.py index ce656dabd..07fe236ba 100644 --- a/setup.py +++ b/setup.py @@ -22,6 +22,7 @@ install_requires = [ "asciimatics>=1.10.0", "distro>=1.3.0", "appdirs>=1.4.3", + "treelib>=1.5.5", ] # Extra dependencies for remote integrations diff --git a/tests/test_pipeline.py b/tests/test_pipeline.py index f59e6afed..0bb531454 100644 --- a/tests/test_pipeline.py +++ b/tests/test_pipeline.py @@ -34,6 +34,10 @@ class TestPipelineShowSingle(TestDvc): self.assertEqual(ret, 0) self.assertTrue(os.path.isfile(self.dotFile)) + def test_tree(self): + ret = main(["pipeline", "show", "--tree", self.stage]) + self.assertEqual(ret, 0) + def test_ascii_commands(self): ret = main(["pipeline", "show", "--ascii", self.stage, "--commands"]) self.assertEqual(ret, 0)