outputs: validate that output is not ignored (#4386)

This commit is contained in:
Paweł Redzyński
2020-08-14 14:30:12 +03:00
committed by GitHub
parent 00925029b4
commit 35f125f3df
4 changed files with 35 additions and 5 deletions
+7 -3
View File
@@ -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
View File
@@ -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)
+8
View File
@@ -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")
+6
View File
@@ -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")