mirror of
https://github.com/Stone-Red-Code/dvc.git
synced 2026-11-01 23:08:43 +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:
|
||||
def __init__(self, tree, root_dir):
|
||||
pass
|
||||
@@ -165,8 +169,8 @@ class DvcIgnoreFilterNoop:
|
||||
def is_ignored_file(self, _):
|
||||
return False
|
||||
|
||||
def check_ignore(self, _):
|
||||
return []
|
||||
def check_ignore(self, path):
|
||||
return _no_match(path)
|
||||
|
||||
|
||||
class DvcIgnoreFilter:
|
||||
@@ -325,7 +329,7 @@ class DvcIgnoreFilter:
|
||||
|
||||
if matches:
|
||||
return CheckIgnoreResult(target, True, matches)
|
||||
return CheckIgnoreResult(target, False, ["::"])
|
||||
return _no_match(target)
|
||||
|
||||
|
||||
def init(path):
|
||||
|
||||
+14
-2
@@ -45,6 +45,12 @@ class OutputIsStageFileError(DvcException):
|
||||
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:
|
||||
IS_DEPENDENCY = False
|
||||
|
||||
@@ -77,6 +83,7 @@ class BaseOutput:
|
||||
DoesNotExistError = OutputDoesNotExistError
|
||||
IsNotFileOrDirError = OutputIsNotFileOrDirError
|
||||
IsStageFileError = OutputIsStageFileError
|
||||
IsIgnoredError = OutputIsIgnoredError
|
||||
|
||||
sep = "/"
|
||||
|
||||
@@ -91,7 +98,7 @@ class BaseOutput:
|
||||
plot=False,
|
||||
persist=False,
|
||||
):
|
||||
self._validate_output_path(path)
|
||||
self._validate_output_path(path, stage)
|
||||
# This output (and dependency) objects have too many paths/urls
|
||||
# here is a list and comments:
|
||||
#
|
||||
@@ -499,8 +506,13 @@ class BaseOutput:
|
||||
return ret
|
||||
|
||||
@classmethod
|
||||
def _validate_output_path(cls, path):
|
||||
def _validate_output_path(cls, path, stage=None):
|
||||
from dvc.dvcfile import is_valid_filename
|
||||
|
||||
if is_valid_filename(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.ignore import DvcIgnore, DvcIgnorePatterns
|
||||
from dvc.output.base import OutputIsIgnoredError
|
||||
from dvc.path_info import PathInfo
|
||||
from dvc.pathspec_math import PatternInfo, merge_patterns
|
||||
from dvc.repo import Repo
|
||||
@@ -400,3 +401,10 @@ def test_ignore_in_added_dir(tmp_dir, dvc):
|
||||
dvc.checkout()
|
||||
|
||||
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 dvc.cache import NamedCache
|
||||
from dvc.ignore import _no_match
|
||||
from dvc.output import CHECKSUM_SCHEMA, BaseOutput
|
||||
|
||||
|
||||
@@ -72,6 +73,11 @@ def test_get_used_cache(exists, expected_message, mocker, caplog):
|
||||
stage = mocker.MagicMock()
|
||||
mocker.patch.object(stage, "__str__", return_value="stage: '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")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user