dvc: detach gitfs from local paths

Our gitfs paths are currently tied to the local path in which the cloned
repository resides in, which results in a lot of hacky assumptions throughout
the code.

This PR detaches gitfs from local paths completely, making dvc work with a
proper fsspec gitfs implementation.

Part of #7543
This commit is contained in:
Ruslan Kuprieiev
2022-05-11 18:17:39 +03:00
parent 14c9348f93
commit a80a85e6bd
30 changed files with 208 additions and 192 deletions
+5 -6
View File
@@ -89,6 +89,8 @@ class Config(dict):
from dvc.fs.local import LocalFileSystem from dvc.fs.local import LocalFileSystem
self.dvc_dir = dvc_dir self.dvc_dir = dvc_dir
self.wfs = LocalFileSystem()
self.fs = fs or self.wfs
if not dvc_dir: if not dvc_dir:
try: try:
@@ -98,10 +100,7 @@ class Config(dict):
except NotDvcRepoError: except NotDvcRepoError:
self.dvc_dir = None self.dvc_dir = None
else: else:
self.dvc_dir = os.path.abspath(os.path.realpath(dvc_dir)) self.dvc_dir = self.fs.path.abspath(self.fs.path.realpath(dvc_dir))
self.wfs = LocalFileSystem(url=self.dvc_dir)
self.fs = fs or self.wfs
self.load(validate=validate, config=config) self.load(validate=validate, config=config)
@@ -124,8 +123,8 @@ class Config(dict):
} }
if self.dvc_dir is not None: if self.dvc_dir is not None:
files["repo"] = os.path.join(self.dvc_dir, self.CONFIG) files["repo"] = self.fs.path.join(self.dvc_dir, self.CONFIG)
files["local"] = os.path.join(self.dvc_dir, self.CONFIG_LOCAL) files["local"] = self.fs.path.join(self.dvc_dir, self.CONFIG_LOCAL)
return files return files
+12 -8
View File
@@ -50,19 +50,21 @@ def external_repo(
config.update(cache_config) config.update(cache_config)
if for_write: if for_write:
scm = None
root_dir = path root_dir = path
fs = None fs = None
else: else:
root_dir = os.path.realpath(path) scm = Git(path)
scm = Git(root_dir)
fs = GitFileSystem(scm=scm, rev=rev) fs = GitFileSystem(scm=scm, rev=rev)
root_dir = "/"
repo_kwargs = dict( repo_kwargs = dict(
root_dir=root_dir, root_dir=root_dir,
url=url, url=url,
fs=fs, fs=fs,
config=config, config=config,
repo_factory=erepo_factory(url, cache_config), repo_factory=erepo_factory(url, root_dir, cache_config),
scm=scm,
**kwargs, **kwargs,
) )
@@ -92,14 +94,16 @@ def external_repo(
_remove(path) _remove(path)
def erepo_factory(url, cache_config, *args, **kwargs): def erepo_factory(url, root_dir, cache_config):
def make_repo(path, **_kwargs): from dvc.fs.local import localfs
def make_repo(path, fs=None, **_kwargs):
_config = cache_config.copy() _config = cache_config.copy()
if os.path.isdir(url): if os.path.isdir(url):
rel = os.path.relpath(path, _kwargs["fs"].fs_args["scm"].root_dir) fs = fs or localfs
repo_path = os.path.join(url, rel) repo_path = os.path.join(url, *fs.path.relparts(path, root_dir))
_config.update(_get_remote_config(repo_path)) _config.update(_get_remote_config(repo_path))
return Repo(path, config=_config, **_kwargs) return Repo(path, fs=fs, config=_config, **_kwargs)
return make_repo return make_repo
+4 -8
View File
@@ -1,4 +1,3 @@
import os
import threading import threading
from typing import TYPE_CHECKING, Any from typing import TYPE_CHECKING, Any
@@ -15,7 +14,6 @@ if TYPE_CHECKING:
class GitFileSystem(FileSystem): # pylint:disable=abstract-method class GitFileSystem(FileSystem): # pylint:disable=abstract-method
"""Proxies the repo file access methods to Git objects""" """Proxies the repo file access methods to Git objects"""
sep = os.sep
scheme = "local" scheme = "local"
def __init__( def __init__(
@@ -40,12 +38,6 @@ class GitFileSystem(FileSystem): # pylint:disable=abstract-method
} }
) )
@cached_property
def path(self):
from .path import Path
return Path(self.sep, getcwd=os.getcwd)
@wrap_prop(threading.Lock()) @wrap_prop(threading.Lock())
@cached_property @cached_property
def fs(self) -> "FsspecGitFileSystem": def fs(self) -> "FsspecGitFileSystem":
@@ -53,6 +45,10 @@ class GitFileSystem(FileSystem): # pylint:disable=abstract-method
return FsspecGitFileSystem(**self.fs_args) return FsspecGitFileSystem(**self.fs_args)
@cached_property
def path(self):
return self.fs.path
@property @property
def rev(self) -> str: def rev(self) -> str:
return self.fs.rev return self.fs.rev
+1 -1
View File
@@ -182,7 +182,7 @@ class LocalFileSystem(FileSystem):
def path(self): def path(self):
from .path import Path from .path import Path
return Path(self.sep, os.getcwd) return Path(self.sep, getcwd=os.getcwd, realpath=os.path.realpath)
def upload_fobj(self, fobj, to_info, **kwargs): def upload_fobj(self, fobj, to_info, **kwargs):
self.makedirs(self.path.parent(to_info)) self.makedirs(self.path.parent(to_info))
+13 -5
View File
@@ -3,11 +3,13 @@ import posixpath
class Path: class Path:
def __init__(self, sep, getcwd=None): def __init__(self, sep, getcwd=None, realpath=None):
def _getcwd(): def _getcwd():
return "" return ""
self.getcwd = getcwd or _getcwd self.getcwd = getcwd or _getcwd
self.realpath = realpath or self.abspath
if sep == posixpath.sep: if sep == posixpath.sep:
self.flavour = posixpath self.flavour = posixpath
elif sep == ntpath.sep: elif sep == ntpath.sep:
@@ -15,6 +17,12 @@ class Path:
else: else:
raise ValueError(f"unsupported separator '{sep}'") raise ValueError(f"unsupported separator '{sep}'")
def chdir(self, path):
def _getcwd():
return path
self.getcwd = _getcwd
def join(self, *parts): def join(self, *parts):
return self.flavour.join(*parts) return self.flavour.join(*parts)
@@ -104,15 +112,15 @@ class Path:
# pylint: disable=arguments-out-of-order # pylint: disable=arguments-out-of-order
return self.isin_or_eq(left, right) or self.isin(right, left) return self.isin_or_eq(left, right) or self.isin(right, left)
def relpath(self, path, start): def relpath(self, path, start=None):
if not start: if start is None:
start = "." start = "."
return self.flavour.relpath( return self.flavour.relpath(
self.abspath(path), start=self.abspath(start) self.abspath(path), start=self.abspath(start)
) )
def relparts(self, path, base): def relparts(self, path, start=None):
return self.parts(self.relpath(path, base)) return self.parts(self.relpath(path, start=start))
def as_posix(self, path): def as_posix(self, path):
return path.replace(self.flavour.sep, posixpath.sep) return path.replace(self.flavour.sep, posixpath.sep)
+14 -4
View File
@@ -106,7 +106,12 @@ class _RepoFileSystem(AbstractFileSystem): # pylint:disable=abstract-method
self.repo_factory = repo_factory self.repo_factory = repo_factory
def _getcwd(): def _getcwd():
return self.root_marker relparts = ()
if repo.fs.path.isin(repo.fs.path.getcwd(), repo.root_dir):
relparts = repo.fs.path.relparts(
repo.fs.path.getcwd(), repo.root_dir
)
return self.root_marker + self.sep.join(relparts)
self.path = Path(self.sep, getcwd=_getcwd) self.path = Path(self.sep, getcwd=_getcwd)
self.repo = repo self.repo = repo
@@ -190,7 +195,7 @@ class _RepoFileSystem(AbstractFileSystem): # pylint:disable=abstract-method
repo_kwargs["config"] = cache_config repo_kwargs["config"] = cache_config
else: else:
repo_kwargs["cache_dir"] = cache_dir repo_kwargs["cache_dir"] = cache_dir
factory = erepo_factory(url, cache_config) factory = erepo_factory(url, root, cache_config)
with _open( with _open(
url if url else root, url if url else root,
@@ -237,6 +242,7 @@ class _RepoFileSystem(AbstractFileSystem): # pylint:disable=abstract-method
repo = self.repo_factory( repo = self.repo_factory(
d, d,
fs=self.repo.fs, fs=self.repo.fs,
scm=self.repo.scm,
repo_factory=self.repo_factory, repo_factory=self.repo_factory,
) )
self._dvcfss[key] = DvcFileSystem(repo=repo) self._dvcfss[key] = DvcFileSystem(repo=repo)
@@ -249,7 +255,7 @@ class _RepoFileSystem(AbstractFileSystem): # pylint:disable=abstract-method
from dvc.repo import Repo from dvc.repo import Repo
repo_path = os.path.join(dir_path, Repo.DVC_DIR) repo_path = self.repo.fs.path.join(dir_path, Repo.DVC_DIR)
return self.repo.fs.isdir(repo_path) return self.repo.fs.isdir(repo_path)
def _get_fs_pair( def _get_fs_pair(
@@ -417,7 +423,7 @@ class _RepoFileSystem(AbstractFileSystem): # pylint:disable=abstract-method
if not dvc_info and not fs_info: if not dvc_info and not fs_info:
raise FileNotFoundError raise FileNotFoundError
info = _merge_info(dvc_fs.repo, fs_info, dvc_info) info = _merge_info(repo, fs_info, dvc_info)
info["name"] = path info["name"] = path
return info return info
@@ -445,6 +451,10 @@ class RepoFileSystem(FileSystem):
def isdvc(self, path, **kwargs): def isdvc(self, path, **kwargs):
return self.fs.isdvc(path, **kwargs) return self.fs.isdvc(path, **kwargs)
@property
def path(self): # pylint: disable=invalid-overridden-method
return self.fs.path
@property @property
def repo(self): def repo(self):
return self.fs.repo return self.fs.repo
+7 -3
View File
@@ -35,7 +35,6 @@ class DvcIgnorePatterns(DvcIgnore):
self.sep = sep self.sep = sep
self.pattern_list = pattern_list self.pattern_list = pattern_list
self.dirname = dirname self.dirname = dirname
self.prefix = self.dirname + self.sep
self.regex_pattern_list = [ self.regex_pattern_list = [
GitWildMatchPattern.pattern_to_regex(pattern_info.patterns) GitWildMatchPattern.pattern_to_regex(pattern_info.patterns)
@@ -74,10 +73,13 @@ class DvcIgnorePatterns(DvcIgnore):
def _get_normalize_path(self, dirname, basename): def _get_normalize_path(self, dirname, basename):
# NOTE: `relpath` is too slow, so we have to assume that both # NOTE: `relpath` is too slow, so we have to assume that both
# `dirname` and `self.dirname` are relative or absolute together. # `dirname` and `self.dirname` are relative or absolute together.
prefix = self.dirname.rstrip(self.sep) + self.sep
if dirname == self.dirname: if dirname == self.dirname:
path = basename path = basename
elif dirname.startswith(self.prefix): elif dirname.startswith(prefix):
rel = dirname[len(self.prefix) :] rel = dirname[len(prefix) :]
# NOTE: `os.path.join` is ~x5.5 slower # NOTE: `os.path.join` is ~x5.5 slower
path = f"{rel}{self.sep}{basename}" path = f"{rel}{self.sep}{basename}"
else: else:
@@ -210,6 +212,7 @@ class DvcIgnoreFilter:
new_pattern = DvcIgnorePatterns.from_file(path, self.fs, name) new_pattern = DvcIgnorePatterns.from_file(path, self.fs, name)
if old_pattern: if old_pattern:
plist, prefix = merge_patterns( plist, prefix = merge_patterns(
self.fs.path.flavour,
old_pattern.pattern_list, old_pattern.pattern_list,
old_pattern.dirname, old_pattern.dirname,
new_pattern.pattern_list, new_pattern.pattern_list,
@@ -259,6 +262,7 @@ class DvcIgnoreFilter:
old_pattern = ignore_trie.longest_prefix(key).value old_pattern = ignore_trie.longest_prefix(key).value
if old_pattern: if old_pattern:
plist, prefix = merge_patterns( plist, prefix = merge_patterns(
self.fs.path.flavour,
old_pattern.pattern_list, old_pattern.pattern_list,
old_pattern.dirname, old_pattern.dirname,
new_pattern.pattern_list, new_pattern.pattern_list,
+18 -8
View File
@@ -295,12 +295,21 @@ class Output:
if ( if (
self.fs.scheme == "local" self.fs.scheme == "local"
and stage and stage
and isinstance(stage.repo.fs, LocalFileSystem)
and path_isin(path, stage.repo.root_dir) and path_isin(path, stage.repo.root_dir)
): ):
self.def_path = relpath(path, stage.wdir) self.def_path = relpath(path, stage.wdir)
self.fs = stage.repo.fs
else: else:
self.def_path = path self.def_path = path
if (
self.repo
and self.fs.scheme == "local"
and not self.fs.path.isabs(self.def_path)
):
self.fs = self.repo.fs
self._validate_output_path(path, stage) 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:
@@ -342,7 +351,7 @@ class Output:
if self.stage and not os.path.isabs(fs_path): if self.stage and not os.path.isabs(fs_path):
fs_path = fs.path.join(self.stage.wdir, fs_path) fs_path = fs.path.join(self.stage.wdir, fs_path)
abs_p = os.path.abspath(os.path.normpath(fs_path)) abs_p = fs.path.abspath(fs.path.normpath(fs_path))
return abs_p return abs_p
def __repr__(self): def __repr__(self):
@@ -361,11 +370,11 @@ class Output:
): ):
return str(self.def_path) return str(self.def_path)
cur_dir = os.getcwd() cur_dir = self.fs.path.getcwd()
if path_isin(cur_dir, self.repo.root_dir): if self.fs.path.isin(cur_dir, self.repo.root_dir):
return relpath(self.fs_path, cur_dir) return self.fs.path.relpath(self.fs_path, cur_dir)
return relpath(self.fs_path, self.repo.root_dir) return self.fs.path.relpath(self.fs_path, self.repo.root_dir)
@property @property
def scheme(self): def scheme(self):
@@ -379,11 +388,12 @@ class Output:
if urlparse(self.def_path).scheme == "remote": if urlparse(self.def_path).scheme == "remote":
return False return False
if os.path.isabs(self.def_path): if self.fs.path.isabs(self.def_path):
return False return False
return self.repo and path_isin( return self.repo and self.fs.path.isin(
os.path.realpath(self.fs_path), self.repo.root_dir self.fs.path.realpath(self.fs_path),
self.repo.root_dir,
) )
@property @property
+2 -8
View File
@@ -18,7 +18,6 @@ from funcy import cached_property, collecting, first, isa, join, reraise
from dvc.exceptions import DvcException from dvc.exceptions import DvcException
from dvc.parsing.interpolate import ParseError from dvc.parsing.interpolate import ParseError
from dvc.utils import relpath
from .context import ( from .context import (
Context, Context,
@@ -135,19 +134,14 @@ def make_definition(
class DataResolver: class DataResolver:
def __init__(self, repo: "Repo", wdir: str, d: dict): def __init__(self, repo: "Repo", wdir: str, d: dict):
from dvc.fs import LocalFileSystem
self.fs = fs = repo.fs self.fs = fs = repo.fs
if os.path.isabs(wdir): if os.path.isabs(wdir):
start = ( wdir = fs.path.relpath(wdir)
os.curdir if isinstance(fs, LocalFileSystem) else repo.root_dir
)
wdir = relpath(wdir, start)
wdir = "" if wdir == os.curdir else wdir wdir = "" if wdir == os.curdir else wdir
self.wdir = wdir self.wdir = wdir
self.relpath = os.path.normpath(fs.path.join(self.wdir, "dvc.yaml")) self.relpath = fs.path.normpath(fs.path.join(self.wdir, "dvc.yaml"))
vars_ = d.get(VARS_KWD, []) vars_ = d.get(VARS_KWD, [])
check_interpolations(vars_, VARS_KWD, self.relpath) check_interpolations(vars_, VARS_KWD, self.relpath)
+1 -1
View File
@@ -395,7 +395,7 @@ class Context(CtxDict):
def merge_from(self, fs, item: str, wdir: str, overwrite=False): def merge_from(self, fs, item: str, wdir: str, overwrite=False):
path, _, keys_str = item.partition(":") path, _, keys_str = item.partition(":")
path = os.path.normpath(fs.path.join(wdir, path)) path = fs.path.normpath(fs.path.join(wdir, path))
select_keys = lfilter(bool, keys_str.split(",")) if keys_str else None select_keys = lfilter(bool, keys_str.split(",")) if keys_str else None
if path in self.imports: if path in self.imports:
+2 -3
View File
@@ -2,7 +2,6 @@
# Including changing base dir of path specification patterns and merging # Including changing base dir of path specification patterns and merging
# of two path specification patterns with different base # of two path specification patterns with different base
# All the operations follow the documents of `gitignore` # All the operations follow the documents of `gitignore`
import os
from collections import namedtuple from collections import namedtuple
from pathspec.util import normalize_file from pathspec.util import normalize_file
@@ -68,7 +67,7 @@ def _change_dirname(dirname, pattern_list, new_dirname):
] ]
def merge_patterns(pattern_a, prefix_a, pattern_b, prefix_b): def merge_patterns(flavour, pattern_a, prefix_a, pattern_b, prefix_b):
""" """
Merge two path specification patterns. Merge two path specification patterns.
@@ -81,7 +80,7 @@ def merge_patterns(pattern_a, prefix_a, pattern_b, prefix_b):
if not pattern_b: if not pattern_b:
return pattern_a, prefix_a return pattern_a, prefix_a
longest_common_dir = os.path.commonpath([prefix_a, prefix_b]) longest_common_dir = flavour.commonpath([prefix_a, prefix_b])
new_pattern_a = _change_dirname(prefix_a, pattern_a, longest_common_dir) new_pattern_a = _change_dirname(prefix_a, pattern_a, longest_common_dir)
new_pattern_b = _change_dirname(prefix_b, pattern_b, longest_common_dir) new_pattern_b = _change_dirname(prefix_b, pattern_b, longest_common_dir)
+31 -18
View File
@@ -18,6 +18,7 @@ if TYPE_CHECKING:
from dvc.fs.base import FileSystem from dvc.fs.base import FileSystem
from dvc.objects.file import HashFile from dvc.objects.file import HashFile
from dvc.repo.scm_context import SCMContext from dvc.repo.scm_context import SCMContext
from dvc.scm import Base
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -85,29 +86,32 @@ class Repo:
root_dir: str = None, root_dir: str = None,
fs: "FileSystem" = None, fs: "FileSystem" = None,
uninitialized: bool = False, uninitialized: bool = False,
scm: "Base" = None,
): ):
from dvc.scm import SCM, Base, SCMError from dvc.fs.local import localfs
from dvc.utils.fs import makedirs from dvc.scm import SCM, SCMError
dvc_dir = None dvc_dir = None
tmp_dir = None tmp_dir = None
try: try:
root_dir = self.find_root(root_dir, fs) root_dir = self.find_root(root_dir, fs)
dvc_dir = os.path.join(root_dir, self.DVC_DIR) fs = fs or localfs
tmp_dir = os.path.join(dvc_dir, "tmp") dvc_dir = fs.path.join(root_dir, self.DVC_DIR)
makedirs(tmp_dir, exist_ok=True) tmp_dir = fs.path.join(dvc_dir, "tmp")
except NotDvcRepoError: except NotDvcRepoError:
if not uninitialized: if not uninitialized:
raise raise
try: if not scm:
scm = SCM(root_dir or os.curdir) try:
except SCMError: scm = SCM(root_dir or os.curdir)
scm = SCM(os.curdir, no_scm=True) except SCMError:
scm = SCM(os.curdir, no_scm=True)
assert isinstance(scm, Base) if not fs or not root_dir:
root_dir = scm.root_dir root_dir = scm.root_dir
assert root_dir
return root_dir, dvc_dir, tmp_dir return root_dir, dvc_dir, tmp_dir
def _get_database_dir(self, db_name): def _get_database_dir(self, db_name):
@@ -148,6 +152,7 @@ class Repo:
config=None, config=None,
url=None, url=None,
repo_factory=None, repo_factory=None,
scm=None,
): ):
from dvc.config import Config from dvc.config import Config
from dvc.data.db import ODBManager from dvc.data.db import ODBManager
@@ -166,14 +171,18 @@ class Repo:
self.url = url self.url = url
self._fs_conf = {"repo_factory": repo_factory} self._fs_conf = {"repo_factory": repo_factory}
self._fs = fs or localfs self._fs = fs or localfs
self._scm = None self._scm = scm
if rev and not fs: if rev and not fs:
self._scm = SCM(root_dir or os.curdir) self._scm = scm = SCM(root_dir or os.curdir)
root_dir = "/"
self._fs = GitFileSystem(scm=self._scm, rev=rev) self._fs = GitFileSystem(scm=self._scm, rev=rev)
self.root_dir, self.dvc_dir, self.tmp_dir = self._get_repo_dirs( self.root_dir, self.dvc_dir, self.tmp_dir = self._get_repo_dirs(
root_dir=root_dir, fs=self.fs, uninitialized=uninitialized root_dir=root_dir,
fs=self.fs,
uninitialized=uninitialized,
scm=scm,
) )
self.config = Config(self.dvc_dir, fs=self.fs, config=config) self.config = Config(self.dvc_dir, fs=self.fs, config=config)
@@ -189,7 +198,11 @@ class Repo:
self.lock = LockNoop() self.lock = LockNoop()
self.state = StateNoop() self.state = StateNoop()
self.odb = ODBManager(self) self.odb = ODBManager(self)
self.tmp_dir = None
else: else:
from dvc.utils.fs import makedirs
makedirs(self.tmp_dir, exist_ok=True)
self.lock = make_lock( self.lock = make_lock(
os.path.join(self.tmp_dir, "lock"), os.path.join(self.tmp_dir, "lock"),
tmp_dir=self.tmp_dir, tmp_dir=self.tmp_dir,
@@ -312,9 +325,9 @@ class Repo:
def find_root(cls, root=None, fs=None) -> str: def find_root(cls, root=None, fs=None) -> str:
from dvc.fs.local import LocalFileSystem, localfs from dvc.fs.local import LocalFileSystem, localfs
root = root or os.curdir
root_dir = os.path.realpath(root)
fs = fs or localfs fs = fs or localfs
root = root or os.curdir
root_dir = fs.path.realpath(root)
if not fs.isdir(root_dir): if not fs.isdir(root_dir):
raise NotDvcRepoError(f"directory '{root}' does not exist") raise NotDvcRepoError(f"directory '{root}' does not exist")
@@ -443,7 +456,7 @@ class Repo:
# using `outs_graph` to ensure graph checks are run # using `outs_graph` to ensure graph checks are run
outs = outs or self.index.outs_graph outs = outs or self.index.outs_graph
abs_path = os.path.abspath(path) abs_path = self.fs.path.abspath(path)
fs_path = abs_path fs_path = abs_path
def func(out): def func(out):
@@ -467,7 +480,7 @@ class Repo:
return matched return matched
def is_dvc_internal(self, path): def is_dvc_internal(self, path):
path_parts = os.path.normpath(path).split(os.path.sep) path_parts = self.fs.path.normpath(path).split(self.fs.sep)
return self.DVC_DIR in path_parts return self.DVC_DIR in path_parts
@cached_property @cached_property
+18
View File
@@ -31,7 +31,18 @@ def brancher( # noqa: E302
from dvc.fs.local import LocalFileSystem from dvc.fs.local import LocalFileSystem
repo_root_parts = ()
if self.fs.path.isin(self.root_dir, self.scm.root_dir):
repo_root_parts = self.fs.path.relparts(
self.root_dir, self.scm.root_dir
)
cwd_parts = ()
if self.fs.path.isin(self.fs.path.getcwd(), self.root_dir):
cwd_parts = self.fs.path.relparts(self.fs.path.getcwd(), self.root_dir)
saved_fs = self.fs saved_fs = self.fs
saved_root = self.root_dir
scm = self.scm scm = self.scm
@@ -56,9 +67,16 @@ def brancher( # noqa: E302
for sha, names in found_revs.items(): for sha, names in found_revs.items():
self.fs = GitFileSystem(scm=scm, rev=sha) self.fs = GitFileSystem(scm=scm, rev=sha)
self.root_dir = self.fs.path.join("/", *repo_root_parts)
if cwd_parts:
cwd = self.fs.path.join("/", *cwd_parts)
self.fs.path.chdir(cwd)
# ignore revs that don't contain repo root # ignore revs that don't contain repo root
# (i.e. revs from before a subdir=True repo was init'ed) # (i.e. revs from before a subdir=True repo was init'ed)
if self.fs.exists(self.root_dir): if self.fs.exists(self.root_dir):
yield sha if sha_only else ",".join(names) yield sha if sha_only else ",".join(names)
finally: finally:
self.fs = saved_fs self.fs = saved_fs
self.root_dir = saved_root
+1 -2
View File
@@ -31,7 +31,6 @@ def _collect_paths(
rev: str = None, rev: str = None,
) -> StrPaths: ) -> StrPaths:
from dvc.fs.repo import RepoFileSystem from dvc.fs.repo import RepoFileSystem
from dvc.utils import relpath
fs = RepoFileSystem(repo=repo) fs = RepoFileSystem(repo=repo)
fs_paths = [fs.from_os_path(target) for target in targets] fs_paths = [fs.from_os_path(target) for target in targets]
@@ -41,7 +40,7 @@ def _collect_paths(
if recursive and fs.isdir(fs_path): if recursive and fs.isdir(fs_path):
target_paths.extend(fs.find(fs_path)) target_paths.extend(fs.find(fs_path))
rel = relpath(fs_path) rel = fs.path.relpath(fs_path)
if not fs.exists(fs_path): if not fs.exists(fs_path):
if rev == "workspace" or rev == "": if rev == "workspace" or rev == "":
logger.warning("'%s' was not found in current workspace.", rel) logger.warning("'%s' was not found in current workspace.", rel)
+8 -14
View File
@@ -122,11 +122,11 @@ def _output_paths(repo, targets):
return True return True
def _to_path(output): def _to_path(output):
return ( relparts = output.fs.path.relparts(output.fs_path)
str(output) base = os.path.join(*relparts)
if not output.is_dir_checksum if output.is_dir_checksum:
else os.path.join(str(output), "") return os.path.join(base, "")
) return base
for output in repo.index.outs: for output in repo.index.outs:
if _exists(output): if _exists(output):
@@ -168,20 +168,14 @@ def _output_paths(repo, targets):
def _dir_output_paths(fs, fs_path, obj, targets=None): def _dir_output_paths(fs, fs_path, obj, targets=None):
if fs.scheme == "local": base = os.path.join(*fs.path.relparts(fs_path))
# NOTE: workaround for filesystems that are based on full local paths
# (e.g. gitfs, dvcfs, repofs). Proper solution is to use upcoming
# fsspec's prefixfs to use relpaths as fs_paths.
base = fs.path.relpath(fs_path, os.getcwd())
else:
base = fs_path
for key, _, oid in obj: for key, _, oid in obj:
fname = fs.path.join(fs_path, *key) fname = fs.path.join(fs_path, *key)
if targets is None or any( if targets is None or any(
fs.path.isin_or_eq(fname, target) for target in targets fs.path.isin_or_eq(fname, target) for target in targets
): ):
# pylint: disable=no-member # pylint: disable=no-member
yield fs.path.join(base, *key), oid.value yield os.path.join(base, *key), oid.value
def _filter_missing(repo_fs, paths): def _filter_missing(repo_fs, paths):
@@ -206,7 +200,7 @@ def _targets_to_paths(repo_fs, targets):
for target in targets: for target in targets:
if repo_fs.exists(target): if repo_fs.exists(target):
paths.append(os.path.abspath(target)) paths.append(repo_fs.repo.fs.path.abspath(target))
else: else:
missing.append(target) missing.append(target)
+8 -21
View File
@@ -10,12 +10,7 @@ from dvc.repo.collect import collect
from dvc.scm import NoSCMError from dvc.scm import NoSCMError
from dvc.stage import PipelineStage from dvc.stage import PipelineStage
from dvc.ui import ui from dvc.ui import ui
from dvc.utils import ( from dvc.utils import error_handler, errored_revisions, onerror_collect
error_handler,
errored_revisions,
onerror_collect,
relpath,
)
from dvc.utils.serialize import LOADERS from dvc.utils.serialize import LOADERS
if TYPE_CHECKING: if TYPE_CHECKING:
@@ -42,7 +37,7 @@ def _collect_configs(
) )
all_fs_paths = fs_paths + [p.fs_path for p in params] all_fs_paths = fs_paths + [p.fs_path for p in params]
if not targets: if not targets:
default_params = os.path.join( default_params = repo.fs.path.join(
repo.root_dir, ParamsDependency.DEFAULT_PARAMS_FILE repo.root_dir, ParamsDependency.DEFAULT_PARAMS_FILE
) )
if default_params not in all_fs_paths and repo.fs.exists( if default_params not in all_fs_paths and repo.fs.exists(
@@ -75,41 +70,33 @@ def _read_params(
onerror=onerror, flatten=False onerror=onerror, flatten=False
) )
if params_dict: if params_dict:
res[ name = os.sep.join(repo.fs.path.relparts(param.fs_path))
repo.fs.path.relpath(param.fs_path, os.getcwd()) res[name] = params_dict
] = params_dict
else: else:
fs_paths += [param.fs_path for param in params] fs_paths += [param.fs_path for param in params]
for fs_path in fs_paths: for fs_path in fs_paths:
from_path = _read_fs_path(repo.fs, fs_path, onerror=onerror) from_path = _read_fs_path(repo.fs, fs_path, onerror=onerror)
if from_path: if from_path:
res[repo.fs.path.relpath(fs_path, os.getcwd())] = from_path name = os.sep.join(repo.fs.path.relparts(fs_path))
res[name] = from_path
return res return res
def _collect_vars(repo, params) -> Dict: def _collect_vars(repo, params) -> Dict:
from dvc.fs.git import GitFileSystem
vars_params: Dict[str, Dict] = defaultdict(dict) vars_params: Dict[str, Dict] = defaultdict(dict)
rel_to_root = relpath(repo.root_dir)
for stage in repo.index.stages: for stage in repo.index.stages:
if isinstance(stage, PipelineStage) and stage.tracked_vars: if isinstance(stage, PipelineStage) and stage.tracked_vars:
for file, vars_ in stage.tracked_vars.items(): for file, vars_ in stage.tracked_vars.items():
if isinstance(repo.fs, GitFileSystem):
# GitFileSystem uses relatively-absolute paths from the
# root of the repo. We need to convert them to relative
# paths based on the current working directory.
file = os.path.normpath(os.path.join(rel_to_root, file))
# `params` file are shown regardless of `tracked` or not # `params` file are shown regardless of `tracked` or not
# to reduce noise and duplication, they are skipped # to reduce noise and duplication, they are skipped
if file in params: if file in params:
continue continue
vars_params[file].update(vars_) name = os.sep.join(repo.fs.path.parts(file))
vars_params[name].update(vars_)
return vars_params return vars_params
+10 -9
View File
@@ -17,12 +17,7 @@ from typing import (
from funcy import cached_property, first, project from funcy import cached_property, first, project
from dvc.exceptions import DvcException from dvc.exceptions import DvcException
from dvc.utils import ( from dvc.utils import error_handler, errored_revisions, onerror_collect
error_handler,
errored_revisions,
onerror_collect,
relpath,
)
from dvc.utils.serialize import LOADERS from dvc.utils.serialize import LOADERS
if TYPE_CHECKING: if TYPE_CHECKING:
@@ -110,6 +105,7 @@ class Plots:
plots = _collect_plots(self.repo, targets, revision, recursive) plots = _collect_plots(self.repo, targets, revision, recursive)
res: Dict[str, Any] = {} res: Dict[str, Any] = {}
for fs_path, rev_props in plots.items(): for fs_path, rev_props in plots.items():
base = os.path.join(*fs.path.relparts(fs_path, fs.fs.root_marker))
if fs.isdir(fs_path): if fs.isdir(fs_path):
plot_files = [] plot_files = []
unpacking_res = _unpack_dir_files(fs, fs_path, onerror=onerror) unpacking_res = _unpack_dir_files(fs, fs_path, onerror=onerror)
@@ -118,12 +114,17 @@ class Plots:
"data" "data"
): ):
plot_files.append( plot_files.append(
(pi, relpath(pi, self.repo.root_dir)) (
pi,
os.path.join(
base, *fs.path.relparts(pi, fs_path)
),
)
) )
else: else:
res[relpath(fs_path, self.repo.root_dir)] = unpacking_res res[base] = unpacking_res
else: else:
plot_files = [(fs_path, relpath(fs_path, self.repo.root_dir))] plot_files = [(fs_path, base)]
props = props or {} props = props or {}
+7 -6
View File
@@ -1,6 +1,5 @@
import fnmatch import fnmatch
import logging import logging
import os
import time import time
import typing import typing
from contextlib import suppress from contextlib import suppress
@@ -24,7 +23,7 @@ from dvc.exceptions import (
OutputNotFoundError, OutputNotFoundError,
) )
from dvc.repo import lock_repo from dvc.repo import lock_repo
from dvc.utils import parse_target, relpath from dvc.utils import as_posix, parse_target, relpath
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -378,7 +377,7 @@ class StageLoad:
if recursive and self.fs.isdir(target): if recursive and self.fs.isdir(target):
from dvc.repo.graph import collect_inside_path from dvc.repo.graph import collect_inside_path
path = os.path.abspath(target) path = self.fs.path.abspath(target)
return collect_inside_path(path, graph or self.graph) return collect_inside_path(path, graph or self.graph)
stages = self.from_target(target, accept_group=accept_group, glob=glob) stages = self.from_target(target, accept_group=accept_group, glob=glob)
@@ -416,6 +415,8 @@ class StageLoad:
if not target: if not target:
return [StageInfo(stage) for stage in self.repo.index] return [StageInfo(stage) for stage in self.repo.index]
target = as_posix(target)
stages, file, _ = _collect_specific_target( stages, file, _ = _collect_specific_target(
self, target, with_deps, recursive, accept_group self, target, with_deps, recursive, accept_group
) )
@@ -423,7 +424,7 @@ class StageLoad:
if not (recursive and self.fs.isdir(target)): if not (recursive and self.fs.isdir(target)):
try: try:
(out,) = self.repo.find_outs_by_path(target, strict=False) (out,) = self.repo.find_outs_by_path(target, strict=False)
return [StageInfo(out.stage, os.path.abspath(target))] return [StageInfo(out.stage, self.fs.path.abspath(target))]
except OutputNotFoundError: except OutputNotFoundError:
pass pass
@@ -467,7 +468,7 @@ class StageLoad:
from dvc.fs.local import LocalFileSystem from dvc.fs.local import LocalFileSystem
scm = self.repo.scm scm = self.repo.scm
sep = os.sep sep = self.fs.sep
outs: Set[str] = set() outs: Set[str] = set()
is_local_fs = isinstance(self.fs, LocalFileSystem) is_local_fs = isinstance(self.fs, LocalFileSystem)
@@ -493,7 +494,7 @@ class StageLoad:
for root, dirs, files in walk_iter: for root, dirs, files in walk_iter:
dvcfile_filter = partial(is_dvcfile_and_not_ignored, root) dvcfile_filter = partial(is_dvcfile_and_not_ignored, root)
for file in filter(dvcfile_filter, files): for file in filter(dvcfile_filter, files):
file_path = os.path.join(root, file) file_path = self.fs.path.join(root, file)
try: try:
new_stages = self.load_file(file_path) new_stages = self.load_file(file_path)
except DvcException as exc: except DvcException as exc:
+4 -2
View File
@@ -77,7 +77,7 @@ class StageLoader(Mapping):
assert stage_data and isinstance(stage_data, dict) assert stage_data and isinstance(stage_data, dict)
path, wdir = resolve_paths( path, wdir = resolve_paths(
dvcfile.path, stage_data.get(Stage.PARAM_WDIR) dvcfile.repo.fs, dvcfile.path, stage_data.get(Stage.PARAM_WDIR)
) )
stage = loads_from(PipelineStage, dvcfile.repo, path, wdir, stage_data) stage = loads_from(PipelineStage, dvcfile.repo, path, wdir, stage_data)
stage.name = name stage.name = name
@@ -184,7 +184,9 @@ class SingleStageLoader(Mapping):
@classmethod @classmethod
def load_stage(cls, dvcfile, d, stage_text): def load_stage(cls, dvcfile, d, stage_text):
path, wdir = resolve_paths(dvcfile.path, d.get(Stage.PARAM_WDIR)) path, wdir = resolve_paths(
dvcfile.repo.fs, dvcfile.path, d.get(Stage.PARAM_WDIR)
)
stage = loads_from(Stage, dvcfile.repo, path, wdir, d) stage = loads_from(Stage, dvcfile.repo, path, wdir, d)
stage._stage_text = stage_text # noqa, pylint:disable=protected-access stage._stage_text = stage_text # noqa, pylint:disable=protected-access
stage.deps = dependency.loadd_from( stage.deps = dependency.loadd_from(
+3 -3
View File
@@ -210,10 +210,10 @@ def resolve_wdir(wdir, path):
return pathlib.PurePath(rel_wdir).as_posix() if rel_wdir != "." else None return pathlib.PurePath(rel_wdir).as_posix() if rel_wdir != "." else None
def resolve_paths(path, wdir=None): def resolve_paths(fs, path, wdir=None):
path = os.path.abspath(path) path = fs.path.abspath(path)
wdir = wdir or os.curdir wdir = wdir or os.curdir
wdir = os.path.abspath(os.path.join(os.path.dirname(path), wdir)) wdir = fs.path.abspath(fs.path.join(fs.path.dirname(path), wdir))
return path, wdir return path, wdir
+1 -1
View File
@@ -332,7 +332,7 @@ def relpath(path, start=os.curdir):
return os.path.relpath(path, start) return os.path.relpath(path, start)
def as_posix(path): def as_posix(path: str) -> str:
import ntpath import ntpath
import posixpath import posixpath
+1 -1
View File
@@ -72,7 +72,7 @@ install_requires =
aiohttp-retry>=2.4.5 aiohttp-retry>=2.4.5
diskcache>=5.2.1 diskcache>=5.2.1
jaraco.windows>=5.7.0; python_version < '3.8' and sys_platform == 'win32' jaraco.windows>=5.7.0; python_version < '3.8' and sys_platform == 'win32'
scmrepo==0.0.19 scmrepo==0.0.22
dvc-render==0.0.5 dvc-render==0.0.5
dvclive>=0.7.3 dvclive>=0.7.3
+13 -15
View File
@@ -33,7 +33,7 @@ def test_new_simple(tmp_dir, scm, dvc, exp_stage, mocker, name, workspace):
new_mock.assert_called_once() new_mock.assert_called_once()
fs = scm.get_fs(exp) fs = scm.get_fs(exp)
with fs.open(tmp_dir / "metrics.yaml", mode="r", encoding="utf-8") as fobj: with fs.open("metrics.yaml", mode="r", encoding="utf-8") as fobj:
assert fobj.read().strip() == "foo: 2" assert fobj.read().strip() == "foo: 2"
if workspace: if workspace:
@@ -75,7 +75,7 @@ def test_experiment_exists(tmp_dir, scm, dvc, exp_stage, mocker, workspace):
exp = first(results) exp = first(results)
fs = scm.get_fs(exp) fs = scm.get_fs(exp)
with fs.open(tmp_dir / "metrics.yaml", mode="r", encoding="utf-8") as fobj: with fs.open("metrics.yaml", mode="r", encoding="utf-8") as fobj:
assert fobj.read().strip() == "foo: 3" assert fobj.read().strip() == "foo: 3"
@@ -145,7 +145,7 @@ def test_modify_params(tmp_dir, scm, dvc, mocker, changes, expected):
new_mock.assert_called_once() new_mock.assert_called_once()
fs = scm.get_fs(exp) fs = scm.get_fs(exp)
with fs.open(tmp_dir / "metrics.yaml", mode="r") as fobj: with fs.open("metrics.yaml", mode="r") as fobj:
assert fobj.read().strip() == expected assert fobj.read().strip() == expected
@@ -263,9 +263,9 @@ def test_update_py_params(tmp_dir, scm, dvc):
exp_a = first(results) exp_a = first(results)
fs = scm.get_fs(exp_a) fs = scm.get_fs(exp_a)
with fs.open(tmp_dir / "params.py", mode="r", encoding="utf-8") as fobj: with fs.open("params.py", mode="r", encoding="utf-8") as fobj:
assert fobj.read().strip() == "INT = 2" assert fobj.read().strip() == "INT = 2"
with fs.open(tmp_dir / "metrics.py", mode="r", encoding="utf-8") as fobj: with fs.open("metrics.py", mode="r", encoding="utf-8") as fobj:
assert fobj.read().strip() == "INT = 2" assert fobj.read().strip() == "INT = 2"
tmp_dir.gen( tmp_dir.gen(
@@ -309,9 +309,9 @@ def test_update_py_params(tmp_dir, scm, dvc):
return text.replace("\r\n", "\n") return text.replace("\r\n", "\n")
fs = scm.get_fs(exp_a) fs = scm.get_fs(exp_a)
with fs.open(tmp_dir / "params.py", mode="r", encoding="utf-8") as fobj: with fs.open("params.py", mode="r", encoding="utf-8") as fobj:
assert _dos2unix(fobj.read().strip()) == result assert _dos2unix(fobj.read().strip()) == result
with fs.open(tmp_dir / "metrics.py", mode="r", encoding="utf-8") as fobj: with fs.open("metrics.py", mode="r", encoding="utf-8") as fobj:
assert _dos2unix(fobj.read().strip()) == result assert _dos2unix(fobj.read().strip()) == result
tmp_dir.gen("params.py", "INT = 1\n") tmp_dir.gen("params.py", "INT = 1\n")
@@ -438,7 +438,7 @@ def test_untracked(tmp_dir, scm, dvc, caplog, workspace):
assert fs.exists("dvc.yaml") assert fs.exists("dvc.yaml")
assert fs.exists("dvc.lock") assert fs.exists("dvc.lock")
assert fs.exists("copy.py") assert fs.exists("copy.py")
with fs.open(tmp_dir / "metrics.yaml", mode="r", encoding="utf-8") as fobj: with fs.open("metrics.yaml", mode="r", encoding="utf-8") as fobj:
assert fobj.read().strip() == "foo: 2" assert fobj.read().strip() == "foo: 2"
@@ -515,8 +515,8 @@ def test_subdir(tmp_dir, scm, dvc, workspace):
fs = scm.get_fs(exp) fs = scm.get_fs(exp)
for fname in ["metrics.yaml", "dvc.lock"]: for fname in ["metrics.yaml", "dvc.lock"]:
assert fs.exists(subdir / fname) assert fs.exists(f"dir/{fname}")
with fs.open(subdir / "metrics.yaml", mode="r", encoding="utf-8") as fobj: with fs.open("dir/metrics.yaml", mode="r", encoding="utf-8") as fobj:
assert fobj.read().strip() == "foo: 2" assert fobj.read().strip() == "foo: 2"
assert dvc.experiments.get_exact_name(exp) == ref_info.name assert dvc.experiments.get_exact_name(exp) == ref_info.name
@@ -560,8 +560,8 @@ def test_subrepo(tmp_dir, scm, workspace):
fs = scm.get_fs(exp) fs = scm.get_fs(exp)
for fname in ["metrics.yaml", "dvc.lock"]: for fname in ["metrics.yaml", "dvc.lock"]:
assert fs.exists(subrepo / fname) assert fs.exists(f"dir/repo/{fname}")
with fs.open(subrepo / "metrics.yaml", mode="r", encoding="utf-8") as fobj: with fs.open("dir/repo/metrics.yaml", mode="r", encoding="utf-8") as fobj:
assert fobj.read().strip() == "foo: 2" assert fobj.read().strip() == "foo: 2"
assert subrepo.dvc.experiments.get_exact_name(exp) == ref_info.name assert subrepo.dvc.experiments.get_exact_name(exp) == ref_info.name
@@ -582,9 +582,7 @@ def test_queue(tmp_dir, scm, dvc, exp_stage, mocker):
metrics = set() metrics = set()
for exp in results: for exp in results:
fs = scm.get_fs(exp) fs = scm.get_fs(exp)
with fs.open( with fs.open("metrics.yaml", mode="r", encoding="utf-8") as fobj:
tmp_dir / "metrics.yaml", mode="r", encoding="utf-8"
) as fobj:
metrics.add(fobj.read().strip()) metrics.add(fobj.read().strip())
assert expected == metrics assert expected == metrics
+4 -5
View File
@@ -1,4 +1,3 @@
import os
from copy import deepcopy from copy import deepcopy
import pytest import pytest
@@ -71,13 +70,13 @@ def test_load_vars_from_file(tmp_dir, dvc):
def test_load_vars_with_relpath(tmp_dir, scm, dvc): def test_load_vars_with_relpath(tmp_dir, scm, dvc):
tmp_dir.scm_gen(DEFAULT_PARAMS_FILE, dumps_yaml(DATA), commit="add params") tmp_dir.scm_gen(DEFAULT_PARAMS_FILE, dumps_yaml(DATA), commit="add params")
subdir = tmp_dir / "subdir"
d = {"vars": [os.path.relpath(tmp_dir / DEFAULT_PARAMS_FILE, subdir)]}
revisions = ["HEAD", "workspace"] revisions = ["HEAD", "workspace"]
for rev in dvc.brancher(revs=["HEAD"]): for rev in dvc.brancher(revs=["HEAD"]):
assert rev == revisions.pop() assert rev == revisions.pop()
resolver = DataResolver(dvc, subdir.fs_path, d) d = {
"vars": [f"../{DEFAULT_PARAMS_FILE}"],
}
resolver = DataResolver(dvc, "subdir", d)
assert resolver.context == deepcopy(DATA) assert resolver.context == deepcopy(DATA)
+1 -1
View File
@@ -198,7 +198,7 @@ def test_walk_dont_ignore_subrepos(tmp_dir, scm, dvc):
get_dirs = itemgetter(1) get_dirs = itemgetter(1)
assert set(get_dirs(next(dvc_fs.walk(path)))) == {".dvc", "subdir", ".git"} assert set(get_dirs(next(dvc_fs.walk(path)))) == {".dvc", "subdir", ".git"}
assert set(get_dirs(next(scm_fs.walk(path)))) == {".dvc", "subdir"} assert set(get_dirs(next(scm_fs.walk("/")))) == {".dvc", "subdir"}
def test_fs_size(dvc, cloud): def test_fs_size(dvc, cloud):
+6 -21
View File
@@ -111,6 +111,7 @@ def test_ignore_collecting_dvcignores(tmp_dir, dvc, dname):
assert ( assert (
DvcIgnorePatterns( DvcIgnorePatterns(
*merge_patterns( *merge_patterns(
os.path,
_to_pattern_info_list([".hg/", ".git/", ".git", ".dvc/"]), _to_pattern_info_list([".hg/", ".git/", ".git", ".dvc/"]),
os.fspath(tmp_dir), os.fspath(tmp_dir),
_to_pattern_info_list([os.path.basename(dname)]), _to_pattern_info_list([os.path.basename(dname)]),
@@ -141,7 +142,8 @@ def test_ignore_on_branch(tmp_dir, scm, dvc):
} }
dvc.fs = GitFileSystem(scm=scm, rev="branch") dvc.fs = GitFileSystem(scm=scm, rev="branch")
assert dvc.dvcignore.is_ignored_file((tmp_dir / "foo").fs_path) dvc.root_dir = "/"
assert dvc.dvcignore.is_ignored_file("/foo")
def test_match_nested(tmp_dir, dvc): def test_match_nested(tmp_dir, dvc):
@@ -177,26 +179,6 @@ def test_ignore_external(tmp_dir, scm, dvc, tmp_path_factory):
) )
def test_ignore_subrepo(tmp_dir, scm, dvc):
tmp_dir.gen({".dvcignore": "foo", "subdir": {"foo": "foo"}})
scm.add([".dvcignore"])
scm.commit("init parent dvcignore")
dvc._reset()
subrepo_dir = tmp_dir / "subdir"
result = walk_files(dvc, dvc.fs, subrepo_dir)
assert set(result) == set()
with subrepo_dir.chdir():
subrepo = Repo.init(subdir=True)
scm.add(str(subrepo_dir / "foo"))
scm.commit("subrepo init")
for _ in subrepo.brancher(all_commits=True):
assert subrepo.fs.exists(subrepo_dir / "foo")
def test_ignore_resurface_subrepo(tmp_dir, scm, dvc): def test_ignore_resurface_subrepo(tmp_dir, scm, dvc):
tmp_dir.dvc_gen({"foo": "foo"}, commit="add foo") tmp_dir.dvc_gen({"foo": "foo"}, commit="add foo")
subrepo_dir = tmp_dir / "subdir" subrepo_dir = tmp_dir / "subdir"
@@ -358,16 +340,19 @@ def test_pattern_trie_fs(tmp_dir, dvc):
os.fspath(tmp_dir), os.fspath(tmp_dir),
) )
first_pattern = merge_patterns( first_pattern = merge_patterns(
os.path,
*base_pattern, *base_pattern,
_to_pattern_info_list(["a", "b", "c"]), _to_pattern_info_list(["a", "b", "c"]),
os.fspath(tmp_dir / "top" / "first"), os.fspath(tmp_dir / "top" / "first"),
) )
second_pattern = merge_patterns( second_pattern = merge_patterns(
os.path,
*first_pattern, *first_pattern,
_to_pattern_info_list(["d", "e", "f"]), _to_pattern_info_list(["d", "e", "f"]),
os.fspath(tmp_dir / "top" / "first" / "middle" / "second"), os.fspath(tmp_dir / "top" / "first" / "middle" / "second"),
) )
other_pattern = merge_patterns( other_pattern = merge_patterns(
os.path,
*base_pattern, *base_pattern,
_to_pattern_info_list(["1", "2", "3"]), _to_pattern_info_list(["1", "2", "3"]),
os.fspath(tmp_dir / "other"), os.fspath(tmp_dir / "other"),
+4 -14
View File
@@ -3,14 +3,6 @@ import os
import pytest import pytest
from dvc.exceptions import NoOutputOrStageError
from dvc.stage.exceptions import StageFileDoesNotExistError
gitfs_xfail = pytest.mark.xfail(
raises=(NoOutputOrStageError, StageFileDoesNotExistError),
reason="gitfs works on absolute paths only",
)
@pytest.mark.parametrize( @pytest.mark.parametrize(
"stage_wdir, cwd, target", "stage_wdir, cwd, target",
@@ -19,23 +11,21 @@ gitfs_xfail = pytest.mark.xfail(
(os.curdir, os.curdir, "train"), (os.curdir, os.curdir, "train"),
(os.curdir, os.curdir, "dvc.yaml:train"), (os.curdir, os.curdir, "dvc.yaml:train"),
(os.curdir, "sub", os.path.join(os.pardir, "foo")), (os.curdir, "sub", os.path.join(os.pardir, "foo")),
pytest.param( (
os.curdir, os.curdir,
"sub", "sub",
os.path.join(os.pardir, "dvc.yaml:train"), os.path.join(os.pardir, "dvc.yaml:train"),
marks=gitfs_xfail,
), ),
("sub", os.curdir, os.path.join("sub", "foo")), ("sub", os.curdir, os.path.join("sub", "foo")),
("sub", os.curdir, os.path.join("sub", "dvc.yaml:train")), ("sub", os.curdir, os.path.join("sub", "dvc.yaml:train")),
("sub", "sub", "foo"), ("sub", "sub", "foo"),
pytest.param("sub", "sub", "train", marks=gitfs_xfail), ("sub", "sub", "train"),
pytest.param("sub", "sub", "dvc.yaml:train", marks=gitfs_xfail), ("sub", "sub", "dvc.yaml:train"),
("sub", "dir", os.path.join(os.pardir, "sub", "foo")), ("sub", "dir", os.path.join(os.pardir, "sub", "foo")),
pytest.param( (
"sub", "sub",
"dir", "dir",
os.path.join(os.pardir, "sub", "dvc.yaml:train"), os.path.join(os.pardir, "sub", "dvc.yaml:train"),
marks=gitfs_xfail,
), ),
], ],
) )
+6 -3
View File
@@ -1,5 +1,6 @@
import os import os
from dvc.fs.local import localfs
from dvc.stage.utils import resolve_paths from dvc.stage.utils import resolve_paths
@@ -7,14 +8,16 @@ def test_resolve_paths():
p = os.path.join("dir", "subdir") p = os.path.join("dir", "subdir")
file_path = os.path.join(p, "dvc.yaml") file_path = os.path.join(p, "dvc.yaml")
path, wdir = resolve_paths(path=file_path, wdir="dir") path, wdir = resolve_paths(fs=localfs, path=file_path, wdir="dir")
assert path == os.path.abspath(file_path) assert path == os.path.abspath(file_path)
assert wdir == os.path.abspath(os.path.join(p, "dir")) assert wdir == os.path.abspath(os.path.join(p, "dir"))
path, wdir = resolve_paths(path=file_path) path, wdir = resolve_paths(fs=localfs, path=file_path)
assert path == os.path.abspath(file_path) assert path == os.path.abspath(file_path)
assert wdir == os.path.abspath(p) assert wdir == os.path.abspath(p)
path, wdir = resolve_paths(path=file_path, wdir="../../some-dir") path, wdir = resolve_paths(
fs=localfs, path=file_path, wdir="../../some-dir"
)
assert path == os.path.abspath(file_path) assert path == os.path.abspath(file_path)
assert wdir == os.path.abspath("some-dir") assert wdir == os.path.abspath("some-dir")
+2 -1
View File
@@ -31,12 +31,13 @@ def test_hook_is_called(tmp_dir, erepo_dir, mocker):
list(repo.repo_fs.walk("", ignore_subrepos=False)) # drain list(repo.repo_fs.walk("", ignore_subrepos=False)) # drain
assert spy.call_count == len(subrepos) assert spy.call_count == len(subrepos)
paths = [os.path.join(repo.root_dir, path) for path in subrepo_paths] paths = ["/" + path.replace("\\", "/") for path in subrepo_paths]
spy.assert_has_calls( spy.assert_has_calls(
[ [
call( call(
path, path,
fs=repo.fs, fs=repo.fs,
scm=repo.scm,
repo_factory=repo.repo_fs.fs.repo_factory, repo_factory=repo.repo_fs.fs.repo_factory,
) )
for path in paths for path in paths
+1
View File
@@ -11,6 +11,7 @@ def mock_dvcignore(dvcignore_path, patterns):
fs = MagicMock() fs = MagicMock()
fs.path = localfs.path fs.path = localfs.path
fs.sep = localfs.sep
with patch.object(fs, "open", mock_open(read_data="\n".join(patterns))): with patch.object(fs, "open", mock_open(read_data="\n".join(patterns))):
ignore_patterns = DvcIgnorePatterns.from_file( ignore_patterns = DvcIgnorePatterns.from_file(
dvcignore_path, fs, "mocked" dvcignore_path, fs, "mocked"