mirror of
https://github.com/Stone-Red-Code/dvc.git
synced 2026-11-02 23:09:28 +01:00
Compare commits
34
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dbd740f374 | ||
|
|
56e5982951 | ||
|
|
defe4062aa | ||
|
|
f74c1c0e70 | ||
|
|
b5dede14f2 | ||
|
|
8d37f966c3 | ||
|
|
95165cc39a | ||
|
|
ccb72126ea | ||
|
|
d5315c3827 | ||
|
|
8131c32c33 | ||
|
|
0013af99d4 | ||
|
|
06ff81cd7f | ||
|
|
e19b0874c0 | ||
|
|
b290eaea43 | ||
|
|
2a2b543dd6 | ||
|
|
356dfa0327 | ||
|
|
0f4e8a23c8 | ||
|
|
333edb2cbf | ||
|
|
a65523a86a | ||
|
|
fb42429107 | ||
|
|
e40e378fc9 | ||
|
|
b1aaeb94c0 | ||
|
|
dcf6f48fb3 | ||
|
|
c6c680d2b2 | ||
|
|
1e8962bf0c | ||
|
|
0f2361f218 | ||
|
|
e9c0a99083 | ||
|
|
3c71fdbb0d | ||
|
|
04cbd40df5 | ||
|
|
5725780790 | ||
|
|
2dc6261a66 | ||
|
|
0f73c0a725 | ||
|
|
ff8752c3dc | ||
|
|
dbc7531f0d |
@@ -14,11 +14,11 @@ jobs:
|
||||
name: run benchmarks
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-python@v6
|
||||
- uses: actions/setup-python@v7
|
||||
with:
|
||||
python-version: "3.14"
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.base.sha }}
|
||||
fetch-depth: 0
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
- name: run benchmarks on base branch
|
||||
run: pytest --benchmark-autosave dvc/testing/benchmarks/ -k 'test_init or test_help'
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
clean: false
|
||||
|
||||
@@ -19,11 +19,11 @@ jobs:
|
||||
name: Build & Verify package
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: actions/setup-python@v6
|
||||
- uses: actions/setup-python@v7
|
||||
with:
|
||||
python-version: "3.14"
|
||||
|
||||
@@ -41,7 +41,7 @@ jobs:
|
||||
- name: Check dist
|
||||
run: uv tool run twine check --strict dist/*
|
||||
|
||||
- uses: actions/upload-artifact@v6
|
||||
- uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: Packages
|
||||
path: dist/
|
||||
@@ -61,7 +61,7 @@ jobs:
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- uses: actions/download-artifact@v7
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: Packages
|
||||
path: dist
|
||||
@@ -86,7 +86,7 @@ jobs:
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- uses: actions/download-artifact@v7
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: Packages
|
||||
path: dist
|
||||
|
||||
@@ -28,13 +28,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v4
|
||||
uses: github/codeql-action/init@v4.38.2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
queries: security-extended
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v4
|
||||
uses: github/codeql-action/analyze@v4.38.2
|
||||
|
||||
@@ -23,18 +23,18 @@ jobs:
|
||||
plugin: ["dvc-s3"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
path: dvc
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
repository: treeverse/${{ matrix.plugin }}
|
||||
ref: main
|
||||
path: ${{ matrix.plugin }}
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v6
|
||||
uses: actions/setup-python@v7
|
||||
with:
|
||||
python-version: ${{ matrix.pyv }}
|
||||
|
||||
|
||||
@@ -26,11 +26,11 @@ jobs:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v6
|
||||
uses: actions/setup-python@v7
|
||||
with:
|
||||
python-version: '3.14'
|
||||
- uses: astral-sh/setup-uv@v7
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
UV_LINK_MODE: ${{ runner.os == 'Windows' && 'symlink' || 'hardlink' }}
|
||||
|
||||
- name: Cache mypy
|
||||
uses: actions/cache@v5
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: .mypy_cache
|
||||
key: mypy-${{ runner.os }}-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}
|
||||
@@ -130,11 +130,11 @@ jobs:
|
||||
pyv: "3.14"
|
||||
pytestargs: "--splitting-algorithm=least_duration --splits 4 --group 4"
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v6
|
||||
uses: actions/setup-python@v7
|
||||
with:
|
||||
python-version: ${{ matrix.pyv }}
|
||||
- uses: astral-sh/setup-uv@v7
|
||||
@@ -155,7 +155,7 @@ jobs:
|
||||
pytest ${{ matrix.pytestargs }} -n=logical --dist=worksteal --timeout=300 --durations=0
|
||||
--cov --cov-report=xml --cov-report=term --durations-path=./.github/.test_durations
|
||||
- name: upload coverage report
|
||||
uses: codecov/codecov-action@v5
|
||||
uses: codecov/codecov-action@v7
|
||||
check:
|
||||
if: always()
|
||||
needs: [tests]
|
||||
@@ -170,7 +170,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Slack Notification
|
||||
uses: rtCamp/action-slack-notify@v2.3.3
|
||||
uses: rtCamp/action-slack-notify@v2.4.0
|
||||
env:
|
||||
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
|
||||
SLACK_COLOR: ${{ job.status }}
|
||||
|
||||
@@ -22,13 +22,13 @@ repos:
|
||||
- id: sort-simple-yaml
|
||||
- id: trailing-whitespace
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: 'v0.14.10'
|
||||
rev: 'v0.15.20'
|
||||
hooks:
|
||||
- id: ruff-check
|
||||
args: [--fix, --exit-non-zero-on-fix]
|
||||
- id: ruff-format
|
||||
- repo: https://github.com/codespell-project/codespell
|
||||
rev: v2.4.1
|
||||
rev: v2.4.2
|
||||
hooks:
|
||||
- id: codespell
|
||||
additional_dependencies: ["tomli"]
|
||||
|
||||
@@ -8,7 +8,7 @@ from PyInstaller.utils.hooks import collect_submodules, is_module_or_submodule
|
||||
_EXCLUDES = ("celery.bin", "celery.contrib")
|
||||
hiddenimports = collect_submodules(
|
||||
"celery",
|
||||
filter=lambda name: not any(
|
||||
is_module_or_submodule(name, module) for module in _EXCLUDES
|
||||
filter=lambda name: (
|
||||
not any(is_module_or_submodule(name, module) for module in _EXCLUDES)
|
||||
),
|
||||
)
|
||||
|
||||
@@ -8,7 +8,6 @@ datas += copy_metadata("gcsfs")
|
||||
datas += copy_metadata("pyarrow")
|
||||
datas += copy_metadata("pydrive2")
|
||||
datas += copy_metadata("s3fs", recursive=True)
|
||||
datas += copy_metadata("boto3")
|
||||
datas += copy_metadata("ossfs")
|
||||
datas += copy_metadata("sshfs")
|
||||
datas += copy_metadata("webdav4")
|
||||
|
||||
+1
-1
@@ -147,7 +147,7 @@ def _git_remote_path_hash(scm: Optional["Base"]) -> Optional[str]:
|
||||
path = _parse_git_remote_path(remote_url)
|
||||
h = hashlib.md5(path.encode("utf-8"), usedforsecurity=False) # for FIPS
|
||||
return h.hexdigest()
|
||||
except Exception: # noqa: BLE001
|
||||
except Exception:
|
||||
logger.debug("Failed to get git remote path", exc_info=True)
|
||||
return None
|
||||
|
||||
|
||||
+11
-4
@@ -40,9 +40,16 @@ class CmdGC(CmdBase):
|
||||
msg += " and all git commits"
|
||||
else:
|
||||
if self.args.all_branches and self.args.all_tags:
|
||||
msg += " and all git branches and tags"
|
||||
if self.args.num:
|
||||
msg += f" and last {self.args.num} commits from all git branches"
|
||||
msg += " and all git tags"
|
||||
else:
|
||||
msg += " and all git branches and tags"
|
||||
elif self.args.all_branches:
|
||||
msg += " and all git branches"
|
||||
if self.args.num:
|
||||
msg += f" and last {self.args.num} commits from all git branches"
|
||||
else:
|
||||
msg += " and all git branches"
|
||||
elif self.args.all_tags:
|
||||
msg += " and all git tags"
|
||||
if self.args.commit_date:
|
||||
@@ -126,8 +133,8 @@ def add_parser(subparsers, parent_parser):
|
||||
metavar="<num>",
|
||||
help=(
|
||||
"Keep data files used in the last `num` commits "
|
||||
"starting from the `--rev` <commit>. "
|
||||
"Only used if `--rev` is also provided. "
|
||||
"starting from each selected revision root. "
|
||||
"Can be used with `--rev` and `--all-branches`. "
|
||||
"Defaults to `1`."
|
||||
),
|
||||
)
|
||||
|
||||
@@ -109,8 +109,6 @@ def _build_tree_structure(
|
||||
|
||||
|
||||
def show_tree(entries, with_color=False, with_size=False, with_hash=False):
|
||||
import tabulate
|
||||
|
||||
rows = _build_tree_structure(
|
||||
entries,
|
||||
with_color=with_color,
|
||||
@@ -119,13 +117,7 @@ def show_tree(entries, with_color=False, with_size=False, with_hash=False):
|
||||
)
|
||||
|
||||
colalign = ("right",) if with_size else None
|
||||
|
||||
_orig = tabulate.PRESERVE_WHITESPACE
|
||||
tabulate.PRESERVE_WHITESPACE = True
|
||||
try:
|
||||
ui.table(rows, colalign=colalign)
|
||||
finally:
|
||||
tabulate.PRESERVE_WHITESPACE = _orig
|
||||
ui.table(rows, colalign=colalign, preserve_whitespace=True)
|
||||
|
||||
|
||||
class CmdList(CmdBaseNoRepo):
|
||||
|
||||
@@ -41,18 +41,6 @@ class RemoteNotFoundError(RemoteConfigError):
|
||||
pass
|
||||
|
||||
|
||||
class MachineConfigError(ConfigError):
|
||||
pass
|
||||
|
||||
|
||||
class NoMachineError(MachineConfigError):
|
||||
pass
|
||||
|
||||
|
||||
class MachineNotFoundError(MachineConfigError):
|
||||
pass
|
||||
|
||||
|
||||
@memoize
|
||||
def get_compiled_schema():
|
||||
from voluptuous import Schema
|
||||
|
||||
+18
-3
@@ -5,7 +5,15 @@ from collections.abc import Iterable, Iterator
|
||||
from itertools import chain, groupby, takewhile
|
||||
from typing import TYPE_CHECKING, Any, Literal, NamedTuple, Optional, Union, overload
|
||||
|
||||
from pathspec.patterns import GitWildMatchPattern
|
||||
try:
|
||||
from pathspec.patterns.gitignore.spec import ( # type: ignore[import-not-found]
|
||||
GitIgnoreSpecPattern,
|
||||
)
|
||||
except ImportError: # pathspec<1
|
||||
from pathspec.patterns import (
|
||||
GitWildMatchPattern as GitIgnoreSpecPattern,
|
||||
)
|
||||
|
||||
from pathspec.util import normalize_file
|
||||
from pygtrie import Trie
|
||||
|
||||
@@ -34,7 +42,14 @@ class DvcIgnorePatterns(DvcIgnore):
|
||||
def __init__(
|
||||
self, pattern_list: Iterable[Union[PatternInfo, str]], dirname: str, sep: str
|
||||
) -> None:
|
||||
from pathspec.patterns.gitwildmatch import _DIR_MARK
|
||||
try:
|
||||
from pathspec.patterns.gitignore.spec import ( # type: ignore[import-not-found]
|
||||
_DIR_MARK,
|
||||
)
|
||||
except ImportError: # pathspec<1
|
||||
from pathspec.patterns.gitwildmatch import ( # type: ignore[attr-defined, no-redef]
|
||||
_DIR_MARK,
|
||||
)
|
||||
|
||||
pattern_infos = [
|
||||
pattern if isinstance(pattern, PatternInfo) else PatternInfo(pattern, "")
|
||||
@@ -48,7 +63,7 @@ class DvcIgnorePatterns(DvcIgnore):
|
||||
|
||||
regex_pattern_list: list[tuple[str, bool, bool, PatternInfo]] = []
|
||||
for count, pattern_info in enumerate(pattern_infos):
|
||||
regex, ignore = GitWildMatchPattern.pattern_to_regex(pattern_info.patterns)
|
||||
regex, ignore = GitIgnoreSpecPattern.pattern_to_regex(pattern_info.patterns)
|
||||
if regex is not None and ignore is not None:
|
||||
self.pattern_list.append(pattern_info)
|
||||
regex = regex.replace(f"<{_DIR_MARK}>", f"<{_DIR_MARK}{count}>")
|
||||
|
||||
@@ -37,13 +37,13 @@ KEYCRE = re.compile(
|
||||
def get_parser():
|
||||
from pyparsing import CharsNotIn, ParserElement, Suppress, ZeroOrMore
|
||||
|
||||
ParserElement.enablePackrat()
|
||||
ParserElement.enable_packrat()
|
||||
|
||||
word = CharsNotIn(f"{PERIOD}{LBRACK}{RBRACK}")
|
||||
idx = Suppress(LBRACK) + word + Suppress(RBRACK)
|
||||
attr = Suppress(PERIOD) + word
|
||||
parser = word + ZeroOrMore(attr ^ idx)
|
||||
parser.setParseAction(PERIOD.join)
|
||||
parser.set_parse_action(PERIOD.join)
|
||||
|
||||
return parser
|
||||
|
||||
@@ -178,7 +178,7 @@ def parse_expr(s: str):
|
||||
from pyparsing import ParseException
|
||||
|
||||
try:
|
||||
result = get_parser().parseString(s, parseAll=True)
|
||||
result = get_parser().parse_string(s, parse_all=True)
|
||||
except ParseException as exc:
|
||||
format_and_raise_parse_error(exc)
|
||||
raise AssertionError("unreachable") # noqa: B904
|
||||
|
||||
@@ -188,7 +188,7 @@ class Experiments:
|
||||
rev_names = self.get_exact_name(revs)
|
||||
for rev in revs:
|
||||
name = rev_names[rev]
|
||||
names.append(name if name else rev[:7])
|
||||
names.append(name or rev[:7])
|
||||
ui.write("\nRan experiment(s): {}".format(", ".join(names)))
|
||||
if tmp_dir:
|
||||
ui.write(
|
||||
|
||||
@@ -58,7 +58,7 @@ def collect_rev(
|
||||
if not (rev == "workspace" or param_deps or data.contains_error):
|
||||
cache.put(data, force=True)
|
||||
return ExpState(rev=rev, data=data)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
except Exception as exc:
|
||||
logger.debug("", exc_info=True)
|
||||
error = SerializableError(str(exc), type(exc).__name__)
|
||||
return ExpState(rev=rev, error=error)
|
||||
@@ -328,7 +328,7 @@ def collect(
|
||||
name=baseline_names.get(baseline_rev),
|
||||
data=baseline_data.data,
|
||||
error=baseline_data.error,
|
||||
experiments=experiments if experiments else None,
|
||||
experiments=experiments or None,
|
||||
)
|
||||
)
|
||||
return result
|
||||
|
||||
@@ -757,7 +757,7 @@ class BaseExecutor(ABC):
|
||||
logger.debug("Commit to current experiment branch '%s'", branch)
|
||||
else:
|
||||
baseline_rev = scm.get_ref(EXEC_BASELINE)
|
||||
name = exp_name if exp_name else f"exp-{exp_hash[:5]}"
|
||||
name = exp_name or f"exp-{exp_hash[:5]}"
|
||||
ref_info = ExpRefInfo(baseline_rev, name)
|
||||
branch = str(ref_info)
|
||||
old_ref = None
|
||||
|
||||
@@ -90,7 +90,7 @@ class BaseStashQueue(ABC):
|
||||
"""Construct a queue.
|
||||
|
||||
Arguments:
|
||||
scm: Git SCM instance for this queue.
|
||||
repo: DVC repo instance for this queue.
|
||||
ref: Git stash ref for this queue.
|
||||
failed_ref: Failed run Git stash ref for this queue.
|
||||
"""
|
||||
|
||||
@@ -84,8 +84,8 @@ def cleanup_exp(executor: TempDirExecutor, infofile: str) -> None:
|
||||
"""Cleanup after an experiment.
|
||||
|
||||
Arguments:
|
||||
tmp_dir: Temp directory to be removed.
|
||||
entry_dict: Serialized QueueEntry for this experiment.
|
||||
executor: Executor to clean up.
|
||||
infofile: Path to the executor info file.
|
||||
"""
|
||||
executor.cleanup(infofile)
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ class ExpRefInfo:
|
||||
|
||||
def __init__(self, baseline_sha: str, name: Optional[str] = None):
|
||||
self.baseline_sha = baseline_sha
|
||||
self.name: str = name if name else ""
|
||||
self.name: str = name or ""
|
||||
|
||||
def __str__(self):
|
||||
return "/".join(self.parts)
|
||||
|
||||
@@ -67,7 +67,7 @@ class ExpStash(Stash):
|
||||
branch: Optional[str] = None,
|
||||
) -> str:
|
||||
msg = cls.MESSAGE_FORMAT.format(
|
||||
rev=rev, baseline_rev=baseline_rev, name=name if name else ""
|
||||
rev=rev, baseline_rev=baseline_rev, name=name or ""
|
||||
)
|
||||
branch_msg = f":{branch}" if branch else ""
|
||||
return f"{msg}{branch_msg}"
|
||||
@@ -124,9 +124,7 @@ class ApplyStash(Stash):
|
||||
|
||||
@classmethod
|
||||
def format_message(cls, head_rev: str, rev: str, name: Optional[str] = None) -> str:
|
||||
return cls.MESSAGE_FORMAT.format(
|
||||
head_rev=head_rev, rev=rev, name=name if name else ""
|
||||
)
|
||||
return cls.MESSAGE_FORMAT.format(head_rev=head_rev, rev=rev, name=name or "")
|
||||
|
||||
@contextmanager
|
||||
def preserve_workspace(
|
||||
|
||||
@@ -200,7 +200,7 @@ def exp_commits(
|
||||
) -> Iterable[str]:
|
||||
"""Iterate over all experiment commits."""
|
||||
shas: set[str] = set()
|
||||
refs = ref_infos if ref_infos else exp_refs(scm)
|
||||
refs = ref_infos or exp_refs(scm)
|
||||
for ref_info in refs:
|
||||
shas.update(scm.branch_revs(str(ref_info), ref_info.baseline_sha))
|
||||
yield from shas
|
||||
|
||||
+7
-3
@@ -28,8 +28,10 @@ def _validate_args(**kwargs):
|
||||
"`--all-experiments`, `--all-commits`, `--date` or `--rev` "
|
||||
"needs to be set."
|
||||
)
|
||||
if kwargs.get("num") and not kwargs.get("rev"):
|
||||
raise InvalidArgumentError("`--num` can only be used alongside `--rev`")
|
||||
if kwargs.get("num") and not (kwargs.get("rev") or kwargs.get("all_branches")):
|
||||
raise InvalidArgumentError(
|
||||
"`--num` can only be used alongside `--rev` or `--all-branches`"
|
||||
)
|
||||
|
||||
|
||||
def _used_obj_ids_not_in_remote(
|
||||
@@ -128,9 +130,11 @@ def gc( # noqa: C901, PLR0912, PLR0913
|
||||
used_obj_ids = set()
|
||||
used_obj_ids.update(*odb_to_obj_ids.values())
|
||||
|
||||
seen_odbs = set()
|
||||
for scheme, odb in self.cache.by_scheme():
|
||||
if not odb:
|
||||
if not odb or odb in seen_odbs:
|
||||
continue
|
||||
seen_odbs.add(odb)
|
||||
num_removed = ogc(odb, used_obj_ids, jobs=jobs, dry=dry)
|
||||
if num_removed:
|
||||
logger.info("Removed %d objects from %s cache.", num_removed, scheme)
|
||||
|
||||
@@ -123,7 +123,7 @@ def try_expand_paths(fs: "FileSystem", paths: Iterable[str]) -> Iterator[str]:
|
||||
if fs.isdir(path):
|
||||
yield from fs.find(path)
|
||||
continue
|
||||
except Exception as e: # noqa: BLE001
|
||||
except Exception as e:
|
||||
logger.debug(
|
||||
"failed to expand %r: %s",
|
||||
path,
|
||||
|
||||
@@ -10,6 +10,8 @@ from dvc.utils import relpath
|
||||
from dvc.utils.collections import ensure_list
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing_extensions import Self
|
||||
|
||||
from dvc.repo import Repo
|
||||
from dvc.scm import Base
|
||||
|
||||
@@ -96,7 +98,7 @@ class SCMContext:
|
||||
@contextmanager
|
||||
def __call__(
|
||||
self, autostage: Optional[bool] = None, quiet: Optional[bool] = None
|
||||
) -> Iterator["SCMContext"]:
|
||||
) -> Iterator["Self"]:
|
||||
try:
|
||||
yield self
|
||||
except Exception:
|
||||
@@ -131,9 +133,10 @@ class SCMContext:
|
||||
|
||||
self.files_to_track = set()
|
||||
|
||||
def __enter__(self) -> "SCMContext":
|
||||
def __enter__(self) -> "Self":
|
||||
self._cm = self()
|
||||
return self._cm.__enter__()
|
||||
self._cm.__enter__()
|
||||
return self
|
||||
|
||||
def __exit__(self, *exc_args) -> None:
|
||||
assert self._cm
|
||||
|
||||
+5
-2
@@ -234,7 +234,7 @@ def iter_revs(
|
||||
results.extend(scm.list_all_commits())
|
||||
else:
|
||||
if all_branches:
|
||||
results.extend(scm.list_branches())
|
||||
results.extend(_get_n_commits(scm, scm.list_branches(), num))
|
||||
|
||||
if all_tags:
|
||||
results.extend(scm.list_tags())
|
||||
@@ -258,7 +258,10 @@ def iter_revs(
|
||||
results.extend(exp_commits(scm))
|
||||
|
||||
rev_resolver = partial(resolve_rev, scm)
|
||||
return group_by(rev_resolver, results)
|
||||
grouped = group_by(rev_resolver, results)
|
||||
for rev, names in grouped.items():
|
||||
grouped[rev] = list(dict.fromkeys(names))
|
||||
return grouped
|
||||
|
||||
|
||||
def lfs_prefetch(fs: "FileSystem", paths: list[str]):
|
||||
|
||||
@@ -9,7 +9,7 @@ from typing import Optional
|
||||
import pytest
|
||||
from dulwich.porcelain import clone
|
||||
from funcy import first
|
||||
from packaging import version
|
||||
from packaging import specifiers, version
|
||||
|
||||
from dvc.types import StrPath
|
||||
|
||||
@@ -109,11 +109,20 @@ def make_dvc_bin(
|
||||
else:
|
||||
pkg = "dvc"
|
||||
packages = [f"{pkg} @ git+file://{dvc_repo}@{dvc_rev}"]
|
||||
try:
|
||||
if version.Version(dvc_rev) < version.Version("3.50.3"):
|
||||
packages.append("pygit2==1.14.1")
|
||||
except version.InvalidVersion:
|
||||
pass
|
||||
|
||||
version_constraints = [
|
||||
("<3.50.3", ["pygit2==1.14.1"]),
|
||||
("<3.44.0", ["dulwich<1.0.0"]),
|
||||
("<3.67.0", ["pathspec<1"]),
|
||||
]
|
||||
for spec, pkgs in version_constraints:
|
||||
try:
|
||||
_dvc_version = version.Version(dvc_rev)
|
||||
except version.InvalidVersion:
|
||||
continue
|
||||
if _dvc_version in specifiers.SpecifierSet(spec):
|
||||
packages.extend(pkgs)
|
||||
|
||||
venv.install(*packages)
|
||||
|
||||
dvc_venvs[dvc_rev] = venv
|
||||
|
||||
@@ -2,7 +2,7 @@ import os
|
||||
import pathlib
|
||||
import posixpath
|
||||
import sys
|
||||
from typing import Callable, ClassVar
|
||||
from typing import Callable, ClassVar, Union
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from dvc.utils import relpath
|
||||
@@ -79,15 +79,17 @@ class PathInfo(pathlib.PurePath, _BasePath):
|
||||
and self._cparts[:n] == other._cparts # type: ignore[attr-defined]
|
||||
)
|
||||
|
||||
def relative_to(self, other, *args, **kwargs):
|
||||
def relative_to(
|
||||
self, *other: Union[str, "os.PathLike[str]"], **kwargs
|
||||
) -> "PathInfo":
|
||||
# pathlib relative_to raises exception when one path is not a direct
|
||||
# descendant of the other when os.path.relpath would return abspath.
|
||||
# For DVC PathInfo we only need the relpath behavior.
|
||||
# See: https://bugs.python.org/issue40358
|
||||
try:
|
||||
path = super().relative_to(other, *args, **kwargs)
|
||||
path = super().relative_to(*other, **kwargs)
|
||||
except ValueError:
|
||||
path = relpath(self, other)
|
||||
path = relpath(self, other[0])
|
||||
return self.__class__(path)
|
||||
|
||||
|
||||
|
||||
+3
-1
@@ -289,7 +289,7 @@ class Console:
|
||||
|
||||
return console.Console(stderr=True)
|
||||
|
||||
def table(
|
||||
def table( # noqa: PLR0913
|
||||
self,
|
||||
data: "TableData",
|
||||
headers: Optional["Headers"] = None,
|
||||
@@ -301,6 +301,7 @@ class Console:
|
||||
row_styles: Optional[Sequence["Styles"]] = None,
|
||||
borders: Union[bool, str] = False,
|
||||
colalign: Optional[tuple[str, ...]] = None,
|
||||
preserve_whitespace: bool = False,
|
||||
) -> None:
|
||||
from dvc.ui import table as t
|
||||
|
||||
@@ -329,6 +330,7 @@ class Console:
|
||||
pager=pager,
|
||||
force=force,
|
||||
colalign=colalign,
|
||||
preserve_whitespace=preserve_whitespace,
|
||||
)
|
||||
|
||||
def status(self, status: str, **kwargs: Any) -> "Status":
|
||||
|
||||
+27
-9
@@ -30,19 +30,37 @@ def plain_table(
|
||||
pager: bool = False,
|
||||
force: bool = True,
|
||||
colalign: Optional[tuple[str, ...]] = None,
|
||||
preserve_whitespace: bool = False,
|
||||
) -> None:
|
||||
import tabulate as tabulate_mod
|
||||
from funcy import nullcontext
|
||||
from packaging.version import Version
|
||||
from tabulate import tabulate
|
||||
|
||||
text: str = tabulate(
|
||||
data,
|
||||
headers if headers is not None else (),
|
||||
tablefmt="github" if markdown else "plain",
|
||||
disable_numparse=True,
|
||||
# None will be shown as "" by default, overriding
|
||||
missingval="-",
|
||||
colalign=colalign,
|
||||
)
|
||||
# NOTE: tabulate 0.10.0+ supports preserve_whitespace as a kwarg,
|
||||
# while 0.9.0 only respects the global PRESERVE_WHITESPACE.
|
||||
kwargs: dict = {}
|
||||
_orig = tabulate_mod.PRESERVE_WHITESPACE
|
||||
if preserve_whitespace:
|
||||
if Version(tabulate_mod.__version__) >= Version("0.10"):
|
||||
kwargs["preserve_whitespace"] = True
|
||||
else:
|
||||
tabulate_mod.PRESERVE_WHITESPACE = True
|
||||
|
||||
try:
|
||||
text: str = tabulate(
|
||||
data,
|
||||
headers if headers is not None else (),
|
||||
tablefmt="github" if markdown else "plain",
|
||||
disable_numparse=True,
|
||||
# None will be shown as "" by default, overriding
|
||||
missingval="-",
|
||||
colalign=colalign,
|
||||
**kwargs,
|
||||
)
|
||||
finally:
|
||||
tabulate_mod.PRESERVE_WHITESPACE = _orig
|
||||
|
||||
if markdown:
|
||||
# NOTE: md table is incomplete without the trailing newline
|
||||
text += "\n"
|
||||
|
||||
+4
-4
@@ -41,13 +41,13 @@ dependencies = [
|
||||
"distro>=1.3",
|
||||
"dpath<3,>=2.1.0",
|
||||
"dulwich",
|
||||
"dvc-data>=3.17.0,<3.18",
|
||||
"dvc-data>=3.18.2,<3.19.0",
|
||||
"dvc-http>=2.29.0",
|
||||
"dvc-objects",
|
||||
"dvc-render>=1.0.1,<2",
|
||||
"dvc-studio-client>=0.21,<1",
|
||||
"dvc-task>=0.3.0,<1",
|
||||
"flatten_dict<1,>=0.4.1",
|
||||
"flatten-dict<1,>=0.4.1",
|
||||
"flufl.lock>=8.1.0,<10",
|
||||
"fsspec>=2024.2.0",
|
||||
"funcy>=1.14",
|
||||
@@ -59,12 +59,12 @@ dependencies = [
|
||||
"networkx>=2.5",
|
||||
"omegaconf",
|
||||
"packaging>=19",
|
||||
"pathspec>=0.10.3,<1",
|
||||
"pathspec>=0.10.3,<2",
|
||||
"platformdirs<5,>=3.1.1",
|
||||
"psutil>=5.8",
|
||||
"pydot>=1.2.4",
|
||||
"pygtrie>=2.3.2",
|
||||
"pyparsing>=2.4.7",
|
||||
"pyparsing>=3.0.0",
|
||||
"requests>=2.22",
|
||||
"rich>=12",
|
||||
"ruamel.yaml>=0.17.11",
|
||||
|
||||
@@ -53,7 +53,7 @@ def test_new_simple(tmp_dir, scm, dvc, exp_stage, mocker, name, workspace):
|
||||
if workspace:
|
||||
assert (tmp_dir / "metrics.yaml").read_text().strip() == "foo: 2"
|
||||
|
||||
exp_name = name if name else ref_info.name
|
||||
exp_name = name or ref_info.name
|
||||
assert dvc.experiments.get_exact_name([exp])[exp] == exp_name
|
||||
assert resolve_rev(scm, exp_name) == exp
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ def test_exp_save(tmp_dir, dvc, scm, name):
|
||||
assert ref_info
|
||||
assert ref_info.baseline_sha == baseline
|
||||
|
||||
exp_name = name if name else ref_info.name
|
||||
exp_name = name or ref_info.name
|
||||
assert dvc.experiments.get_exact_name([exp])[exp] == exp_name
|
||||
assert resolve_rev(scm, exp_name) == exp
|
||||
|
||||
|
||||
@@ -1244,3 +1244,27 @@ def test_shallow_should_iterate_upto_tracked_directory(tmp_dir, dvc, scm, local_
|
||||
"committed": {"added": M.unordered(*files)},
|
||||
"git": M.dict(),
|
||||
}
|
||||
|
||||
|
||||
def test_duplicate_hashes_not_in_remote(tmp_dir, dvc, scm, local_remote):
|
||||
"""Test that files with identical content (same hash) are all correctly reported.
|
||||
|
||||
Regression test for https://github.com/iterative/dvc/issues/10959
|
||||
"""
|
||||
tmp_dir.dvc_gen(
|
||||
{"foo": "content", "bar": "content", "foobar": "foobar"}, commit="add files"
|
||||
)
|
||||
assert dvc.data_status(not_in_remote=True, granular=True, remote_refresh=True) == {
|
||||
**EMPTY_STATUS,
|
||||
"unchanged": M.unordered("foo", "bar", "foobar"),
|
||||
"not_in_remote": M.unordered("foo", "bar", "foobar"),
|
||||
"git": M.dict(),
|
||||
}
|
||||
|
||||
dvc.push()
|
||||
assert dvc.data_status(not_in_remote=True, granular=True, remote_refresh=True) == {
|
||||
**EMPTY_STATUS,
|
||||
"unchanged": M.unordered("foo", "bar", "foobar"),
|
||||
"not_in_remote": [],
|
||||
"git": M.dict(),
|
||||
}
|
||||
|
||||
+49
-2
@@ -92,6 +92,27 @@ def test_gc_multiple_dvc_repos(tmp_dir, scm, dvc, erepo_dir):
|
||||
assert len(list(odb.all())) == 2
|
||||
|
||||
|
||||
def test_gc_multiple_dvc_repos_all_branches_num(tmp_dir, scm, dvc, erepo_dir):
|
||||
tmp_dir.dvc_gen("main", "main", commit="main")
|
||||
|
||||
erepo_dir.dvc.cache.local.path = dvc.cache.local.path
|
||||
with erepo_dir.chdir():
|
||||
erepo_dir.scm_gen("base", "base", commit="base")
|
||||
with erepo_dir.branch("feature", new=True):
|
||||
erepo_dir.dvc_gen("shared", "feature-v1", commit="feature-v1")
|
||||
erepo_dir.dvc.remove("shared.dvc")
|
||||
erepo_dir.dvc_gen("shared", "feature-v2", commit="feature-v2")
|
||||
|
||||
odb = dvc.cache.local
|
||||
assert len(list(odb.all())) == 3
|
||||
|
||||
dvc.gc(all_branches=True, num=2, repos=[erepo_dir])
|
||||
assert len(list(odb.all())) == 3
|
||||
|
||||
dvc.gc(all_branches=True, repos=[erepo_dir])
|
||||
assert len(list(odb.all())) == 2
|
||||
|
||||
|
||||
def test_all_commits(tmp_dir, scm, dvc):
|
||||
tmp_dir.dvc_gen("testfile", "uncommitted")
|
||||
tmp_dir.dvc_gen("testfile", "committed", commit="committed")
|
||||
@@ -437,8 +458,16 @@ def test_gc_logging(caplog, dvc, good_and_bad_cache):
|
||||
with caplog.at_level(logging.INFO, logger="dvc"):
|
||||
dvc.gc(workspace=True)
|
||||
|
||||
assert "Removed 3 objects from repo cache." in caplog.text
|
||||
assert "No unused 'local' cache to remove." in caplog.text
|
||||
# Check that 3 objects were removed from either repo or local cache
|
||||
# (they point to the same ODB, so only one will be logged due to deduplication)
|
||||
has_repo_log = "Removed 3 objects from repo cache." in caplog.text
|
||||
has_local_log = "Removed 3 objects from local cache." in caplog.text
|
||||
|
||||
# Exactly one of repo or local should have the log, not both (deduplication)
|
||||
assert has_repo_log ^ has_local_log, (
|
||||
"Should have exactly one log for repo/local cache"
|
||||
)
|
||||
|
||||
assert "No unused 'legacy' cache to remove." in caplog.text
|
||||
|
||||
|
||||
@@ -450,3 +479,21 @@ def test_gc_skip_failed(tmp_dir, dvc):
|
||||
dvc.gc(force=True, workspace=True)
|
||||
|
||||
dvc.gc(force=True, workspace=True, skip_failed=True)
|
||||
|
||||
|
||||
def test_gc_dry_logs_paths(caplog, tmp_dir, dvc):
|
||||
"""Test that dry run logs paths of objects to be removed."""
|
||||
odb = dvc.cache.local
|
||||
|
||||
# Add some unused objects directly to cache
|
||||
unused_hashes = ["test123", "test456", "test789"]
|
||||
for hash_val in unused_hashes:
|
||||
odb.add_bytes(hash_val, hash_val.encode("utf8"))
|
||||
|
||||
with caplog.at_level(logging.INFO, logger="dvc_data.hashfile.gc"):
|
||||
dvc.gc(workspace=True, dry=True)
|
||||
|
||||
# Verify that paths are logged by dvc-data layer
|
||||
for hash_val in unused_hashes:
|
||||
expected_path = odb.oid_to_path(hash_val)
|
||||
assert f"Removing {expected_path}" in caplog.text
|
||||
|
||||
@@ -196,7 +196,7 @@ def test_diff_show_markdown_and_hash(mocker, show_hash, dvc):
|
||||
cmd = args.func(args)
|
||||
|
||||
diff = {}
|
||||
show_hash = show_hash if show_hash else False
|
||||
show_hash = show_hash or False
|
||||
mock_show_markdown = mocker.patch("dvc.commands.diff._show_markdown")
|
||||
mocker.patch("dvc.repo.Repo.diff", return_value=diff.copy())
|
||||
|
||||
|
||||
@@ -65,6 +65,30 @@ def test_(dvc, scm, mocker):
|
||||
with pytest.raises(InvalidArgumentError):
|
||||
cmd.run()
|
||||
|
||||
cli_args = parse_args(["gc", "--all-branches", "--num", "2", "--force"])
|
||||
cmd = cli_args.func(cli_args)
|
||||
|
||||
assert cmd.run() == 0
|
||||
|
||||
m.assert_called_with(
|
||||
workspace=False,
|
||||
all_tags=False,
|
||||
all_branches=True,
|
||||
all_commits=False,
|
||||
all_experiments=False,
|
||||
commit_date=None,
|
||||
cloud=False,
|
||||
remote=None,
|
||||
force=True,
|
||||
jobs=None,
|
||||
repos=None,
|
||||
rev=None,
|
||||
num=2,
|
||||
not_in_remote=False,
|
||||
dry=False,
|
||||
skip_failed=False,
|
||||
)
|
||||
|
||||
cli_args = parse_args(["gc", "--cloud", "--not-in-remote"])
|
||||
cmd = cli_args.func(cli_args)
|
||||
with pytest.raises(InvalidArgumentError):
|
||||
|
||||
@@ -47,7 +47,7 @@ def test_help(caplog, capsys, command_tuples):
|
||||
out, err = capsys.readouterr()
|
||||
|
||||
# validate metavars are all in lowercase
|
||||
usage = "\n".join(takewhile(lambda o: bool(o), out.splitlines()))
|
||||
usage = "\n".join(takewhile(bool, out.splitlines()))
|
||||
|
||||
message = (
|
||||
"metavars not lowercased, you are likely missing formatter_class=XXX "
|
||||
|
||||
@@ -47,6 +47,13 @@ def test_iter_revs(tmp_dir, scm, mocker):
|
||||
assert gen == {rev_old: ["tag"]}
|
||||
gen = iter_revs(scm, all_branches=True)
|
||||
assert gen == {rev_old: [old], rev_new: ["new"], rev_other: ["other"]}
|
||||
gen = iter_revs(scm, all_branches=True, num=2)
|
||||
assert gen == {
|
||||
rev_old: [old, rev_old],
|
||||
rev_root: [rev_root],
|
||||
rev_new: ["new"],
|
||||
rev_other: ["other"],
|
||||
}
|
||||
gen = iter_revs(scm, all_tags=True)
|
||||
assert gen == {rev_old: ["tag"]}
|
||||
gen = iter_revs(scm, all_commits=True)
|
||||
|
||||
@@ -113,7 +113,7 @@ class TestColorFormatter:
|
||||
with caplog.at_level(logging.DEBUG, logger="dvc"):
|
||||
try:
|
||||
raise Exception("description")
|
||||
except Exception: # noqa: BLE001
|
||||
except Exception:
|
||||
stack_trace = traceback.format_exc()
|
||||
logger.debug("", exc_info=True)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user