Repository: iterative/dvc

Issue:
repro: CLI option to reproduce all pipelines
https://github.com/iterative/dvc/issues/1107#issuecomment-424420783

Discussion:

Patch:
diff --git a/dvc/cli.py b/dvc/cli.py
index 06c76809e..71ee097c5 100644
--- a/dvc/cli.py
+++ b/dvc/cli.py
@@ -439,6 +439,12 @@ def parse_args(argv=None):
                         default=False,
                         help='Reproduce the whole pipeline that the '
                              'specified stage file belongs to.')
+    repro_parser.add_argument(
+                        '-P',
+                        '--all-pipelines',
+                        action='store_true',
+                        default=False,
+                        help='Reproduce all pipelines in the project.')
     repro_parser.set_defaults(func=CmdRepro)
 
     # Remove
@@ -838,7 +844,9 @@ def parse_args(argv=None):
     if (issubclass(args.func, CmdRepro)
         or issubclass(args.func, CmdPipelineShow)) \
        and hasattr(args, 'targets') \
-       and len(args.targets) == 0:
+       and len(args.targets) == 0 \
+       and not (hasattr(args, 'all_pipelines')
+                and args.all_pipelines):
         if hasattr(args, 'cwd'):
             cwd = args.cwd
         else:
diff --git a/dvc/command/repro.py b/dvc/command/repro.py
index 7efa6c5ef..1d1f9c42d 100644
--- a/dvc/command/repro.py
+++ b/dvc/command/repro.py
@@ -21,7 +21,8 @@ class CmdRepro(CmdBase):
                                        force=self.args.force,
                                        dry=self.args.dry,
                                        interactive=self.args.interactive,
-                                       pipeline=self.args.pipeline)
+                                       pipeline=self.args.pipeline,
+                                       all_pipelines=self.args.all_pipelines)
 
                 if len(stages) == 0:
                     self.project.logger.info(CmdDataStatus.UP_TO_DATE_MSG)
diff --git a/dvc/project.py b/dvc/project.py
index e1fbb2f0c..91d92fca2 100644
--- a/dvc/project.py
+++ b/dvc/project.py
@@ -306,12 +306,16 @@ class Project(object):
         return [stage]
 
     def reproduce(self,
-                  target,
+                  target=None,
                   recursive=True,
                   force=False,
                   dry=False,
                   interactive=False,
-                  pipeline=False):
+                  pipeline=False,
+                  all_pipelines=False):
+
+        if target is None and not all_pipelines:
+            raise ValueError()
 
         if not interactive:
             config = self.config
@@ -319,13 +323,18 @@ class Project(object):
             interactive = core.get(config.SECTION_CORE_INTERACTIVE, False)
 
         targets = []
-        if pipeline:
-            stage = Stage.load(self, target)
-            node = os.path.relpath(stage.path, self.root_dir)
-            G = self._get_pipeline(node)
-            for node in G.nodes():
-                if G.in_degree(node) == 0:
-                    targets.append(os.path.join(self.root_dir, node))
+        if pipeline or all_pipelines:
+            if pipeline:
+                stage = Stage.load(self, target)
+                node = os.path.relpath(stage.path, self.root_dir)
+                pipelines = [self._get_pipeline(node)]
+            else:
+                pipelines = self.pipelines()
+
+            for G in pipelines:
+                for node in G.nodes():
+                    if G.in_degree(node) == 0:
+                        targets.append(os.path.join(self.root_dir, node))
         else:
             targets.append(target)
 


Test patch:
diff --git a/tests/test_repro.py b/tests/test_repro.py
index 75d2e026c..bfe14b9fd 100644
--- a/tests/test_repro.py
+++ b/tests/test_repro.py
@@ -217,6 +217,49 @@ class TestReproPipeline(TestReproChangedDeepData):
         self.assertEqual(ret, 0)
 
 
+class TestReproPipelines(TestDvc):
+    def setUp(self):
+        super(TestReproPipelines, self).setUp()
+
+        stages = self.dvc.add(self.FOO)
+        self.assertEqual(len(stages), 1)
+        self.foo_stage = stages[0]
+        self.assertTrue(self.foo_stage is not None)
+
+        stages = self.dvc.add(self.BAR)
+        self.assertEqual(len(stages), 1)
+        self.bar_stage = stages[0]
+        self.assertTrue(self.bar_stage is not None)
+
+        self.file1 = 'file1'
+        self.file1_stage = self.file1 + '.dvc'
+        self.dvc.run(fname=self.file1_stage,
+                     outs=[self.file1],
+                     deps=[self.FOO, self.CODE],
+                     cmd='python {} {} {}'.format(self.CODE, self.FOO, self.file1))
+
+        self.file2 = 'file2'
+        self.file2_stage = self.file2 + '.dvc'
+        self.dvc.run(fname=self.file2_stage,
+                     outs=[self.file2],
+                     deps=[self.BAR, self.CODE],
+                     cmd='python {} {} {}'.format(self.CODE, self.BAR, self.file2))
+
+
+    def test(self):
+        stages = self.dvc.reproduce(all_pipelines=True, force=True)
+        self.assertEqual(len(stages), 4)
+        names = [stage.relpath for stage in stages]
+        self.assertTrue(self.foo_stage.relpath in names)
+        self.assertTrue(self.bar_stage.relpath in names)
+        self.assertTrue(self.file1_stage in names)
+        self.assertTrue(self.file2_stage in names)
+
+    def test_cli(self):
+        ret = main(['repro', '-f', '-P'])
+        self.assertEqual(ret, 0)
+
+
 class TestReproLocked(TestReproChangedData):
     def test(self):
         file2 = 'file2'