From cab4126ed43a493cb3370d78fd76c98386795406 Mon Sep 17 00:00:00 2001 From: Ruslan Kuprieiev Date: Wed, 28 Apr 2021 16:13:01 +0300 Subject: [PATCH] output: relative paths outside of repo (#5893) * output: relative paths outside of repo Fixes #5870 * scm: enhance FileNotInRepo message --- dvc/output/local.py | 17 ++++++++++++++--- dvc/scm/git/__init__.py | 4 +++- tests/func/test_add.py | 18 ++++++++++++++++++ 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/dvc/output/local.py b/dvc/output/local.py index 1ab16fcbe..752d8bf09 100644 --- a/dvc/output/local.py +++ b/dvc/output/local.py @@ -48,7 +48,11 @@ class LocalOutput(BaseOutput): return self.FS_CLS.PATH_CLS(abs_p) def __str__(self): - if not self.repo or not self.is_in_repo: + if ( + not self.repo + or urlparse(self.def_path).scheme == "remote" + or os.path.isabs(self.def_path) + ): return str(self.def_path) cur_dir = os.getcwd() @@ -63,8 +67,15 @@ class LocalOutput(BaseOutput): @property def is_in_repo(self): - def_scheme = urlparse(self.def_path).scheme - return def_scheme != "remote" and not os.path.isabs(self.def_path) + if urlparse(self.def_path).scheme == "remote": + return False + + if os.path.isabs(self.def_path): + return False + + if self.repo and not self.path_info.isin(self.repo.root_dir): + return False + return True def dumpd(self): ret = super().dumpd() diff --git a/dvc/scm/git/__init__.py b/dvc/scm/git/__init__.py index 936f4e4d5..fa2da88f8 100644 --- a/dvc/scm/git/__init__.py +++ b/dvc/scm/git/__init__.py @@ -151,7 +151,9 @@ class Git(Base): gitignore = os.path.join(ignore_file_dir, self.GITIGNORE) if not path_isin(os.path.realpath(gitignore), self.root_dir): - raise FileNotInRepoError(path) + raise FileNotInRepoError( + f"'{path}' is outside of git repository '{self.root_dir}'" + ) return entry, gitignore diff --git a/tests/func/test_add.py b/tests/func/test_add.py index 3c688ef09..4127b4ae4 100644 --- a/tests/func/test_add.py +++ b/tests/func/test_add.py @@ -347,6 +347,24 @@ def test_add_external_file(tmp_dir, dvc, workspace, hash_name, hash_value): assert dvc.status() == {} +def test_add_external_relpath(tmp_dir, dvc, local_cloud): + (fpath,) = local_cloud.gen("file", "file") + rel = os.path.relpath(fpath) + + with pytest.raises(StageExternalOutputsError): + dvc.add(rel) + + dvc.add(rel, external=True) + assert (tmp_dir / "file.dvc").read_text() == ( + "outs:\n" + "- md5: 8c7dd922ad47494fc02c388e12c00eac\n" + " size: 4\n" + f" path: {rel}\n" + ) + assert fpath.read_text() == "file" + assert dvc.status() == {} + + @pytest.mark.parametrize( "workspace, hash_name, hash_value", [