diff --git a/dvc/ignore.py b/dvc/ignore.py index 301e666918..447bbb5b0e 100644 --- a/dvc/ignore.py +++ b/dvc/ignore.py @@ -324,6 +324,8 @@ def _outside_repo(self, path): return False def check_ignore(self, target): + # NOTE: can only be used in `dvc check-ignore`, see + # https://github.com/iterative/dvc/issues/5046 full_target = os.path.abspath(target) if not self._outside_repo(full_target): dirname, basename = os.path.split(os.path.normpath(full_target)) @@ -340,7 +342,11 @@ def check_ignore(self, target): def is_ignored(self, path): # NOTE: can't use self.check_ignore(path).match for now, see # https://github.com/iterative/dvc/issues/4555 - return self.is_ignored_dir(path) or self.is_ignored_file(path) + if os.path.isfile(path): + return self.is_ignored_file(path) + if os.path.isdir(path): + return self.is_ignored_dir(path) + return self.is_ignored_file(path) or self.is_ignored_dir(path) def init(path): diff --git a/dvc/output/base.py b/dvc/output/base.py index 1f4de887b7..23f0e67980 100644 --- a/dvc/output/base.py +++ b/dvc/output/base.py @@ -530,8 +530,8 @@ def _validate_output_path(cls, path, stage=None): raise cls.IsStageFileError(path) if stage: - check = stage.repo.tree.dvcignore.check_ignore(path) - if check.match: + if stage.repo.tree.dvcignore.is_ignored(path): + check = stage.repo.tree.dvcignore.check_ignore(path) raise cls.IsIgnoredError(check) def _check_can_merge(self, out): diff --git a/tests/func/test_ignore.py b/tests/func/test_ignore.py index 0b87588e86..2dc4d3c835 100644 --- a/tests/func/test_ignore.py +++ b/tests/func/test_ignore.py @@ -403,8 +403,17 @@ def test_ignore_in_added_dir(tmp_dir, dvc): assert not ignored_path.exists() -def test_ignored_output(tmp_dir, scm, dvc, run_copy): - tmp_dir.gen({".dvcignore": "*.log", "foo": "foo content"}) +@pytest.mark.parametrize( + "ignore_patterns,raise_error", + [("*.log", True), ("*.log\n!foo.log", False)], +) +def test_ignored_output( + tmp_dir, scm, dvc, run_copy, ignore_patterns, raise_error +): + tmp_dir.gen({".dvcignore": ignore_patterns, "foo": "foo content"}) - with pytest.raises(OutputIsIgnoredError): + if raise_error: + with pytest.raises(OutputIsIgnoredError): + run_copy("foo", "foo.log", name="copy") + else: run_copy("foo", "foo.log", name="copy") diff --git a/tests/unit/output/test_output.py b/tests/unit/output/test_output.py index 8a8240430e..440eee154d 100644 --- a/tests/unit/output/test_output.py +++ b/tests/unit/output/test_output.py @@ -73,6 +73,9 @@ 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, "is_ignored", return_value=False, + ) mocker.patch.object( stage.repo.tree.dvcignore, "check_ignore",