Repository: iterative/dvc

Issue:
Revisit dvc target from config in relation to phony stage files



Discussion:

Resolving patch:
diff --git a/dvc/command/repro.py b/dvc/command/repro.py
--- a/dvc/command/repro.py
+++ b/dvc/command/repro.py
@@ -1,8 +1,16 @@
+from dvc.exceptions import DvcException
 from dvc.command.common.base import CmdBase
 
 class CmdRepro(CmdBase):
     def run(self):
         recursive = not self.args.single_item
-        self.project.reproduce(self.args.targets,
-                               recursive=recursive,
-                               force=self.args.force)
+        for target in self.args.targets:
+            try:
+                self.project.reproduce(target,
+                                       recursive=recursive,
+                                       force=self.args.force)
+            except DvcException as ex:
+                msg = 'Failed to reproduce {}: {}'.format(target, str(ex))
+                self.project.logger.error(msg)
+                return 1
+        return 0
diff --git a/dvc/project.py b/dvc/project.py
--- a/dvc/project.py
+++ b/dvc/project.py
@@ -22,7 +22,9 @@ class StageNotInPipelineError(PipelineError):
 
 
 class StageNotFoundError(DvcException):
-    pass
+    def __init__(self, path):
+        msg = 'Stage file {} does not exist'.format(path)
+        super(StageNotFoundError, self).__init__(msg)
 
 
 class Pipeline(object):
@@ -161,23 +163,22 @@ def run(self,
         stage.dump()
         return stage
 
-    def reproduce(self, targets, recursive=True, force=False):
+    def reproduce(self, target, recursive=True, force=False):
         reproduced = []
         stages = nx.get_node_attributes(self.graph(), 'stage')
-        for target in targets:
-            node = os.path.relpath(os.path.abspath(target), self.root_dir)
-            if node not in stages:
-                raise StageNotFoundError(target)
-
-            if recursive:
-                for n in nx.dfs_postorder_nodes(self.graph(), node):
-                    stages[n].reproduce(force=force)
-                    stages[n].dump()
-                    reproduced.append(stages[n])
-
-            stages[node].reproduce(force=force)
-            stages[node].dump()
-            reproduced.append(stages[node])
+        node = os.path.relpath(os.path.abspath(target), self.root_dir)
+        if node not in stages:
+            raise StageNotFoundError(target)
+
+        if recursive:
+            for n in nx.dfs_postorder_nodes(self.graph(), node):
+                stages[n].reproduce(force=force)
+                stages[n].dump()
+                reproduced.append(stages[n])
+
+        stages[node].reproduce(force=force)
+        stages[node].dump()
+        reproduced.append(stages[node])
 
         return reproduced
 


Test patch:
diff --git a/tests/test_repro.py b/tests/test_repro.py
--- a/tests/test_repro.py
+++ b/tests/test_repro.py
@@ -25,7 +25,7 @@ def test(self):
         with open(self.CODE, 'a') as code:
             code.write("\nshutil.copyfile('{}', sys.argv[2])\n".format(self.BAR))
 
-        self.dvc.reproduce([self.file1_stage])
+        self.dvc.reproduce(self.file1_stage)
 
         self.assertTrue(filecmp.cmp(self.file1, self.BAR))
 
@@ -34,7 +34,7 @@ class TestReproChangedData(TestRepro):
     def test(self):
         self.swap_foo_with_bar()
 
-        self.dvc.reproduce([self.file1_stage])
+        self.dvc.reproduce(self.file1_stage)
 
         self.assertTrue(filecmp.cmp(self.file1, self.BAR))
 
@@ -55,7 +55,18 @@ def test(self):
 
         self.swap_foo_with_bar()
 
-        self.dvc.reproduce([file2_stage])
+        self.dvc.reproduce(file2_stage)
 
         self.assertTrue(filecmp.cmp(self.file1, self.BAR))
         self.assertTrue(filecmp.cmp(file2, self.BAR))
+
+
+class TestReproPhony(TestReproChangedData):
+    def test(self):
+        stage = self.dvc.run(deps=[self.file1])
+
+        self.swap_foo_with_bar()
+
+        self.dvc.reproduce(stage.path)
+
+        self.assertTrue(filecmp.cmp(self.file1, self.BAR))