mirror of
https://github.com/Stone-Red-Code/dvc.git
synced 2026-11-02 07:18:25 +01:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
736ca9a5e5 | ||
|
|
cae1b1b647 | ||
|
|
3783d65dde | ||
|
|
0342e4f1c3 | ||
|
|
fc9af7a976 | ||
|
|
fea59cfc52 | ||
|
|
31ffce4fba | ||
|
|
2da959acd1 | ||
|
|
72a42fce20 | ||
|
|
1cbe64face | ||
|
|
057b0112ea | ||
|
|
1805edbd32 | ||
|
|
1723abcd79 | ||
|
|
3081c60414 | ||
|
|
152991d878 | ||
|
|
8098fd109d |
@@ -32,7 +32,7 @@ repos:
|
||||
hooks:
|
||||
- id: isort
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: 'v0.0.282'
|
||||
rev: 'v0.0.285'
|
||||
hooks:
|
||||
- id: ruff
|
||||
args: [--fix, --exit-non-zero-on-fix]
|
||||
|
||||
@@ -27,10 +27,6 @@ class CmdExperimentsSave(CmdBase):
|
||||
else:
|
||||
name = self.repo.experiments.get_exact_name([ref])[ref]
|
||||
ui.write(f"Experiment has been saved as: {name}")
|
||||
ui.write(
|
||||
"\nTo promote an experiment to a Git branch run:\n\n"
|
||||
"\tdvc exp branch <exp> <branch>\n"
|
||||
)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
@@ -11,7 +11,18 @@ from dvc.ui import ui
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _prettify(entries, with_color=False):
|
||||
def _format_entry(entry, fmt):
|
||||
from dvc.utils.humanize import naturalsize
|
||||
|
||||
size = entry.get("size")
|
||||
if size is None:
|
||||
size = ""
|
||||
else:
|
||||
size = naturalsize(size)
|
||||
return size, fmt(entry)
|
||||
|
||||
|
||||
def show_entries(entries, with_color=False, with_size=False):
|
||||
if with_color:
|
||||
ls_colors = LsColors()
|
||||
fmt = ls_colors.format
|
||||
@@ -20,7 +31,12 @@ def _prettify(entries, with_color=False):
|
||||
def fmt(entry):
|
||||
return entry["path"]
|
||||
|
||||
return [fmt(entry) for entry in entries]
|
||||
if with_size:
|
||||
ui.table([_format_entry(entry, fmt) for entry in entries])
|
||||
return
|
||||
|
||||
# NOTE: this is faster than ui.table for very large number of entries
|
||||
ui.write("\n".join(fmt(entry) for entry in entries))
|
||||
|
||||
|
||||
class CmdList(CmdBaseNoRepo):
|
||||
@@ -41,8 +57,7 @@ class CmdList(CmdBaseNoRepo):
|
||||
if self.args.json:
|
||||
ui.write_json(entries)
|
||||
elif entries:
|
||||
entries = _prettify(entries, with_color=True)
|
||||
ui.write("\n".join(entries))
|
||||
show_entries(entries, with_color=True, with_size=self.args.size)
|
||||
return 0
|
||||
except DvcException:
|
||||
logger.exception("failed to list '%s'", self.args.url)
|
||||
@@ -106,6 +121,11 @@ def add_parser(subparsers, parent_parser):
|
||||
"specified by '--remote') in the target repository."
|
||||
),
|
||||
)
|
||||
list_parser.add_argument(
|
||||
"--size",
|
||||
action="store_true",
|
||||
help="Show sizes.",
|
||||
)
|
||||
list_parser.add_argument(
|
||||
"path",
|
||||
nargs="?",
|
||||
|
||||
@@ -3,9 +3,8 @@ import logging
|
||||
|
||||
from dvc.cli.command import CmdBaseNoRepo
|
||||
from dvc.cli.utils import append_doc_link
|
||||
from dvc.ui import ui
|
||||
|
||||
from .ls import _prettify
|
||||
from .ls import show_entries
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -16,8 +15,7 @@ class CmdListUrl(CmdBaseNoRepo):
|
||||
|
||||
entries = Repo.ls_url(self.args.url, recursive=self.args.recursive)
|
||||
if entries:
|
||||
entries = _prettify(entries, with_color=True)
|
||||
ui.write("\n".join(entries))
|
||||
show_entries(entries, with_color=True, with_size=self.args.size)
|
||||
return 0
|
||||
|
||||
|
||||
@@ -40,4 +38,9 @@ def add_parser(subparsers, parent_parser):
|
||||
action="store_true",
|
||||
help="Recursively list files.",
|
||||
)
|
||||
lsurl_parser.add_argument(
|
||||
"--size",
|
||||
action="store_true",
|
||||
help="Show sizes.",
|
||||
)
|
||||
lsurl_parser.set_defaults(func=CmdListUrl)
|
||||
|
||||
@@ -30,8 +30,8 @@ class CmdRemoteAdd(CmdRemote):
|
||||
with self.config.edit(self.args.level) as conf:
|
||||
if self.args.name in conf["remote"] and not self.args.force:
|
||||
raise ConfigError(
|
||||
"remote '{}' already exists. Use `-f|--force` to "
|
||||
"overwrite it.".format(self.args.name)
|
||||
f"remote '{self.args.name}' already exists. Use `-f|--force` to "
|
||||
"overwrite it."
|
||||
)
|
||||
|
||||
conf["remote"][self.args.name] = {"url": self.args.url}
|
||||
|
||||
@@ -99,6 +99,7 @@ class Config(dict):
|
||||
): # pylint: disable=super-init-not-called
|
||||
from dvc.fs import LocalFileSystem
|
||||
|
||||
dvc_dir = os.fspath(dvc_dir) if dvc_dir else None
|
||||
self.dvc_dir = dvc_dir
|
||||
self.wfs = LocalFileSystem()
|
||||
self.fs = fs or self.wfs
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from copy import deepcopy
|
||||
from typing import TYPE_CHECKING, Dict, Optional, Union
|
||||
|
||||
from voluptuous import Any, Required
|
||||
@@ -24,12 +25,12 @@ class RepoDependency(Dependency):
|
||||
Required(PARAM_URL): str,
|
||||
PARAM_REV: str,
|
||||
PARAM_REV_LOCK: str,
|
||||
PARAM_CONFIG: str,
|
||||
PARAM_CONFIG: Any(str, dict),
|
||||
PARAM_REMOTE: Any(str, dict),
|
||||
}
|
||||
}
|
||||
|
||||
def __init__(self, def_repo: Dict[str, str], stage: "Stage", *args, **kwargs):
|
||||
def __init__(self, def_repo: Dict[str, Any], stage: "Stage", *args, **kwargs):
|
||||
self.def_repo = def_repo
|
||||
super().__init__(stage, *args, **kwargs)
|
||||
|
||||
@@ -107,17 +108,17 @@ class RepoDependency(Dependency):
|
||||
|
||||
rem = self.def_repo.get("remote")
|
||||
if isinstance(rem, dict):
|
||||
remote = None # type: ignore[unreachable]
|
||||
remote = None
|
||||
remote_config = rem
|
||||
else:
|
||||
remote = rem
|
||||
remote_config = None
|
||||
|
||||
conf = self.def_repo.get("config")
|
||||
if conf:
|
||||
config = Config.load_file(conf)
|
||||
conf = self.def_repo.get("config", {})
|
||||
if isinstance(conf, dict):
|
||||
config = deepcopy(conf)
|
||||
else:
|
||||
config = {}
|
||||
config = Config.load_file(conf)
|
||||
|
||||
config["cache"] = self.repo.config["cache"]
|
||||
config["cache"]["dir"] = self.repo.cache.local_cache_dir
|
||||
|
||||
+1
-1
@@ -196,7 +196,7 @@ class ETagMismatchError(DvcException):
|
||||
def __init__(self, etag, cached_etag):
|
||||
super().__init__(
|
||||
"ETag mismatch detected when copying file to cache! "
|
||||
"(expected: '{}', actual: '{}')".format(etag, cached_etag)
|
||||
f"(expected: '{etag}', actual: '{cached_etag}')"
|
||||
)
|
||||
|
||||
|
||||
|
||||
+4
-6
@@ -139,7 +139,7 @@ class _DVCFileSystem(AbstractFileSystem): # pylint:disable=abstract-method
|
||||
self.repo_factory = repo_factory
|
||||
|
||||
def _getcwd():
|
||||
relparts = ()
|
||||
relparts: Tuple[str, ...] = ()
|
||||
assert repo is not None
|
||||
if repo.fs.path.isin(repo.fs.path.getcwd(), repo.root_dir):
|
||||
relparts = repo.fs.path.relparts(repo.fs.path.getcwd(), repo.root_dir)
|
||||
@@ -163,6 +163,7 @@ class _DVCFileSystem(AbstractFileSystem): # pylint:disable=abstract-method
|
||||
self._datafss[key] = DataFileSystem(index=repo.index.data["repo"])
|
||||
|
||||
def _get_key(self, path: "StrPath") -> Key:
|
||||
path = os.fspath(path)
|
||||
parts = self.repo.fs.path.relparts(path, self.repo.root_dir)
|
||||
if parts == (os.curdir,):
|
||||
return ()
|
||||
@@ -261,15 +262,12 @@ class _DVCFileSystem(AbstractFileSystem): # pylint:disable=abstract-method
|
||||
try:
|
||||
return self.repo.fs.open(fs_path, mode=mode)
|
||||
except FileNotFoundError:
|
||||
repo, dvc_fs, subkey = self._get_subrepo_info(key)
|
||||
_, dvc_fs, subkey = self._get_subrepo_info(key)
|
||||
if not dvc_fs:
|
||||
raise
|
||||
|
||||
dvc_path = _get_dvc_path(dvc_fs, subkey)
|
||||
kw = {}
|
||||
if kwargs.get("cache_remote_stream", False):
|
||||
kw["cache_odb"] = repo.cache.local
|
||||
return dvc_fs.open(dvc_path, mode=mode, **kw)
|
||||
return dvc_fs.open(dvc_path, mode=mode, cache=kwargs.get("cache", False))
|
||||
|
||||
def isdvc(self, path, **kwargs) -> bool:
|
||||
"""Is this entry dvc-tracked?"""
|
||||
|
||||
+3
-1
@@ -148,7 +148,9 @@ class DvcIgnorePatterns(DvcIgnore):
|
||||
return bool(self.pattern_list)
|
||||
|
||||
|
||||
CheckIgnoreResult = namedtuple("CheckIgnoreResult", ["file", "match", "patterns"])
|
||||
CheckIgnoreResult = namedtuple( # noqa: PYI024
|
||||
"CheckIgnoreResult", ["file", "match", "patterns"]
|
||||
)
|
||||
|
||||
|
||||
def _no_match(path):
|
||||
|
||||
+5
-9
@@ -1142,7 +1142,7 @@ class Output:
|
||||
if not force and not prompt.confirm(msg.format(self.fs_path)):
|
||||
raise CollectCacheError( # noqa: B904
|
||||
"unable to fully collect used cache"
|
||||
" without cache for directory '{}'".format(self)
|
||||
f" without cache for directory '{self}'"
|
||||
)
|
||||
return None
|
||||
|
||||
@@ -1171,19 +1171,15 @@ class Output:
|
||||
|
||||
if not self.hash_info:
|
||||
msg = (
|
||||
"Output '{}'({}) is missing version info. "
|
||||
f"Output '{self}'({self.stage}) is missing version info. "
|
||||
"Cache for it will not be collected. "
|
||||
"Use `dvc repro` to get your pipeline up to date.".format(
|
||||
self, self.stage
|
||||
)
|
||||
"Use `dvc repro` to get your pipeline up to date."
|
||||
)
|
||||
if self.exists:
|
||||
msg += (
|
||||
"\n"
|
||||
"You can also use `dvc commit {stage.addressing}` "
|
||||
"to associate existing '{out}' with {stage}.".format(
|
||||
out=self, stage=self.stage
|
||||
)
|
||||
f"You can also use `dvc commit {self.stage.addressing}` "
|
||||
f"to associate existing '{self}' with {self.stage}."
|
||||
)
|
||||
logger.warning(msg)
|
||||
return {}
|
||||
|
||||
@@ -8,7 +8,7 @@ from pathspec.util import normalize_file
|
||||
|
||||
from dvc.utils import relpath
|
||||
|
||||
PatternInfo = namedtuple("PatternInfo", ["patterns", "file_info"])
|
||||
PatternInfo = namedtuple("PatternInfo", ["patterns", "file_info"]) # noqa: PYI024
|
||||
|
||||
|
||||
def _not_ignore(rule):
|
||||
|
||||
@@ -57,11 +57,11 @@ def brancher( # noqa: E302
|
||||
|
||||
from dvc.fs import LocalFileSystem
|
||||
|
||||
repo_root_parts = ()
|
||||
repo_root_parts: Tuple[str, ...] = ()
|
||||
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 = ()
|
||||
cwd_parts: Tuple[str, ...] = ()
|
||||
if self.fs.path.isin(self.fs.path.getcwd(), self.scm.root_dir):
|
||||
cwd_parts = self.fs.path.relparts(self.fs.path.getcwd(), self.scm.root_dir)
|
||||
|
||||
@@ -144,11 +144,11 @@ def switch(repo: "Repo", rev: str) -> Iterator[str]:
|
||||
if rev != "workspace":
|
||||
rev = resolve_rev(repo.scm, rev)
|
||||
|
||||
repo_root_parts = ()
|
||||
repo_root_parts: Tuple[str, ...] = ()
|
||||
if repo.fs.path.isin(repo.root_dir, repo.scm.root_dir):
|
||||
repo_root_parts = repo.fs.path.relparts(repo.root_dir, repo.scm.root_dir)
|
||||
|
||||
cwd_parts = ()
|
||||
cwd_parts: Tuple[str, ...] = ()
|
||||
if repo.fs.path.isin(repo.fs.path.getcwd(), repo.scm.root_dir):
|
||||
cwd_parts = repo.fs.path.relparts(repo.fs.path.getcwd(), repo.scm.root_dir)
|
||||
|
||||
|
||||
+9
-1
@@ -26,6 +26,14 @@ def check_missing(repo, rev=None, max_size=None, types=None):
|
||||
continue
|
||||
|
||||
if not fs.exists(path):
|
||||
ret.append(os.path.join(*entry.key))
|
||||
typ = "directory" if (entry.meta and entry.meta.isdir) else "file"
|
||||
ret.append(
|
||||
(
|
||||
typ,
|
||||
entry.hash_info.name,
|
||||
entry.hash_info.value,
|
||||
os.path.join(*entry.key),
|
||||
)
|
||||
)
|
||||
|
||||
return ret
|
||||
|
||||
@@ -81,7 +81,7 @@ def _check_can_delete(
|
||||
if cache_fs.exists(cache_path):
|
||||
continue
|
||||
|
||||
entry_paths.append(fs.path.join(path, *entry.key))
|
||||
entry_paths.append(fs.path.join(path, *(entry.key or ())))
|
||||
|
||||
if not entry_paths:
|
||||
return
|
||||
|
||||
@@ -210,10 +210,6 @@ class Experiments:
|
||||
)
|
||||
else:
|
||||
ui.write("Experiment results have been applied to your workspace.")
|
||||
ui.write(
|
||||
"\nTo promote an experiment to a Git branch run:\n\n"
|
||||
"\tdvc exp branch <exp> <branch>\n"
|
||||
)
|
||||
|
||||
def new(
|
||||
self,
|
||||
|
||||
@@ -129,7 +129,6 @@ class BaseExecutor(ABC):
|
||||
|
||||
PACKED_ARGS_FILE = "repro.dat"
|
||||
WARN_UNTRACKED = False
|
||||
QUIET = False
|
||||
INFOFILE_EXT = ".run"
|
||||
DEFAULT_LOCATION: str = "workspace"
|
||||
|
||||
@@ -602,8 +601,7 @@ class BaseExecutor(ABC):
|
||||
info.status = TaskStatus.RUNNING
|
||||
if infofile is not None:
|
||||
info.dump_json(infofile)
|
||||
if cls.QUIET:
|
||||
dvc.scm_context.quiet = cls.QUIET
|
||||
dvc.scm_context.quiet = True
|
||||
old_cwd = os.getcwd()
|
||||
|
||||
for path in copy_paths or []:
|
||||
|
||||
@@ -65,7 +65,6 @@ class TempDirExecutor(BaseLocalExecutor):
|
||||
# debugging user code), and suppress other DVC hints (like `git add`
|
||||
# suggestions) that are not applicable outside of workspace runs
|
||||
WARN_UNTRACKED = True
|
||||
QUIET = True
|
||||
DEFAULT_LOCATION = "tempdir"
|
||||
|
||||
@retry(180, errors=LockError, timeout=1)
|
||||
|
||||
@@ -13,16 +13,28 @@ def imp(
|
||||
if rev is not None:
|
||||
erepo["rev"] = rev
|
||||
|
||||
if remote and remote_config and isinstance(config, str):
|
||||
raise ValueError(
|
||||
"Can't specify config path together with both remote and remote_config"
|
||||
)
|
||||
|
||||
if config is not None:
|
||||
erepo["config"] = config
|
||||
|
||||
if remote is not None and remote_config is not None:
|
||||
conf = erepo.get("config") or {}
|
||||
|
||||
core = conf.get("core") or {}
|
||||
core["remote"] = remote
|
||||
|
||||
remotes = conf.get("remote") or {}
|
||||
remote_conf = remotes.get(remote) or {}
|
||||
remote_conf.update(remote_config)
|
||||
remotes[remote] = remote_conf
|
||||
|
||||
conf["core"] = core
|
||||
conf["remote"] = remotes
|
||||
|
||||
erepo["config"] = conf
|
||||
elif remote is not None:
|
||||
erepo["remote"] = remote
|
||||
|
||||
@@ -109,6 +109,7 @@ def _ls(
|
||||
"isout": dvc_info.get("isout", False),
|
||||
"isdir": info["type"] == "directory",
|
||||
"isexec": info.get("isexec", False),
|
||||
"size": info.get("size"),
|
||||
}
|
||||
|
||||
return ret
|
||||
|
||||
@@ -20,6 +20,7 @@ def ls_url(url, *, config=None, recursive=False):
|
||||
ls_info = {
|
||||
"path": fs.path.relpath(info["name"], fs_path),
|
||||
"isdir": info["type"] == "directory",
|
||||
"size": info.get("size"),
|
||||
}
|
||||
ret.append(ls_info)
|
||||
|
||||
|
||||
@@ -46,8 +46,8 @@ SUPPORTED_IMAGE_EXTENSIONS = ImageRenderer.EXTENSIONS
|
||||
class PlotMetricTypeError(DvcException):
|
||||
def __init__(self, file):
|
||||
super().__init__(
|
||||
"'{}' - file type error\n"
|
||||
"Only JSON, YAML, CSV and TSV formats are supported.".format(file)
|
||||
f"'{file}' - file type error\n"
|
||||
"Only JSON, YAML, CSV and TSV formats are supported."
|
||||
)
|
||||
|
||||
|
||||
@@ -212,7 +212,7 @@ class Plots:
|
||||
short_rev = "workspace"
|
||||
if rev := getattr(self.repo.fs, "rev", None):
|
||||
short_rev = rev[:7]
|
||||
_resolve_data_sources(data, short_rev, cache_remote_stream=True)
|
||||
_resolve_data_sources(data, short_rev, cache=True)
|
||||
result.update(data)
|
||||
|
||||
errored = errored_revisions(result)
|
||||
@@ -281,9 +281,7 @@ def _is_plot(out: "Output") -> bool:
|
||||
return bool(out.plot)
|
||||
|
||||
|
||||
def _resolve_data_sources(
|
||||
plots_data: Dict, rev: str, cache_remote_stream: bool = False
|
||||
):
|
||||
def _resolve_data_sources(plots_data: Dict, rev: str, cache: bool = False):
|
||||
from dvc.progress import Tqdm
|
||||
|
||||
values = list(plots_data.values())
|
||||
@@ -298,7 +296,7 @@ def _resolve_data_sources(
|
||||
def resolve(value):
|
||||
data_source = value.pop("data_source")
|
||||
assert callable(data_source)
|
||||
value.update(data_source(cache_remote_stream=cache_remote_stream))
|
||||
value.update(data_source(cache=cache))
|
||||
|
||||
if not to_resolve:
|
||||
return
|
||||
@@ -448,6 +446,7 @@ def _resolve_definitions(
|
||||
definitions: "DictStrAny",
|
||||
onerror: Optional[Callable[[Any], Any]] = None,
|
||||
):
|
||||
config_path = os.fspath(config_path)
|
||||
config_dir = fs.path.dirname(config_path)
|
||||
result: Dict[str, Dict] = {}
|
||||
for plot_id, plot_props in definitions.items():
|
||||
|
||||
@@ -98,7 +98,7 @@ class StageNameUnspecified(DvcException):
|
||||
def __init__(self, file):
|
||||
super().__init__(
|
||||
"Stage name not provided."
|
||||
"Please specify the name as: `{}:stage_name`".format(file.relpath)
|
||||
f"Please specify the name as: `{file.relpath}:stage_name`"
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -115,8 +115,8 @@ class TmpDir(pathlib.Path):
|
||||
def _require(self, name):
|
||||
if not hasattr(self, name):
|
||||
raise TypeError(
|
||||
"Can't use {name} for this temporary dir. "
|
||||
'Did you forget to use "{name}" fixture?'.format(name=name)
|
||||
f"Can't use {name} for this temporary dir. "
|
||||
f'Did you forget to use "{name}" fixture?'
|
||||
)
|
||||
|
||||
# Bootstrapping methods
|
||||
|
||||
@@ -31,3 +31,11 @@ def truncate_text(text: str, max_length: int, with_ellipsis: bool = True) -> str
|
||||
return text[: max_length - 1] + ELLIPSIS
|
||||
|
||||
return text[:max_length]
|
||||
|
||||
|
||||
def naturalsize(value: float, base: int = 1024) -> str:
|
||||
from tqdm import tqdm
|
||||
|
||||
if value < base:
|
||||
return f"{value:.0f}"
|
||||
return tqdm.format_sizeof(value, divisor=base)
|
||||
|
||||
+3
-3
@@ -34,7 +34,7 @@ dependencies = [
|
||||
"configobj>=5.0.6",
|
||||
"distro>=1.3",
|
||||
"dpath<3,>=2.1.0",
|
||||
"dvc-data>=2.14.0,<2.15.0",
|
||||
"dvc-data>=2.15.0,<2.16.0",
|
||||
"dvc-http>=2.29.0",
|
||||
"dvc-render>=0.3.1,<1",
|
||||
"dvc-studio-client>=0.9.2,<1",
|
||||
@@ -75,7 +75,7 @@ gdrive = ["dvc-gdrive==2.20"]
|
||||
gs = ["dvc-gs==2.22.1"]
|
||||
hdfs = ["dvc-hdfs==2.19"]
|
||||
lint = [
|
||||
"mypy==1.4.1",
|
||||
"mypy==1.5.1",
|
||||
"pylint==2.17.5",
|
||||
"types-colorama",
|
||||
"types-psutil",
|
||||
@@ -102,7 +102,7 @@ tests = [
|
||||
"flaky",
|
||||
"pytest<8,>=7",
|
||||
"pytest-cov>=4.1.0",
|
||||
"pytest-docker>=1,<2",
|
||||
"pytest-docker>=1,<3",
|
||||
"pytest-lazy-fixture",
|
||||
"pytest-mock",
|
||||
"pytest-test-utils",
|
||||
|
||||
@@ -658,3 +658,75 @@ def test_parameterized_repo(tmp_dir, dvc, scm, erepo_dir, paths):
|
||||
"url": os.fspath(erepo_dir),
|
||||
"rev_lock": erepo_dir.scm.get_rev(),
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"options, def_repo",
|
||||
[
|
||||
({"config": "myconfig"}, {"config": "myconfig"}),
|
||||
({"remote": "myremote"}, {"remote": "myremote"}),
|
||||
({"remote_config": {"key": "value"}}, {"remote": {"key": "value"}}),
|
||||
(
|
||||
{
|
||||
"remote": "myremote",
|
||||
"remote_config": {"key": "value"},
|
||||
},
|
||||
{
|
||||
"config": {
|
||||
"core": {"remote": "myremote"},
|
||||
"remote": {
|
||||
"myremote": {"key": "value"},
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
(
|
||||
{
|
||||
"remote": "myremote",
|
||||
"remote_config": {"key": "value"},
|
||||
"config": {"otherkey": "othervalue"},
|
||||
},
|
||||
{
|
||||
"config": {
|
||||
"core": {"remote": "myremote"},
|
||||
"remote": {
|
||||
"myremote": {"key": "value"},
|
||||
},
|
||||
"otherkey": "othervalue",
|
||||
},
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_import_configs(tmp_dir, scm, dvc, erepo_dir, options, def_repo):
|
||||
with erepo_dir.chdir():
|
||||
erepo_dir.dvc_gen("foo", "foo content", commit="create foo")
|
||||
|
||||
(tmp_dir / "myconfig").touch()
|
||||
|
||||
stage = dvc.imp(
|
||||
os.fspath(erepo_dir), "foo", "foo_imported", no_exec=True, **options
|
||||
)
|
||||
assert stage.deps[0].def_repo == {
|
||||
"url": os.fspath(erepo_dir),
|
||||
**def_repo,
|
||||
}
|
||||
|
||||
|
||||
def test_import_invalid_configs(tmp_dir, scm, dvc, erepo_dir):
|
||||
with erepo_dir.chdir():
|
||||
erepo_dir.dvc_gen("foo", "foo content", commit="create foo")
|
||||
|
||||
with pytest.raises(
|
||||
ValueError,
|
||||
match="Can't specify config path together with both remote and remote_config",
|
||||
):
|
||||
dvc.imp(
|
||||
os.fspath(erepo_dir),
|
||||
"foo",
|
||||
"foo_imported",
|
||||
no_exec=True,
|
||||
config="myconfig",
|
||||
remote="myremote",
|
||||
remote_config={"key": "value"},
|
||||
)
|
||||
|
||||
@@ -113,10 +113,7 @@ def test_init_when_ignored_by_git(tmp_dir, scm, caplog):
|
||||
with caplog.at_level(logging.ERROR, logger="dvc"):
|
||||
assert main(["init"]) == 1
|
||||
assert (
|
||||
"{dvc_dir} is ignored by your SCM tool. \n"
|
||||
f"{tmp_dir / DvcRepo.DVC_DIR} is ignored by your SCM tool. \n"
|
||||
"Make sure that it's tracked, "
|
||||
"for example, by adding '!.dvc' to .gitignore.".format(
|
||||
dvc_dir=tmp_dir / DvcRepo.DVC_DIR
|
||||
)
|
||||
in caplog.text
|
||||
"for example, by adding '!.dvc' to .gitignore." in caplog.text
|
||||
)
|
||||
|
||||
+57
-19
@@ -460,7 +460,7 @@ def test_ls_shows_pipeline_tracked_outs(tmp_dir, dvc, scm, run_copy):
|
||||
match_files(files, ((("bar",), True),))
|
||||
|
||||
|
||||
def test_ls_granular(erepo_dir):
|
||||
def test_ls_granular(erepo_dir, M):
|
||||
with erepo_dir.chdir():
|
||||
erepo_dir.dvc_gen(
|
||||
{
|
||||
@@ -475,15 +475,21 @@ def test_ls_granular(erepo_dir):
|
||||
|
||||
entries = Repo.ls(os.fspath(erepo_dir), os.path.join("dir", "subdir"))
|
||||
assert entries == [
|
||||
{"isout": True, "isdir": False, "isexec": False, "path": "bar"},
|
||||
{"isout": True, "isdir": False, "isexec": False, "path": "foo"},
|
||||
{"isout": True, "isdir": False, "isexec": False, "path": "bar", "size": 3},
|
||||
{"isout": True, "isdir": False, "isexec": False, "path": "foo", "size": 3},
|
||||
]
|
||||
|
||||
entries = Repo.ls(os.fspath(erepo_dir), "dir")
|
||||
assert entries == [
|
||||
{"isout": True, "isdir": False, "isexec": False, "path": "1"},
|
||||
{"isout": True, "isdir": False, "isexec": False, "path": "2"},
|
||||
{"isout": True, "isdir": True, "isexec": False, "path": "subdir"},
|
||||
{"isout": True, "isdir": False, "isexec": False, "path": "1", "size": 1},
|
||||
{"isout": True, "isdir": False, "isexec": False, "path": "2", "size": 1},
|
||||
{
|
||||
"isout": True,
|
||||
"isdir": True,
|
||||
"isexec": False,
|
||||
"path": "subdir",
|
||||
"size": M.instance_of(int),
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@@ -508,14 +514,14 @@ def test_ls_target(erepo_dir, use_scm):
|
||||
return Repo.ls(os.fspath(erepo_dir), path)
|
||||
|
||||
assert _ls(os.path.join("dir", "1")) == [
|
||||
{"isout": isout, "isdir": False, "isexec": False, "path": "1"}
|
||||
{"isout": isout, "isdir": False, "isexec": False, "path": "1", "size": 1}
|
||||
]
|
||||
assert _ls(os.path.join("dir", "subdir", "foo")) == [
|
||||
{"isout": isout, "isdir": False, "isexec": False, "path": "foo"}
|
||||
{"isout": isout, "isdir": False, "isexec": False, "path": "foo", "size": 3}
|
||||
]
|
||||
assert _ls(os.path.join("dir", "subdir")) == [
|
||||
{"isdir": False, "isexec": 0, "isout": isout, "path": "bar"},
|
||||
{"isdir": False, "isexec": 0, "isout": isout, "path": "foo"},
|
||||
{"isdir": False, "isexec": 0, "isout": isout, "path": "bar", "size": 3},
|
||||
{"isdir": False, "isexec": 0, "isout": isout, "path": "foo", "size": 3},
|
||||
]
|
||||
|
||||
|
||||
@@ -559,7 +565,7 @@ def test_subrepo(dvc_top_level, erepo):
|
||||
assert _list_files(subrepo, "dvc_dir") == {"lorem"}
|
||||
|
||||
|
||||
def test_broken_symlink(tmp_dir, dvc):
|
||||
def test_broken_symlink(tmp_dir, dvc, M):
|
||||
from dvc.fs import system
|
||||
|
||||
tmp_dir.gen("file", "content")
|
||||
@@ -575,17 +581,19 @@ def test_broken_symlink(tmp_dir, dvc):
|
||||
"isdir": False,
|
||||
"isexec": False,
|
||||
"path": ".dvcignore",
|
||||
"size": M.instance_of(int),
|
||||
},
|
||||
{
|
||||
"isout": False,
|
||||
"isdir": False,
|
||||
"isexec": False,
|
||||
"path": "link",
|
||||
"size": 0,
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def test_ls_broken_dir(tmp_dir, dvc):
|
||||
def test_ls_broken_dir(tmp_dir, dvc, M):
|
||||
from dvc_data.index import DataIndexDirError
|
||||
|
||||
tmp_dir.dvc_gen(
|
||||
@@ -606,13 +614,43 @@ def test_ls_broken_dir(tmp_dir, dvc):
|
||||
|
||||
entries = Repo.ls(os.fspath(tmp_dir))
|
||||
assert entries == [
|
||||
{"isdir": False, "isexec": False, "isout": False, "path": ".dvcignore"},
|
||||
{"isdir": True, "isexec": False, "isout": True, "path": "broken"},
|
||||
{"isdir": False, "isexec": False, "isout": False, "path": "broken.dvc"},
|
||||
{"isdir": True, "isexec": False, "isout": True, "path": "dir"},
|
||||
{"isdir": False, "isexec": False, "isout": False, "path": "dir.dvc"},
|
||||
{"isdir": False, "isexec": False, "isout": True, "path": "foo"},
|
||||
{"isdir": False, "isexec": False, "isout": False, "path": "foo.dvc"},
|
||||
{
|
||||
"isdir": False,
|
||||
"isexec": False,
|
||||
"isout": False,
|
||||
"path": ".dvcignore",
|
||||
"size": M.instance_of(int),
|
||||
},
|
||||
{"isdir": True, "isexec": False, "isout": True, "path": "broken", "size": 3},
|
||||
{
|
||||
"isdir": False,
|
||||
"isexec": False,
|
||||
"isout": False,
|
||||
"path": "broken.dvc",
|
||||
"size": M.instance_of(int),
|
||||
},
|
||||
{
|
||||
"isdir": True,
|
||||
"isexec": False,
|
||||
"isout": True,
|
||||
"path": "dir",
|
||||
"size": M.instance_of(int),
|
||||
},
|
||||
{
|
||||
"isdir": False,
|
||||
"isexec": False,
|
||||
"isout": False,
|
||||
"path": "dir.dvc",
|
||||
"size": M.instance_of(int),
|
||||
},
|
||||
{"isdir": False, "isexec": False, "isout": True, "path": "foo", "size": 3},
|
||||
{
|
||||
"isdir": False,
|
||||
"isexec": False,
|
||||
"isout": False,
|
||||
"path": "foo.dvc",
|
||||
"size": M.instance_of(int),
|
||||
},
|
||||
]
|
||||
|
||||
with pytest.raises(DataIndexDirError):
|
||||
|
||||
@@ -45,10 +45,10 @@ def test_default(mocker, capsys, dvc):
|
||||
" file\n"
|
||||
"\n"
|
||||
"Renamed:\n"
|
||||
" data{sep}file_old -> data{sep}file_new\n"
|
||||
f" data{os.path.sep}file_old -> data{os.path.sep}file_new\n"
|
||||
"\n"
|
||||
"files summary: 1 added, 1 renamed"
|
||||
).format(sep=os.path.sep) in capsys.readouterr()[0]
|
||||
) in capsys.readouterr()[0]
|
||||
|
||||
|
||||
def test_show_hash(mocker, capsys, dvc):
|
||||
@@ -252,14 +252,14 @@ def test_show_markdown(capsys):
|
||||
"|--------------|----------------------|\n"
|
||||
"| added | file |\n"
|
||||
"| deleted | zoo |\n"
|
||||
"| deleted | data{sep} |\n"
|
||||
"| deleted | data{sep}foo |\n"
|
||||
"| deleted | data{sep}bar |\n"
|
||||
f"| deleted | data{os.path.sep} |\n"
|
||||
f"| deleted | data{os.path.sep}foo |\n"
|
||||
f"| deleted | data{os.path.sep}bar |\n"
|
||||
"| renamed | file_old -> file_new |\n"
|
||||
"| modified | file |\n"
|
||||
"| not in cache | file2 |\n"
|
||||
"\n"
|
||||
).format(sep=os.path.sep)
|
||||
)
|
||||
|
||||
|
||||
def test_show_markdown_with_hash(capsys):
|
||||
@@ -289,14 +289,14 @@ def test_show_markdown_with_hash(capsys):
|
||||
"|--------------|--------------------|----------------------|\n"
|
||||
"| added | 00000000 | file |\n"
|
||||
"| deleted | 22222 | zoo |\n"
|
||||
"| deleted | XXXXXXXX | data{sep} |\n"
|
||||
"| deleted | 11111111 | data{sep}foo |\n"
|
||||
"| deleted | 00000000 | data{sep}bar |\n"
|
||||
f"| deleted | XXXXXXXX | data{os.path.sep} |\n"
|
||||
f"| deleted | 11111111 | data{os.path.sep}foo |\n"
|
||||
f"| deleted | 00000000 | data{os.path.sep}bar |\n"
|
||||
"| renamed | 11111111 | file_old -> file_new |\n"
|
||||
"| modified | AAAAAAAA..BBBBBBBB | file |\n"
|
||||
"| not in cache | 12345678 | file2 |\n"
|
||||
"\n"
|
||||
).format(sep=os.path.sep)
|
||||
)
|
||||
|
||||
|
||||
def test_show_markdown_hide_missing(capsys):
|
||||
@@ -321,13 +321,13 @@ def test_show_markdown_hide_missing(capsys):
|
||||
"|----------|----------------------|\n"
|
||||
"| added | file |\n"
|
||||
"| deleted | zoo |\n"
|
||||
"| deleted | data{sep} |\n"
|
||||
"| deleted | data{sep}foo |\n"
|
||||
"| deleted | data{sep}bar |\n"
|
||||
f"| deleted | data{os.path.sep} |\n"
|
||||
f"| deleted | data{os.path.sep}foo |\n"
|
||||
f"| deleted | data{os.path.sep}bar |\n"
|
||||
"| renamed | file_old -> file_new |\n"
|
||||
"| modified | file |\n"
|
||||
"\n"
|
||||
).format(sep=os.path.sep)
|
||||
)
|
||||
|
||||
|
||||
def test_hide_missing(mocker, capsys, dvc):
|
||||
|
||||
@@ -25,18 +25,18 @@ def command_tuples():
|
||||
|
||||
main_parser = get_main_parser()
|
||||
recurse_parser(main_parser)
|
||||
|
||||
# the no. of commands will usually go up,
|
||||
# but if we ever remove commands and drop below, adjust the magic number accordingly
|
||||
assert len(commands) >= 96
|
||||
return sorted(commands)
|
||||
|
||||
|
||||
def pytest_generate_tests(metafunc):
|
||||
def ids(values):
|
||||
if values:
|
||||
return "-".join(values)
|
||||
return "dvc"
|
||||
|
||||
metafunc.parametrize("command_tuples", command_tuples(), ids=ids)
|
||||
def ids(values):
|
||||
return "-".join(values) or "dvc"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("command_tuples", command_tuples(), ids=ids)
|
||||
def test_help(caplog, capsys, command_tuples):
|
||||
with caplog.at_level(logging.INFO), pytest.raises(SystemExit) as e:
|
||||
main([*command_tuples, "--help"])
|
||||
|
||||
@@ -100,13 +100,14 @@ def test_scm_context_on_no_files_to_track(caplog, scm_context, autostage, quiet)
|
||||
assert not caplog.text
|
||||
|
||||
|
||||
def test_scm_context_remind_to_track(caplog, scm_context):
|
||||
with scm_context() as context:
|
||||
@pytest.mark.parametrize("quiet", [True, False])
|
||||
def test_scm_context_remind_to_track(caplog, scm_context, quiet):
|
||||
with scm_context(quiet=quiet) as context:
|
||||
context.track_file("foo")
|
||||
context.track_file("lorem ipsum")
|
||||
assert context.files_to_track == {"foo", "lorem ipsum"}
|
||||
|
||||
if isinstance(scm_context.scm, NoSCM):
|
||||
if quiet or isinstance(scm_context.scm, NoSCM):
|
||||
assert not caplog.text
|
||||
else:
|
||||
assert "To track the changes with git, run:" in caplog.text
|
||||
|
||||
@@ -80,10 +80,8 @@ def test_resolve_output(inp, out, is_dir, expected, mocker):
|
||||
["dvc.yaml:name", ("dvc.yaml", "name"), None],
|
||||
[":name", ("dvc.yaml", "name"), None],
|
||||
["stage.dvc", ("stage.dvc", None), None],
|
||||
["dvc.yaml:name", ("dvc.yaml", "name"), None],
|
||||
["../models/stage.dvc", ("../models/stage.dvc", None), "def"],
|
||||
[":name", ("default", "name"), "default"],
|
||||
[":name", ("default", "name"), "default"],
|
||||
["something.dvc:name", ("something.dvc", "name"), None],
|
||||
["../something.dvc:name", ("../something.dvc", "name"), None],
|
||||
["file", (None, "file"), None],
|
||||
|
||||
Reference in New Issue
Block a user