diff --git a/dvc/exceptions.py b/dvc/exceptions.py index 2cadd52bb..6895c67c1 100644 --- a/dvc/exceptions.py +++ b/dvc/exceptions.py @@ -31,6 +31,13 @@ class OutputDuplicationError(DvcException): super(OutputDuplicationError, self).__init__(msg.format(output, s)) +class CircularDependencyError(DvcException): + def __init__(self, dependency): + assert isinstance(dependency, str) + msg = "File '{}' is specified as an output and as a dependency." + super(CircularDependencyError, self).__init__(msg.format(dependency)) + + class MoveNotDataSourceError(DvcException): def __init__(self, path): msg = "Move is not permitted for stages that are not data sources. " \ diff --git a/dvc/project.py b/dvc/project.py index 0bb795044..39f9e2211 100644 --- a/dvc/project.py +++ b/dvc/project.py @@ -143,6 +143,14 @@ class Project(object): stages = [o.stage.relpath, out.stage.relpath] raise OutputDuplicationError(o.path, stages) + def _check_circular_dependency(self, deps, outs): + from dvc.exceptions import CircularDependencyError + + circular_dependencies = set(deps) & set(outs) + + raise CircularDependencyError(circular_dependencies.pop()) + + def add(self, fname, recursive=False): fnames = [] if recursive and os.path.isdir(fname): @@ -258,6 +266,7 @@ class Project(object): overwrite=overwrite) self._check_output_duplication(stage.outs) + self._check_circular_dependency(deps, outs) self._files_to_git_add = [] with self.state: diff --git a/tests/test_run.py b/tests/test_run.py index 32b908ee6..4c14940fd 100644 --- a/tests/test_run.py +++ b/tests/test_run.py @@ -5,7 +5,7 @@ from dvc.main import main from dvc.utils import file_md5 from dvc.stage import Stage, StageFileBadNameError, MissingDep from dvc.command.run import CmdRun -from dvc.exceptions import OutputDuplicationError +from dvc.exceptions import OutputDuplicationError, CircularDependencyError from tests.basic_env import TestDvc @@ -93,6 +93,15 @@ class TestRunNoExec(TestDvc): self.assertFalse(os.path.exists('out')) +class TestRunCircularDependency(TestDvc): + def test(self): + with self.assertRaises(CircularDependencyError): + self.dvc.run(cmd='', + deps=[self.FOO], + outs=[self.FOO], + fname='circular-dependency.dvc') + + class TestCmdRun(TestDvc): def test_run(self): ret = main(['run',