mirror of
https://github.com/Stone-Red-Code/dvc.git
synced 2026-11-02 07:18:25 +01:00
run: Check CWD specified as output in any stage (#1375)
* run: Check CWD specified as output in any stage Fix #1123 * Fix some typo Co-Authored-By: mroutis <[email protected]> * normalize paths and make stage an obligatory argument * Pass stages() to _check_output_duplication * Update dvc/project.py Co-Authored-By: mroutis <[email protected]>
This commit is contained in:
committed by
Ruslan Kuprieiev
parent
44e67d4cdc
commit
22d38cda9f
@@ -31,6 +31,18 @@ class OutputDuplicationError(DvcException):
|
||||
super(OutputDuplicationError, self).__init__(msg.format(output, s))
|
||||
|
||||
|
||||
class WorkingDirectoryAsOutputError(DvcException):
|
||||
def __init__(self, cwd, fname):
|
||||
assert isinstance(cwd, str)
|
||||
assert isinstance(fname, str)
|
||||
msg = (
|
||||
"Current working directory '{cwd}' is specified as an output in"
|
||||
" '{fname}'. Use another CWD to prevent any data removal."
|
||||
.format(cwd=cwd, fname=fname)
|
||||
)
|
||||
super(WorkingDirectoryAsOutputError, self).__init__(msg)
|
||||
|
||||
|
||||
class CircularDependencyError(DvcException):
|
||||
def __init__(self, dependency):
|
||||
assert isinstance(dependency, str)
|
||||
|
||||
+28
-5
@@ -165,10 +165,20 @@ class Project(object):
|
||||
def to_dvc_path(self, path):
|
||||
return os.path.relpath(path, self.root_dir)
|
||||
|
||||
def _check_output_duplication(self, outs):
|
||||
def _check_cwd_specified_as_output(self, cwd, stages):
|
||||
from dvc.exceptions import WorkingDirectoryAsOutputError
|
||||
|
||||
cwd_path = os.path.abspath(os.path.normpath(cwd))
|
||||
|
||||
for stage in stages:
|
||||
for output in stage.outs:
|
||||
if os.path.isdir(output.path) and output.path == cwd_path:
|
||||
raise WorkingDirectoryAsOutputError(cwd, stage.relpath)
|
||||
|
||||
def _check_output_duplication(self, outs, stages):
|
||||
from dvc.exceptions import OutputDuplicationError
|
||||
|
||||
for stage in self.stages():
|
||||
for stage in stages:
|
||||
for o in stage.outs:
|
||||
for out in outs:
|
||||
if o.path == out.path and o.stage.path != out.stage.path:
|
||||
@@ -203,6 +213,7 @@ class Project(object):
|
||||
else:
|
||||
fnames = [fname]
|
||||
|
||||
all_stages = self.stages()
|
||||
stages = []
|
||||
self._files_to_git_add = []
|
||||
with self.state:
|
||||
@@ -211,7 +222,7 @@ class Project(object):
|
||||
outs=[f],
|
||||
add=True)
|
||||
|
||||
self._check_output_duplication(stage.outs)
|
||||
self._check_output_duplication(stage.outs, all_stages)
|
||||
|
||||
stage.save()
|
||||
stage.dump()
|
||||
@@ -332,7 +343,10 @@ class Project(object):
|
||||
deps=deps,
|
||||
overwrite=overwrite)
|
||||
|
||||
self._check_output_duplication(stage.outs)
|
||||
all_stages = self.stages()
|
||||
|
||||
self._check_cwd_specified_as_output(cwd, all_stages)
|
||||
self._check_output_duplication(stage.outs, all_stages)
|
||||
self._check_circular_dependency(stage.deps, stage.outs)
|
||||
|
||||
self._files_to_git_add = []
|
||||
@@ -352,7 +366,8 @@ class Project(object):
|
||||
deps=[url],
|
||||
outs=[out])
|
||||
|
||||
self._check_output_duplication(stage.outs)
|
||||
self._check_output_duplication(stage.outs, self.stages())
|
||||
self._check_circular_dependency(stage.deps, stage.outs)
|
||||
|
||||
self._files_to_git_add = []
|
||||
with self.state:
|
||||
@@ -1116,6 +1131,14 @@ class Project(object):
|
||||
return pipelines
|
||||
|
||||
def stages(self):
|
||||
"""
|
||||
Walks down the root directory looking for Dvcfiles,
|
||||
skipping the directories that are related with
|
||||
any SCM (e.g. `.git`) or DVC itself (`.dvc`).
|
||||
|
||||
NOTE: For large projects, this could be an expensive
|
||||
operation. Consider using some memorization.
|
||||
"""
|
||||
stages = []
|
||||
outs = []
|
||||
for root, dirs, files in os.walk(self.root_dir):
|
||||
|
||||
+16
-1
@@ -6,7 +6,9 @@ from dvc.utils import file_md5
|
||||
from dvc.stage import Stage, StageFileBadNameError, MissingDep
|
||||
from dvc.stage import StageBadCwdError
|
||||
from dvc.command.run import CmdRun
|
||||
from dvc.exceptions import OutputDuplicationError, CircularDependencyError
|
||||
from dvc.exceptions import (OutputDuplicationError,
|
||||
CircularDependencyError,
|
||||
WorkingDirectoryAsOutputError)
|
||||
|
||||
from tests.basic_env import TestDvc
|
||||
|
||||
@@ -117,6 +119,19 @@ class TestRunCircularDependency(TestDvc):
|
||||
fname='circular-dependency.dvc')
|
||||
|
||||
|
||||
class TestRunWorkingDirectoryAsOutput(TestDvc):
|
||||
def test(self):
|
||||
self.dvc.run(cmd='',
|
||||
deps=[],
|
||||
outs=[self.DATA_DIR])
|
||||
|
||||
with self.assertRaises(WorkingDirectoryAsOutputError):
|
||||
self.dvc.run(cmd='',
|
||||
cwd=self.DATA_DIR,
|
||||
outs=[self.FOO],
|
||||
fname='inside-cwd.dvc')
|
||||
|
||||
|
||||
class TestRunBadCwd(TestDvc):
|
||||
def test(self):
|
||||
with self.assertRaises(StageBadCwdError):
|
||||
|
||||
Reference in New Issue
Block a user