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) 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'