From 2a17f0928c962d08b1d9a2cc81f49a235ebc4752 Mon Sep 17 00:00:00 2001 From: uttam12331 Date: Wed, 15 Jul 2026 12:58:46 +0530 Subject: [PATCH] api: fix get_url() when CWD is a subdirectory of the repo get_url() documents `path` as relative to the root of the repo, but get_data_index_entry() resolved it against the current working directory: from_os_path() returns a repo-root-relative path, which _get_key_from_relative() then re-resolves against the DVC filesystem's CWD. From a subdirectory, 'bar.txt' became the key ('subdir', 'bar.txt') and the lookup failed with OutputNotFoundError even though the output exists. Anchor a relative path at the repo root before building the index key, and anchor the fs path at the filesystem root so the CWD no longer leaks into the lookup. The original path is kept for the error message. Fixes #11029 --- dvc/repo/__init__.py | 15 ++++++++++++--- tests/func/api/test_data.py | 16 ++++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/dvc/repo/__init__.py b/dvc/repo/__init__.py index a7c3d4d7ca..05b08f0a55 100644 --- a/dvc/repo/__init__.py +++ b/dvc/repo/__init__.py @@ -387,15 +387,24 @@ def get_data_index_entry( path: str, workspace: str = "repo", ) -> tuple["DataIndex", "DataIndexEntry"]: + abspath = path + if not os.path.isabs(abspath): + # `path` is interpreted as relative to the root of the repo, not + # the current working directory (which may be a subdirectory of + # the repo). See #11029. + abspath = self.fs.join(self.root_dir, abspath) if self.subrepos: - fs_path = self.dvcfs.from_os_path(path) + # `from_os_path` returns a path relative to the repo root. Anchor + # it at the filesystem root so it is not re-resolved against the + # filesystem's current working directory. + fs_path = self.dvcfs.from_os_path(abspath) fs = self.dvcfs.fs - key = fs._get_key_from_relative(fs_path) + key = fs._get_key_from_relative(fs.root_marker + fs_path) subrepo, _, key = fs._get_subrepo_info(key) index = subrepo.index.data[workspace] else: index = self.index.data[workspace] - key = self.fs.relparts(path, self.root_dir) + key = self.fs.relparts(abspath, self.root_dir) try: return index, index[key] diff --git a/tests/func/api/test_data.py b/tests/func/api/test_data.py index 48d4e98eed..d3769f0dd6 100644 --- a/tests/func/api/test_data.py +++ b/tests/func/api/test_data.py @@ -250,6 +250,22 @@ def test_get_url_granular(tmp_dir, dvc, cloud): assert api.get_url(os.path.join("dir", "nested", "file")) == expected_url +def test_get_url_from_subdir(tmp_dir, dvc, local_cloud): + tmp_dir.add_remote(config=local_cloud.config) + tmp_dir.dvc_gen({"foo": "foo", "subdir": {"bar": "bar"}}) + + expected_url = ( + local_cloud / "files" / "md5" / "ac" / "bd18db4cc2f85cedef654fccc4a4d8" + ).url + + # A relative path is relative to the root of the repo, even when the + # current working directory is a subdirectory of the repo (#11029). + subdir = tmp_dir / "subdir" + with subdir.chdir(): + assert api.get_url("foo") == expected_url + assert api.get_url("foo", repo=os.fspath(tmp_dir)) == expected_url + + def test_get_url_subrepos(tmp_dir, scm, local_cloud): subrepo = tmp_dir / "subrepo" make_subrepo(subrepo, scm, config=local_cloud.config)