Compare commits

...
Author SHA1 Message Date
pre-commit-ci[bot] 91586e85f8 [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.15.20 → v0.16.10](https://github.com/astral-sh/ruff-pre-commit/compare/v0.15.20...v0.16.10)
- [github.com/codespell-project/codespell: v2.4.2 → v2.4.3](https://github.com/codespell-project/codespell/compare/v2.4.2...v2.4.3)
2026-10-05 19:53:34 +00:00
dependabot[bot] 56e5982951 build(deps): bump actions/setup-python from 6 to 7 (#11073)
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 6 to 7.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-06 10:02:40 +05:45
dependabot[bot] defe4062aa build(deps): bump github/codeql-action from 4 to 4.37.4 (#11077)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4 to 4.37.4.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v4...v4.37.4)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 4.37.4
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-06 10:02:19 +05:45
eeshsaxena f74c1c0e70 docs: fix parameter name mismatches in docstrings (#11060)
- repo/experiments/queue/base.py: `scm` -> `repo` in
  BaseStashQueue.__init__ (signature takes a Repo, not an SCM instance)
- repo/experiments/queue/tasks.py: stale `tmp_dir`/`entry_dict` args in
  cleanup_exp replaced with the actual `executor`/`infofile` parameters
2026-07-10 13:12:18 +05:45
dependabot[bot] b5dede14f2 build(deps): bump codecov/codecov-action from 6 to 7 (#11051)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 6 to 7.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v6...v7)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-04 18:16:33 +05:45
dependabot[bot] 8d37f966c3 build(deps): bump rtCamp/action-slack-notify from 2.3.3 to 2.4.0 (#11052)
Bumps [rtCamp/action-slack-notify](https://github.com/rtcamp/action-slack-notify) from 2.3.3 to 2.4.0.
- [Release notes](https://github.com/rtcamp/action-slack-notify/releases)
- [Commits](https://github.com/rtcamp/action-slack-notify/compare/v2.3.3...v2.4.0)

---
updated-dependencies:
- dependency-name: rtCamp/action-slack-notify
  dependency-version: 2.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-04 18:16:07 +05:45
pre-commit-ci[bot] 95165cc39a [pre-commit.ci] pre-commit autoupdate (#11053)
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.15.15 → v0.15.20](https://github.com/astral-sh/ruff-pre-commit/compare/v0.15.15...v0.15.20)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-07-04 18:10:04 +05:45
dependabot[bot] ccb72126ea build(deps): bump actions/cache from 5 to 6 (#11056)
Bumps [actions/cache](https://github.com/actions/cache) from 5 to 6.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-04 18:09:51 +05:45
dependabot[bot] d5315c3827 build(deps): bump actions/checkout from 6 to 7 (#11055)
Bumps [actions/checkout](https://github.com/actions/checkout) from 6 to 7.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-04 18:06:32 +05:45
pre-commit-ci[bot] 8131c32c33 [pre-commit.ci] pre-commit autoupdate (#11048)
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.15.14 → v0.15.15](https://github.com/astral-sh/ruff-pre-commit/compare/v0.15.14...v0.15.15)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-06-02 14:07:40 +05:45
pre-commit-ci[bot] 0013af99d4 [pre-commit.ci] pre-commit autoupdate (#11040)
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.15.12 → v0.15.14](https://github.com/astral-sh/ruff-pre-commit/compare/v0.15.12...v0.15.14)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-05-27 21:05:07 +05:45
pre-commit-ci[bot] 06ff81cd7f [pre-commit.ci] pre-commit autoupdate (#11031)
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.15.11 → v0.15.12](https://github.com/astral-sh/ruff-pre-commit/compare/v0.15.11...v0.15.12)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-04-28 17:15:27 +05:45
pre-commit-ci[bot] e19b0874c0 [pre-commit.ci] pre-commit autoupdate (#11028)
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.15.10 → v0.15.11](https://github.com/astral-sh/ruff-pre-commit/compare/v0.15.10...v0.15.11)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-04-26 09:26:45 +05:45
pre-commit-ci[bot] b290eaea43 [pre-commit.ci] pre-commit autoupdate (#11024)
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.15.9 → v0.15.10](https://github.com/astral-sh/ruff-pre-commit/compare/v0.15.9...v0.15.10)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-04-14 15:24:00 +05:45
pre-commit-ci[bot] 2a2b543dd6 [pre-commit.ci] pre-commit autoupdate (#11023)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-04-07 12:10:50 +05:45
pre-commit-ci[bot]andSaugat Pachhai 356dfa0327 [pre-commit.ci] pre-commit autoupdate (#10981)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Saugat Pachhai (सौगात) <[email protected]>
2026-03-31 10:49:52 +05:45
skshetry 0f4e8a23c8 fix(ls): support tabulate 0.10.0 preserve_whitespace (#11021) 2026-03-31 10:32:56 +05:45
dependabot[bot] 333edb2cbf build(deps): bump codecov/codecov-action from 5 to 6 (#11020)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5 to 6.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v5...v6)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-31 09:26:06 +05:45
Ivan Shcheklein a65523a86a feat(gc): allow num with all-branches (#11013) 2026-03-18 22:21:20 +05:45
Christian W. Feldmann fb42429107 Resolve PyparsingDeprecationWarning for enablePackrat setParseAction parseString (#11016) 2026-03-18 22:02:17 +05:45
dependabot[bot] e40e378fc9 build(deps): bump actions/download-artifact from 7 to 8 (#11006)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-27 11:08:23 +05:45
dependabot[bot] b1aaeb94c0 build(deps): bump actions/upload-artifact from 6 to 7 (#11007)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-27 11:08:15 +05:45
skshetry dcf6f48fb3 bench: add version constraint for pathspec (#10986) 2026-02-01 09:15:42 +05:45
Caleb P. BurnsandSaugat Pachhai c6c680d2b2 Support pathspec v1 (#10951)
Co-authored-by: Saugat Pachhai (सौगात) <[email protected]>
2026-01-31 11:12:13 +05:45
skshetry 1e8962bf0c Remove boto3 from metadata copying in hook-dvc.py (#10983) 2026-01-28 19:08:15 +05:45
skshetryandpre-commit-ci[bot] 0f2361f218 fix(benchmarks): pin dulwich<1.0.0 for DVC versions before 3.44.0 (#10979)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-01-23 23:33:50 +05:45
pre-commit-ci[bot] e9c0a99083 [pre-commit.ci] pre-commit autoupdate (#10975)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-01-21 16:01:48 +05:45
Ritoban Dutta 3c71fdbb0d fix: replace deprecated parseAll with parse_all in interpolate.py (#10971) 2026-01-16 18:27:02 +05:45
pre-commit-ci[bot] 04cbd40df5 [pre-commit.ci] pre-commit autoupdate (#10967)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-01-13 22:44:02 +05:45
Pandikunta Anand Reddy 5725780790 refactor: drop stale exceptions related to dvc machine (#10963) 2026-01-08 21:54:13 +05:45
skshetry 2dc6261a66 Update dvc-data version to 3.18.2 (#10962) 2026-01-08 21:25:25 +05:45
skshetry 0f73c0a725 tests: add a regression test for #10959 (#10960) 2026-01-08 21:14:22 +05:45
Wyatt ff8752c3dc gc: log paths during dry-run garbage collection (#10937) 2026-01-07 18:16:05 +05:45
Wei Ji dbc7531f0d Typo on flatten-dict dependency (#10953) 2026-01-07 09:49:51 +05:45
skshetry d4594c749c fix(cli/dag): improve help message (#10949) 2026-01-06 14:34:06 +05:45
fhausmannandskshetry e062f545c9 Add option to collapse foreach/matrix stages into a single stage (#10945)
Co-authored-by: skshetry <[email protected]>
2026-01-06 08:38:08 +00:00
vorj 3c77443696 fix pathspec version requirement (#10948) 2026-01-06 11:46:26 +05:45
dependabot[bot] b5555b7285 build(deps-dev): update pytest requirement from <9,>=7 to >=7,<10 (#10946)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-06 11:16:49 +05:45
dependabot[bot] fbdcb6e140 build(deps): update flufl-lock requirement from <9,>=8.1.0 to >=8.1.0,<10 (#10947)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-06 11:16:27 +05:45
skshetry 7ed8d896df update test fixture to use matchers from dvc.testing (#10940) 2025-12-30 09:08:31 +05:45
skshetry 9fb761772c update test fixture to use tmp_path_factory (#10939) 2025-12-29 23:50:15 +05:45
skshetry f523105219 Remove pytest-test-utils from dependencies (#10938) 2025-12-28 23:51:56 +05:45
pre-commit-ci[bot] e1ccc2c7c5 [pre-commit.ci] pre-commit autoupdate (#10935)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-12-23 09:18:53 +05:45
56 changed files with 445 additions and 143 deletions
+3 -3
View File
@@ -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
+5 -5
View File
@@ -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
+3 -3
View File
@@ -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.37.4
with:
languages: ${{ matrix.language }}
queries: security-extended
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4
uses: github/codeql-action/analyze@v4.37.4
+3 -3
View File
@@ -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 }}
+7 -7
View File
@@ -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 }}
+2 -2
View File
@@ -22,13 +22,13 @@ repos:
- id: sort-simple-yaml
- id: trailing-whitespace
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: 'v0.14.9'
rev: 'v0.16.10'
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.3
hooks:
- id: codespell
additional_dependencies: ["tomli"]
+2 -2
View File
@@ -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)
),
)
-1
View File
@@ -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
View File
@@ -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
+71 -2
View File
@@ -127,19 +127,80 @@ def _filter(graph, targets, full):
return new_graph
def _build(repo, target=None, full=False, outs=False):
def _is_foreach_matrix_stage(node, join_string):
if node.endswith(".dvc"):
return False
return join_string in node
def _collapse_foreach_matrix_get_nodes(graph):
from dvc.parsing import JOIN
new_nodes = set()
nodes_to_remove = set()
for _node in list(graph.nodes):
if not _is_foreach_matrix_stage(_node, JOIN):
continue
nodes_to_remove.add(_node)
new_nodes.add(_node.split(JOIN)[0])
return new_nodes, nodes_to_remove
def _collapse_foreach_matrix_get_edges(graph):
from dvc.parsing import JOIN
new_edges = set()
edges_to_remove = set()
for _e1, _e2 in list(graph.edges):
_replace = False
_new_e1 = _e1
_new_e2 = _e2
if _is_foreach_matrix_stage(_e1, JOIN):
_new_e1 = _e1.split(JOIN)[0]
_replace = True
if _is_foreach_matrix_stage(_e2, JOIN):
_new_e2 = _e2.split(JOIN)[0]
_replace = True
if _replace:
edges_to_remove.add((_e1, _e2))
new_edges.add((_new_e1, _new_e2))
return new_edges, edges_to_remove
def _collapse_foreach_matrix(graph):
new_nodes, nodes_to_remove = _collapse_foreach_matrix_get_nodes(graph)
new_edges, edges_to_remove = _collapse_foreach_matrix_get_edges(graph)
new_graph = graph.copy()
new_graph.remove_edges_from(edges_to_remove)
new_graph.add_nodes_from(new_nodes)
new_graph.add_edges_from(new_edges)
new_graph.remove_nodes_from(nodes_to_remove)
return new_graph
def _build(repo, target=None, full=False, outs=False, collapse_foreach_matrix=False):
targets = _collect_targets(repo, target, outs)
graph = _transform(repo.index, outs)
return _filter(graph, targets, full)
filtered_graph = _filter(graph, targets, full)
if collapse_foreach_matrix:
filtered_graph = _collapse_foreach_matrix(filtered_graph)
return filtered_graph
class CmdDAG(CmdBase):
def run(self):
from dvc.exceptions import InvalidArgumentError
if self.args.outs and self.args.collapse_foreach_matrix:
raise InvalidArgumentError(
"`--outs` and `--collapse-foreach-matrix` are mutually exclusive"
)
graph = _build(
self.repo,
target=self.args.target,
full=self.args.full,
outs=self.args.outs,
collapse_foreach_matrix=self.args.collapse_foreach_matrix,
)
if self.args.dot:
@@ -181,6 +242,14 @@ def add_parser(subparsers, parent_parser):
dest="markdown",
help="Print DAG with mermaid format wrapped in Markdown block.",
)
dag_parser.add_argument(
"--collapse-foreach-matrix",
action="store_true",
default=False,
help=(
"Collapse stages from each foreach/matrix definition into a single node."
),
)
dag_parser.add_argument(
"--full",
action="store_true",
+11 -4
View File
@@ -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`."
),
)
+1 -9
View File
@@ -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):
-12
View File
@@ -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
View File
@@ -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}>")
+3 -3
View File
@@ -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
+1 -1
View File
@@ -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(
+2 -2
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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.
"""
+2 -2
View File
@@ -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)
+1 -1
View File
@@ -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)
+2 -4
View File
@@ -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(
+1 -1
View File
@@ -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
View File
@@ -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)
+1 -1
View File
@@ -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,
+6 -3
View File
@@ -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
View File
@@ -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]):
+15 -6
View File
@@ -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
+30 -1
View File
@@ -1,5 +1,5 @@
from collections.abc import Iterable, Mapping
from typing import TYPE_CHECKING, Any, Optional
from typing import TYPE_CHECKING, Any, Optional, Union
if TYPE_CHECKING:
import builtins
@@ -68,8 +68,37 @@ class attrs: # noqa: N801, PLW1641
return all(getattr(other, name) == v for name, v in self.attribs.items())
class instance_of: # noqa: N801, PLW1641
def __init__(self, expected_type: Union[Any, tuple[Any, ...]]) -> None:
self.expected_type = expected_type
def __repr__(self) -> str:
if isinstance(self.expected_type, tuple):
inner = f"({', '.join(t.__name__ for t in self.expected_type)})"
else:
inner = self.expected_type.__name__
return f"{self.__class__.__name__}({inner})"
def __eq__(self, other: object) -> bool:
return isinstance(other, self.expected_type)
class any_of: # noqa: N801, PLW1641
def __init__(self, *items: Any) -> None:
self.items = sorted(items)
def __repr__(self) -> str:
inner = ", ".join(map(repr, self.items))
return f"any_of({inner})"
def __eq__(self, other: object) -> bool:
return other in self.items
__all__ = [
"any_of",
"attrs",
"dict",
"instance_of",
"unordered",
]
+6 -4
View File
@@ -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
View File
@@ -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
View File
@@ -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"
+6 -7
View File
@@ -41,14 +41,14 @@ 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",
"flufl.lock>=8.1.0,<9",
"flatten-dict<1,>=0.4.1",
"flufl.lock>=8.1.0,<10",
"fsspec>=2024.2.0",
"funcy>=1.14",
"grandalf<1,>=0.7",
@@ -59,12 +59,12 @@ dependencies = [
"networkx>=2.5",
"omegaconf",
"packaging>=19",
"pathspec>=0.10.3",
"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",
@@ -110,12 +110,11 @@ tests = [
"beautifulsoup4>=4.4",
"dvc-ssh",
"filelock",
"pytest>=7,<9",
"pytest>=7,<10",
"pytest-rerunfailures<17.0",
"pytest-cov>=4.1.0",
"pytest-docker>=1,<4",
"pytest-mock",
"pytest-test-utils",
"pytest-timeout>=2",
"pytest-xdist>=3.2",
'pywin32>=225; sys_platform == "win32"', # optional test dependency
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+3 -2
View File
@@ -4,6 +4,7 @@ from os.path import join
import pytest
from dvc.cli import main
from dvc.testing import matchers as M
from dvc.utils import relpath
from dvc.utils.serialize import JSONFileCorruptedError
@@ -95,7 +96,7 @@ def test_metrics_diff_json_unchanged(tmp_dir, scm, dvc, run_copy_metrics):
assert dvc.metrics.diff(a_rev="HEAD~2") == {}
def test_metrics_diff_broken_json(M, tmp_dir, scm, dvc, run_copy_metrics):
def test_metrics_diff_broken_json(tmp_dir, scm, dvc, run_copy_metrics):
metrics = {"a": {"b": {"c": 1, "d": 1, "e": "3"}}}
(tmp_dir / "m_temp.json").dump(metrics)
run_copy_metrics(
@@ -143,7 +144,7 @@ def test_metrics_diff_new_metric(tmp_dir, scm, dvc, run_copy_metrics):
}
def test_metrics_diff_deleted_metric(M, tmp_dir, scm, dvc, run_copy_metrics):
def test_metrics_diff_deleted_metric(tmp_dir, scm, dvc, run_copy_metrics):
metrics = {"a": {"b": {"c": 1, "d": 1, "e": "3"}}}
(tmp_dir / "m_temp.json").dump(metrics)
run_copy_metrics(
+3 -2
View File
@@ -11,6 +11,7 @@ from dvc.dvcfile import PROJECT_FILE
from dvc.exceptions import OverlappingOutputPathsError
from dvc.repo import Repo
from dvc.repo.metrics.show import FileResult, Result
from dvc.testing import matchers as M
from dvc.utils.fs import remove
from dvc.utils.serialize import JSONFileCorruptedError
from dvc_data.index import DataIndexDirError
@@ -246,7 +247,7 @@ def test_show_malformed_metric(tmp_dir, scm, dvc, caplog):
)
def test_metrics_show_no_target(M, tmp_dir, dvc, capsys):
def test_metrics_show_no_target(tmp_dir, dvc, capsys):
assert dvc.metrics.show(targets=["metrics.json"]) == {
"": {"data": {"metrics.json": {"error": M.instance_of(FileNotFoundError)}}}
}
@@ -356,7 +357,7 @@ def test_top_level_parametrized(tmp_dir, dvc):
}
def test_metric_in_a_tracked_directory_with_missing_dir_file(M, tmp_dir, dvc):
def test_metric_in_a_tracked_directory_with_missing_dir_file(tmp_dir, dvc):
tmp_dir.dvc_gen({"dir": {"file": "2"}})
(tmp_dir / "dvc.yaml").dump({"metrics": [join("dir", "file")]})
shutil.rmtree(tmp_dir / "dir") # remove from workspace
+2 -1
View File
@@ -2,6 +2,7 @@ from os.path import join
import pytest
from dvc.testing import matchers as M
from dvc.utils import relpath
@@ -54,7 +55,7 @@ def test_diff_new(tmp_dir, scm, dvc):
}
def test_diff_deleted(M, tmp_dir, scm, dvc):
def test_diff_deleted(tmp_dir, scm, dvc):
tmp_dir.gen("params.yaml", "foo: bar")
dvc.run(cmd="echo params.yaml", params=["foo"], name="echo-params")
scm.add(["params.yaml", "Dvcfile"])
+2 -1
View File
@@ -6,6 +6,7 @@ import pytest
from dvc.dvcfile import PROJECT_FILE
from dvc.repo import Repo
from dvc.repo.metrics.show import FileResult, Result
from dvc.testing import matchers as M
from dvc_data.index import DataIndexDirError
@@ -197,7 +198,7 @@ def test_top_level_parametrized(tmp_dir, dvc):
}
def test_param_in_a_tracked_directory_with_missing_dir_file(M, tmp_dir, dvc):
def test_param_in_a_tracked_directory_with_missing_dir_file(tmp_dir, dvc):
tmp_dir.dvc_gen({"dir": {"file": "2"}})
(tmp_dir / "dvc.yaml").dump({"params": [join("dir", "file")]})
shutil.rmtree(tmp_dir / "dir") # remove from workspace
+2 -1
View File
@@ -15,6 +15,7 @@ from dvc.output import Output
from dvc.stage import PipelineStage, Stage
from dvc.stage.cache import RunCacheNotSupported
from dvc.stage.exceptions import StageFileDoesNotExistError, StageNotFound
from dvc.testing import matchers as M
from dvc.utils.fs import remove
from dvc.utils.serialize import modify_yaml
from dvc_data.hashfile.hash import file_md5
@@ -55,7 +56,7 @@ def test_repro_frozen(tmp_dir, dvc, run_copy):
assert stages == [data_stage, stage0]
def test_downstream(M, tmp_dir, dvc):
def test_downstream(tmp_dir, dvc):
# The dependency graph should look like this:
#
# E
+25 -1
View File
@@ -577,7 +577,7 @@ def test_root_from_file_to_dir(tmp_dir, dvc, scm):
}
def test_empty_dir(tmp_dir, scm, dvc, M):
def test_empty_dir(tmp_dir, scm, dvc):
# regression testing for https://github.com/treeverse/dvc/issues/8958
tmp_dir.dvc_gen({"data": {"foo": "foo"}})
remove("data")
@@ -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
View File
@@ -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
+3 -1
View File
@@ -6,6 +6,8 @@ import pandas as pd
import pytest
from funcy import compact
from dvc.testing import matchers as M
@pytest.fixture
def db_path(tmp_dir):
@@ -46,7 +48,7 @@ def load_data(file, output_format):
({"table": "model"}, "model"),
],
)
def test(M, tmp_dir, scm, dvc, db_connection, seed_db, output_format, args, file_name):
def test(tmp_dir, scm, dvc, db_connection, seed_db, output_format, args, file_name):
seed_db(values=range(5))
if output_format == "json":
file_size = 96, 192
+7 -6
View File
@@ -10,6 +10,7 @@ from dvc.fs import MemoryFileSystem
from dvc.repo import Repo
from dvc.repo.ls import _ls_tree, ls_tree
from dvc.scm import CloneError
from dvc.testing import matchers as M
FS_STRUCTURE = {
"README.md": "content",
@@ -460,7 +461,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, M):
def test_ls_granular(erepo_dir):
with erepo_dir.chdir():
erepo_dir.dvc_gen(
{
@@ -624,7 +625,7 @@ def test_subrepo(request, dvc_top_level, erepo_type):
assert _list_files(subrepo, "dvc_dir") == {"lorem"}
def test_broken_symlink(tmp_dir, dvc, M):
def test_broken_symlink(tmp_dir, dvc):
from dvc.fs import system
tmp_dir.gen("file", "content")
@@ -654,7 +655,7 @@ def test_broken_symlink(tmp_dir, dvc, M):
]
def test_ls_broken_dir(tmp_dir, dvc, M):
def test_ls_broken_dir(tmp_dir, dvc):
from dvc_data.index import DataIndexDirError
tmp_dir.dvc_gen(
@@ -836,7 +837,7 @@ def _simplify_tree(files):
return ret
def test_ls_tree(M, tmp_dir, scm, dvc):
def test_ls_tree(tmp_dir, scm, dvc):
tmp_dir.scm_gen(FS_STRUCTURE, commit="init")
tmp_dir.dvc_gen(DVC_STRUCTURE, commit="dvc")
@@ -887,7 +888,7 @@ def test_ls_tree(M, tmp_dir, scm, dvc):
}
def test_ls_tree_dvc_only(M, tmp_dir, scm, dvc):
def test_ls_tree_dvc_only(tmp_dir, scm, dvc):
tmp_dir.scm_gen(FS_STRUCTURE, commit="init")
tmp_dir.dvc_gen(DVC_STRUCTURE, commit="dvc")
@@ -905,7 +906,7 @@ def test_ls_tree_dvc_only(M, tmp_dir, scm, dvc):
assert _simplify_tree(files) == expected
def test_ls_tree_maxdepth(M, tmp_dir, scm, dvc):
def test_ls_tree_maxdepth(tmp_dir, scm, dvc):
tmp_dir.scm_gen(FS_STRUCTURE, commit="init")
tmp_dir.dvc_gen(DVC_STRUCTURE, commit="dvc")
+1 -1
View File
@@ -166,7 +166,7 @@ def test_virtual_update_noop(tmp_dir, dvc, remote):
assert not dvc.push()
def test_partial_checkout_and_update(M, tmp_dir, dvc, remote):
def test_partial_checkout_and_update(tmp_dir, dvc, remote):
dir1 = {f"{i}.txt": f"dir1 {i}" for i in range(10)}
dir2 = {f"{i}.txt": f"dir2 {i}" for i in range(10)}
tmp_dir.gen({"dir": {"foo": "foo", "subdir": dir1, "subdir2": dir2}})
+52 -1
View File
@@ -2,7 +2,15 @@ import networkx as nx
import pytest
from dvc.cli import main, parse_args
from dvc.commands.dag import CmdDAG, _build, _show_ascii, _show_dot, _show_mermaid
from dvc.commands.dag import (
CmdDAG,
_build,
_collapse_foreach_matrix,
_show_ascii,
_show_dot,
_show_mermaid,
)
from dvc.parsing import JOIN
@pytest.mark.parametrize(
@@ -49,10 +57,53 @@ def repo(tmp_dir, dvc):
return dvc
def test_collapse_foreach_matrix(repo):
graph = nx.DiGraph(
[
("2", "1"),
("3@a", "2"),
("3@a", "1"),
("4", "1"),
("3@b", "4"),
("3@b", "1"),
("5", "3@a"),
("6", "3@b"),
("7", "5"),
("7", "6"),
]
)
expected_graph = nx.DiGraph(
[
("2", "1"),
("3", "2"),
("4", "1"),
("3", "4"),
("3", "1"),
("5", "3"),
("6", "3"),
("7", "5"),
("7", "6"),
]
)
collapsed_graph = _collapse_foreach_matrix(graph)
for node in collapsed_graph.nodes:
assert JOIN not in node
for n1, n2 in collapsed_graph.edges:
assert JOIN not in n1
assert JOIN not in n2
assert nx.is_isomorphic(collapsed_graph, expected_graph)
def test_build(repo):
assert nx.is_isomorphic(_build(repo), repo.index.graph)
def test_build_collapse(repo):
assert nx.is_isomorphic(
_build(repo, collapse_foreach_matrix=True), repo.index.graph
)
def test_build_target(repo):
graph = _build(repo, target="3")
assert set(graph.nodes()) == {"3", "b.dvc", "a.dvc"}
+1 -1
View File
@@ -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())
+24
View File
@@ -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):
+2 -1
View File
@@ -1,9 +1,10 @@
from dvc.cli import parse_args
from dvc.commands.get_url import CmdGetUrl
from dvc.config import Config
from dvc.testing import matchers as M
def test_get_url(mocker, M):
def test_get_url(mocker):
cli_args = parse_args(["get-url", "src", "out", "-j", "5"])
assert cli_args.func == CmdGetUrl
+1 -1
View File
@@ -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 "
+4 -3
View File
@@ -2,9 +2,10 @@ from dvc.cli import parse_args
from dvc.commands.ls_url import CmdListUrl
from dvc.config import Config
from dvc.fs import LocalFileSystem
from dvc.testing import matchers as M
def test_ls_url(mocker, M):
def test_ls_url(mocker):
cli_args = parse_args(["ls-url", "src"])
assert cli_args.func == CmdListUrl
cmd = cli_args.func(cli_args)
@@ -21,7 +22,7 @@ def test_ls_url(mocker, M):
)
def test_recursive(mocker, M):
def test_recursive(mocker):
cli_args = parse_args(["ls-url", "-R", "-L", "2", "src"])
assert cli_args.func == CmdListUrl
cmd = cli_args.func(cli_args)
@@ -34,7 +35,7 @@ def test_recursive(mocker, M):
)
def test_tree(mocker, M):
def test_tree(mocker):
cli_args = parse_args(["ls-url", "--tree", "--level", "2", "src"])
assert cli_args.func == CmdListUrl
cmd = cli_args.func(cli_args)
+2 -2
View File
@@ -7,9 +7,9 @@ from dvc_studio_client.auth import AuthorizationExpiredError
@pytest.fixture(autouse=True)
def global_config_dir(monkeypatch, tmp_dir_factory):
def global_config_dir(monkeypatch, tmp_path_factory):
monkeypatch.setenv(
env.DVC_GLOBAL_CONFIG_DIR, str(tmp_dir_factory.mktemp("studio-login"))
env.DVC_GLOBAL_CONFIG_DIR, str(tmp_path_factory.mktemp("studio-login"))
)
+3 -2
View File
@@ -4,6 +4,7 @@ from funcy import set_in
from dvc.render import FIELD, FILENAME, REVISION
from dvc.render.converter.vega import VegaConverter
from dvc.render.match import PlotsData, _squash_plots_properties, match_defs_renderers
from dvc.testing import matchers as M
@pytest.mark.parametrize(
@@ -118,7 +119,7 @@ def test_group_definitions(data, expected):
assert grouped == expected
def test_match_renderers(M):
def test_match_renderers():
data = {
"v1": {
"definitions": {
@@ -187,7 +188,7 @@ def test_match_renderers(M):
assert not renderer_with_errors.definition_errors
def test_flat_datapoints_errors_are_caught(M, mocker):
def test_flat_datapoints_errors_are_caught(mocker):
d = {}
d = set_in(
d,
+2 -1
View File
@@ -4,6 +4,7 @@ from networkx import DiGraph
from networkx.utils import graphs_equal
from dvc.repo.reproduce import get_active_graph, plan_repro
from dvc.testing import matchers as M
def test_active_graph(mocker):
@@ -26,7 +27,7 @@ def test_active_graph(mocker):
assert not graphs_equal(g, active)
def test_repro_plan(M):
def test_repro_plan():
r"""
1
/ \
+7
View File
@@ -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)
+1 -1
View File
@@ -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)