vcs-versioning 2.2.0__tar.gz → 2.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/CHANGELOG.md +15 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/MANIFEST.in +1 -0
- {vcs_versioning-2.2.0/src/vcs_versioning.egg-info → vcs_versioning-2.2.2}/PKG-INFO +1 -1
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/pyproject.toml +20 -18
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning/_backends/_hg.py +0 -3
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning/_compat.py +0 -13
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning/_config.py +0 -3
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning/_file_finders/__init__.py +33 -2
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning/_file_finders/_git.py +2 -16
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning/_file_finders/_hg.py +2 -12
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning/_file_finders/_jj.py +2 -14
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning/_get_version_impl.py +32 -8
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning/_overrides.py +1 -1
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning/_run_cmd.py +0 -2
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning/_scm_version.py +2 -6
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning/_version_schemes/_standard.py +0 -4
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2/src/vcs_versioning.egg-info}/PKG-INFO +1 -1
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning.egg-info/SOURCES.txt +1 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/testing_vcs/test_file_finders.py +1 -1
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/testing_vcs/test_regressions.py +35 -5
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/testing_vcs/test_tag_config.py +46 -0
- vcs_versioning-2.2.2/uv.lock +330 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/LICENSE.txt +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/README.md +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/setup.cfg +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/setup.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning/__init__.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning/__main__.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning/_backends/__init__.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning/_backends/_discover_vcs.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning/_backends/_git.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning/_backends/_hg_git.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning/_backends/_jj.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning/_backends/_scm_workdir.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning/_cli/__init__.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning/_cli/_args.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning/_cli/git_archival_full.txt +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning/_cli/git_archival_stable.txt +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning/_discover.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning/_dump_version.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning/_entrypoints.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning/_environment.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning/_exceptions.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning/_fallback_workdir.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning/_fallbacks.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning/_integration.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning/_integrator_helpers.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning/_legacy_parse.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning/_log.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning/_modify_version.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning/_node_utils.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning/_paths.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning/_project_overrides.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning/_protocols.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning/_pyproject_reading.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning/_requirement_cls.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning/_scm_metadata.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning/_test_utils.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning/_toml.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning/_types.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning/_version_cls.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning/_version_fields.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning/_version_inference.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning/_version_schemes/__init__.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning/_version_schemes/_common.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning/_version_schemes/_towncrier.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning/_worktree_discovery.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning/overrides.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning/py.typed +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning/test_api.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning.egg-info/dependency_links.txt +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning.egg-info/entry_points.txt +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning.egg-info/requires.txt +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning.egg-info/top_level.txt +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/testing_vcs/__init__.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/testing_vcs/conftest.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/testing_vcs/test_better_root_errors.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/testing_vcs/test_chain_api.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/testing_vcs/test_compat.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/testing_vcs/test_config.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/testing_vcs/test_expect_parse.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/testing_vcs/test_git.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/testing_vcs/test_hg_git.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/testing_vcs/test_integrator_helpers.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/testing_vcs/test_internal_log_level.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/testing_vcs/test_jj.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/testing_vcs/test_legacy_parse.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/testing_vcs/test_mercurial.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/testing_vcs/test_overrides_api.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/testing_vcs/test_overrides_env_reader.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/testing_vcs/test_project_overrides.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/testing_vcs/test_project_path.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/testing_vcs/test_scm_metadata.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/testing_vcs/test_version.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/testing_vcs/test_version_scheme_towncrier.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/testing_vcs/test_version_schemes.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/testing_vcs/test_workdir_api.py +0 -0
- {vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/testing_vcs/test_workdir_discovery.py +0 -0
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
<!-- towncrier release notes start -->
|
|
4
4
|
|
|
5
|
+
## 2.2.2 (2026-06-29)
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Add missing pytest-timeout to vcs-versioning test dependencies. ([#1461](https://github.com/pypa/setuptools-scm/issues/1461))
|
|
10
|
+
|
|
11
|
+
## 2.2.1 (2026-06-29)
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- Fix crash in `_warn_if_tracked` when the version file target is a relative path
|
|
16
|
+
by resolving it against the project root before comparison.
|
|
17
|
+
Also warn (instead of crashing) when the target resolves outside the project root. ([#1451](https://github.com/pypa/setuptools-scm/issues/1451))
|
|
18
|
+
|
|
5
19
|
## 2.2.0 (2026-06-24)
|
|
6
20
|
|
|
7
21
|
### Added
|
|
@@ -18,6 +32,7 @@
|
|
|
18
32
|
### Fixed
|
|
19
33
|
|
|
20
34
|
- Fix MetadataWorkdir crash when using custom `tag_regex` — stored tags are already parsed version strings and no longer re-parsed through the tag regex. ([#1439](https://github.com/pypa/setuptools-scm/issues/1439))
|
|
35
|
+
- Scope `list_tracked_files` to `project_root` in monorepo setups so that sdists no longer include files from sibling projects when `root=".."` is configured. ([#1440](https://github.com/pypa/setuptools-scm/issues/1440))
|
|
21
36
|
|
|
22
37
|
## 2.1.1 (2026-06-23)
|
|
23
38
|
|
|
@@ -46,7 +46,7 @@ dependencies = [
|
|
|
46
46
|
[dependency-groups]
|
|
47
47
|
test = [
|
|
48
48
|
"pytest",
|
|
49
|
-
"pytest-
|
|
49
|
+
"pytest-timeout",
|
|
50
50
|
"pytest-xdist",
|
|
51
51
|
]
|
|
52
52
|
|
|
@@ -105,27 +105,29 @@ tag_regex = "^vcs-versioning-(?P<version>v?\\d+(?:\\.\\d+){0,2}[^\\+]*)(?:\\+.*)
|
|
|
105
105
|
fallback_version = "0.1.0"
|
|
106
106
|
scm.git.describe_command = ["git", "describe", "--dirty", "--tags", "--long", "--match", "vcs-versioning-*"]
|
|
107
107
|
|
|
108
|
-
[tool.coverage.run]
|
|
109
|
-
branch = true
|
|
110
|
-
parallel = true
|
|
111
|
-
omit = [
|
|
112
|
-
"vcs_versioning/__about__.py",
|
|
113
|
-
]
|
|
114
|
-
|
|
115
|
-
[tool.coverage.report]
|
|
116
|
-
exclude_lines = [
|
|
117
|
-
"no cov",
|
|
118
|
-
"if __name__ == .__main__.:",
|
|
119
|
-
"if TYPE_CHECKING:",
|
|
120
|
-
]
|
|
121
|
-
|
|
122
108
|
[tool.pytest.ini_options]
|
|
109
|
+
minversion = "8.2"
|
|
123
110
|
testpaths = ["testing_vcs"]
|
|
124
111
|
python_files = ["test_*.py"]
|
|
125
|
-
|
|
112
|
+
xfail_strict = true
|
|
113
|
+
addopts = ["-ra", "--strict-config", "--strict-markers", "-p", "vcs_versioning.test_api"]
|
|
114
|
+
timeout = 300
|
|
115
|
+
filterwarnings = [
|
|
116
|
+
"error",
|
|
117
|
+
"ignore:.*tool\\.setuptools_scm.*",
|
|
118
|
+
"ignore:.*git archive did not support describe output.*:UserWarning",
|
|
119
|
+
"ignore:.*unprocessed git archival found.*:UserWarning",
|
|
120
|
+
"ignore:.*without VcsEnvironment.*:DeprecationWarning",
|
|
121
|
+
"ignore:.*Passing config to workdir.*:DeprecationWarning",
|
|
122
|
+
"ignore:.*tag_regex.*is deprecated.*:DeprecationWarning",
|
|
123
|
+
"ignore:.*write_to_source.*:DeprecationWarning",
|
|
124
|
+
"ignore:.*relative_to is expected to be a file.*:UserWarning",
|
|
125
|
+
]
|
|
126
|
+
log_level = "debug"
|
|
127
|
+
log_cli_level = "info"
|
|
126
128
|
markers = [
|
|
127
|
-
"issue:
|
|
128
|
-
"no_init:
|
|
129
|
+
"issue(id): reference to github issue",
|
|
130
|
+
"no_init: skip initializing the VCS repository in the test fixture",
|
|
129
131
|
"skip_commit: allows to skip committing in the helpers",
|
|
130
132
|
]
|
|
131
133
|
|
|
@@ -86,19 +86,6 @@ def strip_path_suffix(
|
|
|
86
86
|
return full_path[: -len(suffix_path)]
|
|
87
87
|
|
|
88
88
|
|
|
89
|
-
# Legacy aliases for backward compatibility during transition
|
|
90
|
-
def assert_path_endswith(
|
|
91
|
-
full_path: str, suffix_path: str, error_msg: str | None = None
|
|
92
|
-
) -> None:
|
|
93
|
-
"""Legacy alias - use strip_path_suffix instead."""
|
|
94
|
-
strip_path_suffix(full_path, suffix_path, error_msg)
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
def compute_path_prefix(full_path: str, suffix_path: str) -> str:
|
|
98
|
-
"""Legacy alias - use strip_path_suffix instead."""
|
|
99
|
-
return strip_path_suffix(full_path, suffix_path)
|
|
100
|
-
|
|
101
|
-
|
|
102
89
|
def norm_real(path: PathT) -> str:
|
|
103
90
|
"""Normalize and resolve a path (combining normcase and realpath).
|
|
104
91
|
|
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import logging
|
|
4
4
|
import os
|
|
5
5
|
import sys
|
|
6
|
-
from collections.abc import Callable, Mapping
|
|
6
|
+
from collections.abc import Callable, Iterable, Mapping
|
|
7
7
|
|
|
8
8
|
if sys.version_info >= (3, 10):
|
|
9
9
|
from typing import TypeGuard
|
|
@@ -140,4 +140,35 @@ def find_files(path: _t.PathT = "") -> list[str]:
|
|
|
140
140
|
return []
|
|
141
141
|
|
|
142
142
|
|
|
143
|
-
|
|
143
|
+
def collect_files_and_dirs(
|
|
144
|
+
raw_names: Iterable[str], toplevel: str
|
|
145
|
+
) -> tuple[set[str], set[str]]:
|
|
146
|
+
"""Normalize VCS file listings into absolute ``(files, dirs)`` sets.
|
|
147
|
+
|
|
148
|
+
Each backend produces a list of relative paths from its own command
|
|
149
|
+
(``git ls-files``, ``hg files``, ``jj file list``). This helper
|
|
150
|
+
normalizes case and separators, joins with *toplevel*, and collects
|
|
151
|
+
the directory ancestry — the same loop that was previously duplicated
|
|
152
|
+
in every backend.
|
|
153
|
+
"""
|
|
154
|
+
files: set[str] = set()
|
|
155
|
+
dirs: set[str] = {toplevel}
|
|
156
|
+
for name in raw_names:
|
|
157
|
+
if not name:
|
|
158
|
+
continue
|
|
159
|
+
name = os.path.normcase(name).replace("/", os.path.sep)
|
|
160
|
+
fullname = os.path.join(toplevel, name)
|
|
161
|
+
files.add(fullname)
|
|
162
|
+
dirname = os.path.dirname(fullname)
|
|
163
|
+
while len(dirname) > len(toplevel) and dirname not in dirs:
|
|
164
|
+
dirs.add(dirname)
|
|
165
|
+
dirname = os.path.dirname(dirname)
|
|
166
|
+
return files, dirs
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
__all__ = [
|
|
170
|
+
"scm_find_files",
|
|
171
|
+
"is_toplevel_acceptable",
|
|
172
|
+
"find_files",
|
|
173
|
+
"collect_files_and_dirs",
|
|
174
|
+
]
|
|
@@ -10,7 +10,7 @@ from .._backends._git import run_git
|
|
|
10
10
|
from .._compat import norm_real, strip_path_suffix
|
|
11
11
|
from .._integration import data_from_mime
|
|
12
12
|
from .._run_cmd import run as _run
|
|
13
|
-
from . import is_toplevel_acceptable, scm_find_files
|
|
13
|
+
from . import collect_files_and_dirs, is_toplevel_acceptable, scm_find_files
|
|
14
14
|
|
|
15
15
|
log = logging.getLogger(__name__)
|
|
16
16
|
|
|
@@ -85,22 +85,8 @@ def _git_ls_files_and_dirs(
|
|
|
85
85
|
log.error("listing git files failed - pretending there aren't any")
|
|
86
86
|
return set(), set()
|
|
87
87
|
|
|
88
|
-
# Normalize toplevel to match what scm_find_files expects:
|
|
89
|
-
# absolute, symlinks resolved, case-normalized (normcase).
|
|
90
88
|
toplevel = norm_real(toplevel)
|
|
91
|
-
|
|
92
|
-
git_dirs: set[str] = {toplevel}
|
|
93
|
-
for name in res.stdout.rstrip("\0").split("\0"):
|
|
94
|
-
if not name:
|
|
95
|
-
continue
|
|
96
|
-
name = os.path.normcase(name).replace("/", os.path.sep)
|
|
97
|
-
fullname = os.path.join(toplevel, name)
|
|
98
|
-
git_files.add(fullname)
|
|
99
|
-
dirname = os.path.dirname(fullname)
|
|
100
|
-
while len(dirname) > len(toplevel) and dirname not in git_dirs:
|
|
101
|
-
git_dirs.add(dirname)
|
|
102
|
-
dirname = os.path.dirname(dirname)
|
|
103
|
-
return git_files, git_dirs
|
|
89
|
+
return collect_files_and_dirs(res.stdout.rstrip("\0").split("\0"), toplevel)
|
|
104
90
|
|
|
105
91
|
|
|
106
92
|
def git_find_files(path: _t.PathT = "") -> list[str]:
|
|
@@ -8,7 +8,7 @@ from .. import _types as _t
|
|
|
8
8
|
from .._backends._hg import run_hg
|
|
9
9
|
from .._compat import norm_real
|
|
10
10
|
from .._integration import data_from_mime
|
|
11
|
-
from . import is_toplevel_acceptable, scm_find_files
|
|
11
|
+
from . import collect_files_and_dirs, is_toplevel_acceptable, scm_find_files
|
|
12
12
|
|
|
13
13
|
log = logging.getLogger(__name__)
|
|
14
14
|
|
|
@@ -34,20 +34,10 @@ def _hg_ls_files_and_dirs(
|
|
|
34
34
|
hg_command: str | None = None,
|
|
35
35
|
timeout: int | None = None,
|
|
36
36
|
) -> tuple[set[str], set[str]]:
|
|
37
|
-
hg_files: set[str] = set()
|
|
38
|
-
hg_dirs = {toplevel}
|
|
39
37
|
res = run_hg(["files"], cwd=toplevel, hg_command=hg_command, timeout=timeout)
|
|
40
38
|
if res.returncode:
|
|
41
39
|
return set(), set()
|
|
42
|
-
|
|
43
|
-
name = os.path.normcase(name).replace("/", os.path.sep)
|
|
44
|
-
fullname = os.path.join(toplevel, name)
|
|
45
|
-
hg_files.add(fullname)
|
|
46
|
-
dirname = os.path.dirname(fullname)
|
|
47
|
-
while len(dirname) > len(toplevel) and dirname not in hg_dirs:
|
|
48
|
-
hg_dirs.add(dirname)
|
|
49
|
-
dirname = os.path.dirname(dirname)
|
|
50
|
-
return hg_files, hg_dirs
|
|
40
|
+
return collect_files_and_dirs(res.stdout.splitlines(), toplevel)
|
|
51
41
|
|
|
52
42
|
|
|
53
43
|
def hg_find_files(path: str = "") -> list[str]:
|
|
@@ -13,7 +13,7 @@ import subprocess
|
|
|
13
13
|
from .. import _types as _t
|
|
14
14
|
from .._compat import norm_real
|
|
15
15
|
from .._run_cmd import run as _run
|
|
16
|
-
from . import is_toplevel_acceptable, scm_find_files
|
|
16
|
+
from . import collect_files_and_dirs, is_toplevel_acceptable, scm_find_files
|
|
17
17
|
|
|
18
18
|
log = logging.getLogger(__name__)
|
|
19
19
|
|
|
@@ -51,19 +51,7 @@ def _jj_ls_files_and_dirs(
|
|
|
51
51
|
log.error("listing jj files failed - pretending there aren't any")
|
|
52
52
|
return set(), set()
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
jj_dirs: set[str] = {toplevel}
|
|
56
|
-
for name in res.stdout.strip().split("\n"):
|
|
57
|
-
if not name:
|
|
58
|
-
continue
|
|
59
|
-
name = os.path.normcase(name).replace("/", os.path.sep)
|
|
60
|
-
fullname = os.path.join(toplevel, name)
|
|
61
|
-
jj_files.add(fullname)
|
|
62
|
-
dirname = os.path.dirname(fullname)
|
|
63
|
-
while len(dirname) > len(toplevel) and dirname not in jj_dirs:
|
|
64
|
-
jj_dirs.add(dirname)
|
|
65
|
-
dirname = os.path.dirname(dirname)
|
|
66
|
-
return jj_files, jj_dirs
|
|
54
|
+
return collect_files_and_dirs(res.stdout.strip().split("\n"), toplevel)
|
|
67
55
|
|
|
68
56
|
|
|
69
57
|
def jj_find_files(path: _t.PathT = "") -> list[str]:
|
|
@@ -6,13 +6,18 @@ import re
|
|
|
6
6
|
import warnings
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
from re import Pattern
|
|
9
|
-
from typing import Any, NoReturn
|
|
9
|
+
from typing import TYPE_CHECKING, Any, NoReturn
|
|
10
10
|
|
|
11
11
|
from . import _config
|
|
12
12
|
from . import _types as _t
|
|
13
13
|
from ._config import Configuration, TagConfiguration
|
|
14
14
|
from ._environment import resolve_runtime_env
|
|
15
15
|
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from ._config import ParseFunction
|
|
18
|
+
from ._environment import VcsEnvironment
|
|
19
|
+
from ._version_cls import Version as _VersionType
|
|
20
|
+
|
|
16
21
|
# Backward-compat re-export used by vcs-versioning/setup.py
|
|
17
22
|
from ._legacy_parse import (
|
|
18
23
|
resolved_fallback_root as resolved_fallback_root, # noqa: F401
|
|
@@ -90,15 +95,31 @@ def _resolve_version(config: Configuration) -> ScmVersion | None:
|
|
|
90
95
|
return None
|
|
91
96
|
|
|
92
97
|
|
|
93
|
-
def _warn_if_tracked(target: Path, root: Path, config: Configuration) ->
|
|
98
|
+
def _warn_if_tracked(target: Path, root: Path, config: Configuration) -> bool:
|
|
94
99
|
"""Warn when *target* is tracked in version control (#468).
|
|
95
100
|
|
|
96
101
|
Writing a version file that is tracked makes ``git describe --dirty``
|
|
97
102
|
report a dirty tree on tag checkouts, causing wrong version numbers.
|
|
103
|
+
|
|
104
|
+
Returns False if target resolves outside root (caller should skip the write).
|
|
98
105
|
"""
|
|
99
106
|
from ._backends._git import GitWorkdir
|
|
100
107
|
from ._backends._hg import HgWorkdir
|
|
101
108
|
|
|
109
|
+
if not target.is_absolute():
|
|
110
|
+
target = root / target
|
|
111
|
+
resolved_target = target.resolve()
|
|
112
|
+
resolved_root = root.resolve()
|
|
113
|
+
try:
|
|
114
|
+
resolved_target.relative_to(resolved_root)
|
|
115
|
+
except ValueError:
|
|
116
|
+
# todo: emit as GitHub Actions warning via ::warning:: syntax
|
|
117
|
+
warnings.warn(
|
|
118
|
+
f"version file target {target} resolves outside of the project root {root}",
|
|
119
|
+
stacklevel=2,
|
|
120
|
+
)
|
|
121
|
+
return False
|
|
122
|
+
|
|
102
123
|
for workdir_cls in (GitWorkdir, HgWorkdir):
|
|
103
124
|
try:
|
|
104
125
|
wd = workdir_cls.from_potential_worktree(root, config)
|
|
@@ -113,7 +134,8 @@ def _warn_if_tracked(target: Path, root: Path, config: Configuration) -> None:
|
|
|
113
134
|
" See https://github.com/pypa/setuptools-scm/issues/468",
|
|
114
135
|
stacklevel=3,
|
|
115
136
|
)
|
|
116
|
-
return
|
|
137
|
+
return True
|
|
138
|
+
return True
|
|
117
139
|
|
|
118
140
|
|
|
119
141
|
def write_version_files(
|
|
@@ -125,7 +147,8 @@ def write_version_files(
|
|
|
125
147
|
|
|
126
148
|
write_to = Path(config.write_to)
|
|
127
149
|
target = root / write_to if not write_to.is_absolute() else write_to
|
|
128
|
-
_warn_if_tracked(target, root, config)
|
|
150
|
+
if not _warn_if_tracked(target, root, config):
|
|
151
|
+
return
|
|
129
152
|
|
|
130
153
|
dump_version(
|
|
131
154
|
root=config.root,
|
|
@@ -142,7 +165,8 @@ def write_version_files(
|
|
|
142
165
|
# todo: use a better name than fallback root
|
|
143
166
|
assert config.relative_to is not None
|
|
144
167
|
target = Path(config.relative_to).parent.joinpath(version_file)
|
|
145
|
-
_warn_if_tracked(target, root, config)
|
|
168
|
+
if not _warn_if_tracked(target, root, config):
|
|
169
|
+
return
|
|
146
170
|
|
|
147
171
|
write_version_to_path(
|
|
148
172
|
target,
|
|
@@ -252,14 +276,14 @@ def get_version(
|
|
|
252
276
|
parentdir_prefix_version: str | None = None,
|
|
253
277
|
fallback_version: str | None = None,
|
|
254
278
|
fallback_root: _t.PathT = ".",
|
|
255
|
-
parse:
|
|
279
|
+
parse: ParseFunction | None = None,
|
|
256
280
|
git_describe_command: _t.CMD_TYPE | None = None,
|
|
257
281
|
dist_name: str | None = None,
|
|
258
|
-
version_cls:
|
|
282
|
+
version_cls: type[_VersionType] | str | None = None,
|
|
259
283
|
normalize: bool = True,
|
|
260
284
|
search_parent_directories: bool = False,
|
|
261
285
|
scm: dict[str, Any] | None = None,
|
|
262
|
-
_env:
|
|
286
|
+
_env: VcsEnvironment | None = None,
|
|
263
287
|
) -> str:
|
|
264
288
|
"""
|
|
265
289
|
If supplied, relative_to should be a file from which root may
|
|
@@ -19,7 +19,7 @@ from packaging.utils import canonicalize_name
|
|
|
19
19
|
|
|
20
20
|
from . import _config
|
|
21
21
|
from . import _types as _t
|
|
22
|
-
from ._scm_version import ScmVersion, meta
|
|
22
|
+
from ._scm_version import ScmVersion, meta
|
|
23
23
|
from ._version_cls import Version as _Version
|
|
24
24
|
|
|
25
25
|
log = logging.getLogger(__name__)
|
|
@@ -152,19 +152,15 @@ def tag_to_version(
|
|
|
152
152
|
version_str = tag_dict["version"]
|
|
153
153
|
log.debug("version pre parse %s", version_str)
|
|
154
154
|
|
|
155
|
-
# Try to create version from base version first
|
|
156
155
|
try:
|
|
157
156
|
version: _Version = config.version_cls(version_str)
|
|
158
157
|
log.debug("version=%r", version)
|
|
159
158
|
except Exception:
|
|
160
159
|
warnings.warn(
|
|
161
|
-
f"tag {tag!r}
|
|
160
|
+
f"tag {tag!r} version {version_str!r} could not be parsed",
|
|
162
161
|
stacklevel=2,
|
|
163
162
|
)
|
|
164
|
-
|
|
165
|
-
version = config.version_cls(version_str)
|
|
166
|
-
log.debug("version=%r", version)
|
|
167
|
-
return version
|
|
163
|
+
return None
|
|
168
164
|
|
|
169
165
|
# If base version is valid, check if we can preserve the suffix
|
|
170
166
|
if suffix := tag_dict.get("suffix", ""):
|
{vcs_versioning-2.2.0 → vcs_versioning-2.2.2}/src/vcs_versioning/_version_schemes/_standard.py
RENAMED
|
@@ -11,7 +11,6 @@ import re
|
|
|
11
11
|
import warnings
|
|
12
12
|
from datetime import date, datetime, timedelta, timezone
|
|
13
13
|
from re import Match
|
|
14
|
-
from typing import TYPE_CHECKING
|
|
15
14
|
|
|
16
15
|
from .. import _modify_version
|
|
17
16
|
from .._exceptions import DirtyWorkingTreeError
|
|
@@ -19,9 +18,6 @@ from .._scm_version import ScmVersion, _parse_version_tag
|
|
|
19
18
|
from .._version_cls import Version as PkgVersion
|
|
20
19
|
from ._common import SEMVER_LEN, SEMVER_MINOR, SEMVER_PATCH
|
|
21
20
|
|
|
22
|
-
if TYPE_CHECKING:
|
|
23
|
-
pass
|
|
24
|
-
|
|
25
21
|
log = logging.getLogger(__name__)
|
|
26
22
|
|
|
27
23
|
|
|
@@ -13,7 +13,7 @@ from vcs_versioning.test_api import WorkDir
|
|
|
13
13
|
def inwd(
|
|
14
14
|
request: pytest.FixtureRequest, wd: WorkDir, monkeypatch: pytest.MonkeyPatch
|
|
15
15
|
) -> WorkDir:
|
|
16
|
-
param: str = request.param
|
|
16
|
+
param: str = request.param
|
|
17
17
|
if param == "git":
|
|
18
18
|
wd.setup_git(monkeypatch)
|
|
19
19
|
elif param == "hg":
|
|
@@ -82,11 +82,7 @@ def test_write_to_absolute_path_passes_when_subdir_of_root(tmp_path: Path) -> No
|
|
|
82
82
|
write_version_files(replace(c, write_to="VERSION.py"), "1.0", v)
|
|
83
83
|
subdir = tmp_path / "subdir"
|
|
84
84
|
subdir.mkdir()
|
|
85
|
-
with pytest.
|
|
86
|
-
# todo: python version specific error list
|
|
87
|
-
ValueError,
|
|
88
|
-
match=r".*VERSION.py' .* .*subdir.*",
|
|
89
|
-
):
|
|
85
|
+
with pytest.warns(UserWarning, match=r"resolves outside of"):
|
|
90
86
|
write_version_files(replace(c, root=subdir), "1.0", v)
|
|
91
87
|
|
|
92
88
|
|
|
@@ -138,6 +134,40 @@ def test_no_warn_when_version_file_not_tracked(tmp_path: Path, scm: str) -> None
|
|
|
138
134
|
assert tracked_warnings == []
|
|
139
135
|
|
|
140
136
|
|
|
137
|
+
@pytest.mark.issue(1451)
|
|
138
|
+
def test_warn_if_tracked_relative_target_no_crash(tmp_path: Path) -> None:
|
|
139
|
+
"""_warn_if_tracked must not crash when target is relative and root is absolute."""
|
|
140
|
+
from vcs_versioning._get_version_impl import _warn_if_tracked
|
|
141
|
+
|
|
142
|
+
wd = WorkDir(tmp_path).setup_git()
|
|
143
|
+
version_file = tmp_path / "pkg" / "__version__.py"
|
|
144
|
+
version_file.parent.mkdir()
|
|
145
|
+
version_file.write_text("__version__ = '0.0.0'\n")
|
|
146
|
+
wd.add_and_commit("add tracked version file")
|
|
147
|
+
|
|
148
|
+
c = Configuration(root=tmp_path)
|
|
149
|
+
relative_target = Path("pkg/__version__.py")
|
|
150
|
+
|
|
151
|
+
with pytest.warns(UserWarning, match="tracked by version control"):
|
|
152
|
+
_warn_if_tracked(relative_target, tmp_path, c)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
@pytest.mark.issue(1451)
|
|
156
|
+
def test_warn_if_tracked_warns_when_target_escapes_root(tmp_path: Path) -> None:
|
|
157
|
+
"""_warn_if_tracked must warn when target resolves outside root."""
|
|
158
|
+
from vcs_versioning._get_version_impl import _warn_if_tracked
|
|
159
|
+
|
|
160
|
+
project = tmp_path / "project"
|
|
161
|
+
project.mkdir()
|
|
162
|
+
WorkDir(project).setup_git()
|
|
163
|
+
|
|
164
|
+
c = Configuration(root=project)
|
|
165
|
+
escaping_target = Path("../../etc/passwd")
|
|
166
|
+
|
|
167
|
+
with pytest.warns(UserWarning, match="resolves outside of"):
|
|
168
|
+
_warn_if_tracked(escaping_target, project, c)
|
|
169
|
+
|
|
170
|
+
|
|
141
171
|
@pytest.mark.issue(1423)
|
|
142
172
|
def test_parse_version_importable_from_get_version_impl() -> None:
|
|
143
173
|
"""setuptools-scm <=10.0.x imports parse_version from this module."""
|
|
@@ -4,6 +4,8 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import warnings
|
|
6
6
|
|
|
7
|
+
import pytest
|
|
8
|
+
from packaging.version import Version
|
|
7
9
|
from vcs_versioning import Configuration
|
|
8
10
|
from vcs_versioning._config import TagConfiguration
|
|
9
11
|
from vcs_versioning._scm_version import tag_to_version
|
|
@@ -109,6 +111,50 @@ class TestTagPrefixStripping:
|
|
|
109
111
|
assert str(version) == "1.2.3"
|
|
110
112
|
|
|
111
113
|
|
|
114
|
+
class TestTagToVersionSuffixPreservation:
|
|
115
|
+
"""Test that tag_to_version preserves +local suffixes when possible."""
|
|
116
|
+
|
|
117
|
+
def test_tag_with_build_metadata_preserved(self) -> None:
|
|
118
|
+
config = Configuration()
|
|
119
|
+
version = tag_to_version("1.2.3+build.123", config)
|
|
120
|
+
assert version is not None
|
|
121
|
+
assert str(version) == "1.2.3+build.123"
|
|
122
|
+
|
|
123
|
+
def test_tag_with_invalid_suffix_stripped(self) -> None:
|
|
124
|
+
config = Configuration()
|
|
125
|
+
# "+invalid!!" is captured as suffix by the default regex but
|
|
126
|
+
# Version("1.2.3+invalid!!") raises, so the suffix gets stripped
|
|
127
|
+
with pytest.warns(UserWarning, match="will be stripped of its suffix"):
|
|
128
|
+
version = tag_to_version("1.2.3+invalid!!", config)
|
|
129
|
+
assert version is not None
|
|
130
|
+
assert str(version) == "1.2.3"
|
|
131
|
+
|
|
132
|
+
@pytest.mark.parametrize(
|
|
133
|
+
("tag", "expected"),
|
|
134
|
+
[
|
|
135
|
+
("1.0.0+ci.456", "1.0.0+ci.456"),
|
|
136
|
+
("v2.0.0+build.789", "2.0.0+build.789"),
|
|
137
|
+
("release-1.0.0+metadata", "1.0.0+metadata"),
|
|
138
|
+
],
|
|
139
|
+
)
|
|
140
|
+
def test_various_build_metadata_tags(self, tag: str, expected: str) -> None:
|
|
141
|
+
config = Configuration()
|
|
142
|
+
version = tag_to_version(tag, config)
|
|
143
|
+
assert version is not None
|
|
144
|
+
assert str(version) == expected
|
|
145
|
+
|
|
146
|
+
def test_custom_version_cls_parse_failure_returns_none(self) -> None:
|
|
147
|
+
"""When version_cls rejects the base version string, return None."""
|
|
148
|
+
|
|
149
|
+
def rejecting_version_cls(version_str: str) -> Version:
|
|
150
|
+
raise ValueError(f"rejected: {version_str}")
|
|
151
|
+
|
|
152
|
+
config = Configuration(version_cls=rejecting_version_cls) # type: ignore[arg-type]
|
|
153
|
+
with pytest.warns(UserWarning, match="could not be parsed"):
|
|
154
|
+
result = tag_to_version("1.2.3", config)
|
|
155
|
+
assert result is None
|
|
156
|
+
|
|
157
|
+
|
|
112
158
|
class TestConfigFromData:
|
|
113
159
|
"""Test that Configuration.from_data correctly parses tag config."""
|
|
114
160
|
|