mirror of
https://github.com/Stone-Red-Code/dvc.git
synced 2026-11-02 07:18:25 +01:00
outputs: validate that output is not ignored (#4386)
This commit is contained in:
+7
-3
@@ -152,6 +152,10 @@ CheckIgnoreResult = namedtuple(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _no_match(path):
|
||||||
|
return CheckIgnoreResult(path, False, ["::"])
|
||||||
|
|
||||||
|
|
||||||
class DvcIgnoreFilterNoop:
|
class DvcIgnoreFilterNoop:
|
||||||
def __init__(self, tree, root_dir):
|
def __init__(self, tree, root_dir):
|
||||||
pass
|
pass
|
||||||
@@ -165,8 +169,8 @@ class DvcIgnoreFilterNoop:
|
|||||||
def is_ignored_file(self, _):
|
def is_ignored_file(self, _):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def check_ignore(self, _):
|
def check_ignore(self, path):
|
||||||
return []
|
return _no_match(path)
|
||||||
|
|
||||||
|
|
||||||
class DvcIgnoreFilter:
|
class DvcIgnoreFilter:
|
||||||
@@ -325,7 +329,7 @@ class DvcIgnoreFilter:
|
|||||||
|
|
||||||
if matches:
|
if matches:
|
||||||
return CheckIgnoreResult(target, True, matches)
|
return CheckIgnoreResult(target, True, matches)
|
||||||
return CheckIgnoreResult(target, False, ["::"])
|
return _no_match(target)
|
||||||
|
|
||||||
|
|
||||||
def init(path):
|
def init(path):
|
||||||
|
|||||||
+14
-2
@@ -45,6 +45,12 @@ class OutputIsStageFileError(DvcException):
|
|||||||
super().__init__(f"Stage file '{path}' cannot be an output.")
|
super().__init__(f"Stage file '{path}' cannot be an output.")
|
||||||
|
|
||||||
|
|
||||||
|
class OutputIsIgnoredError(DvcException):
|
||||||
|
def __init__(self, match):
|
||||||
|
lines = "\n".join(match.patterns)
|
||||||
|
super().__init__(f"Path '{match.file}' is ignored by\n{lines}")
|
||||||
|
|
||||||
|
|
||||||
class BaseOutput:
|
class BaseOutput:
|
||||||
IS_DEPENDENCY = False
|
IS_DEPENDENCY = False
|
||||||
|
|
||||||
@@ -77,6 +83,7 @@ class BaseOutput:
|
|||||||
DoesNotExistError = OutputDoesNotExistError
|
DoesNotExistError = OutputDoesNotExistError
|
||||||
IsNotFileOrDirError = OutputIsNotFileOrDirError
|
IsNotFileOrDirError = OutputIsNotFileOrDirError
|
||||||
IsStageFileError = OutputIsStageFileError
|
IsStageFileError = OutputIsStageFileError
|
||||||
|
IsIgnoredError = OutputIsIgnoredError
|
||||||
|
|
||||||
sep = "/"
|
sep = "/"
|
||||||
|
|
||||||
@@ -91,7 +98,7 @@ class BaseOutput:
|
|||||||
plot=False,
|
plot=False,
|
||||||
persist=False,
|
persist=False,
|
||||||
):
|
):
|
||||||
self._validate_output_path(path)
|
self._validate_output_path(path, stage)
|
||||||
# This output (and dependency) objects have too many paths/urls
|
# This output (and dependency) objects have too many paths/urls
|
||||||
# here is a list and comments:
|
# here is a list and comments:
|
||||||
#
|
#
|
||||||
@@ -499,8 +506,13 @@ class BaseOutput:
|
|||||||
return ret
|
return ret
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _validate_output_path(cls, path):
|
def _validate_output_path(cls, path, stage=None):
|
||||||
from dvc.dvcfile import is_valid_filename
|
from dvc.dvcfile import is_valid_filename
|
||||||
|
|
||||||
if is_valid_filename(path):
|
if is_valid_filename(path):
|
||||||
raise cls.IsStageFileError(path)
|
raise cls.IsStageFileError(path)
|
||||||
|
|
||||||
|
if stage:
|
||||||
|
check = stage.repo.tree.dvcignore.check_ignore(path)
|
||||||
|
if check.match:
|
||||||
|
raise cls.IsIgnoredError(check)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import pytest
|
|||||||
|
|
||||||
from dvc.exceptions import DvcIgnoreInCollectedDirError
|
from dvc.exceptions import DvcIgnoreInCollectedDirError
|
||||||
from dvc.ignore import DvcIgnore, DvcIgnorePatterns
|
from dvc.ignore import DvcIgnore, DvcIgnorePatterns
|
||||||
|
from dvc.output.base import OutputIsIgnoredError
|
||||||
from dvc.path_info import PathInfo
|
from dvc.path_info import PathInfo
|
||||||
from dvc.pathspec_math import PatternInfo, merge_patterns
|
from dvc.pathspec_math import PatternInfo, merge_patterns
|
||||||
from dvc.repo import Repo
|
from dvc.repo import Repo
|
||||||
@@ -400,3 +401,10 @@ def test_ignore_in_added_dir(tmp_dir, dvc):
|
|||||||
dvc.checkout()
|
dvc.checkout()
|
||||||
|
|
||||||
assert not ignored_path.exists()
|
assert not ignored_path.exists()
|
||||||
|
|
||||||
|
|
||||||
|
def test_ignored_output(tmp_dir, scm, dvc, run_copy):
|
||||||
|
tmp_dir.gen({".dvcignore": "*.log", "foo": "foo content"})
|
||||||
|
|
||||||
|
with pytest.raises(OutputIsIgnoredError):
|
||||||
|
run_copy("foo", "foo.log", name="copy")
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from funcy import first
|
|||||||
from voluptuous import MultipleInvalid, Schema
|
from voluptuous import MultipleInvalid, Schema
|
||||||
|
|
||||||
from dvc.cache import NamedCache
|
from dvc.cache import NamedCache
|
||||||
|
from dvc.ignore import _no_match
|
||||||
from dvc.output import CHECKSUM_SCHEMA, BaseOutput
|
from dvc.output import CHECKSUM_SCHEMA, BaseOutput
|
||||||
|
|
||||||
|
|
||||||
@@ -72,6 +73,11 @@ def test_get_used_cache(exists, expected_message, mocker, caplog):
|
|||||||
stage = mocker.MagicMock()
|
stage = mocker.MagicMock()
|
||||||
mocker.patch.object(stage, "__str__", return_value="stage: 'stage.dvc'")
|
mocker.patch.object(stage, "__str__", return_value="stage: 'stage.dvc'")
|
||||||
mocker.patch.object(stage, "addressing", "stage.dvc")
|
mocker.patch.object(stage, "addressing", "stage.dvc")
|
||||||
|
mocker.patch.object(
|
||||||
|
stage.repo.tree.dvcignore,
|
||||||
|
"check_ignore",
|
||||||
|
return_value=_no_match("path"),
|
||||||
|
)
|
||||||
|
|
||||||
output = BaseOutput(stage, "path")
|
output = BaseOutput(stage, "path")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user