vcs-versioning 2.1.1__tar.gz → 2.2.0__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.1.1 → vcs_versioning-2.2.0}/CHANGELOG.md +17 -0
- {vcs_versioning-2.1.1/src/vcs_versioning.egg-info → vcs_versioning-2.2.0}/PKG-INFO +6 -4
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/pyproject.toml +7 -5
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/src/vcs_versioning/_backends/_git.py +6 -2
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/src/vcs_versioning/_backends/_hg.py +1 -1
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/src/vcs_versioning/_backends/_hg_git.py +1 -1
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/src/vcs_versioning/_backends/_jj.py +1 -1
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/src/vcs_versioning/_backends/_scm_workdir.py +33 -7
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/src/vcs_versioning/_cli/__init__.py +9 -2
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/src/vcs_versioning/_compat.py +32 -4
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/src/vcs_versioning/_entrypoints.py +3 -2
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/src/vcs_versioning/_fallback_workdir.py +18 -1
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/src/vcs_versioning/_file_finders/__init__.py +6 -1
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/src/vcs_versioning/_file_finders/_git.py +3 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/src/vcs_versioning/_get_version_impl.py +4 -4
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/src/vcs_versioning/_legacy_parse.py +11 -10
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/src/vcs_versioning/_overrides.py +14 -4
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/src/vcs_versioning/_protocols.py +10 -3
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/src/vcs_versioning/_pyproject_reading.py +7 -3
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/src/vcs_versioning/_run_cmd.py +7 -1
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/src/vcs_versioning/_scm_metadata.py +8 -7
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/src/vcs_versioning/_scm_version.py +17 -17
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/src/vcs_versioning/_toml.py +17 -7
- vcs_versioning-2.2.0/src/vcs_versioning/_types.py +40 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/src/vcs_versioning/_version_cls.py +9 -3
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/src/vcs_versioning/_worktree_discovery.py +4 -4
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0/src/vcs_versioning.egg-info}/PKG-INFO +6 -4
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/src/vcs_versioning.egg-info/requires.txt +1 -1
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/testing_vcs/test_legacy_parse.py +5 -5
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/testing_vcs/test_overrides_api.py +1 -4
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/testing_vcs/test_workdir_discovery.py +60 -42
- vcs_versioning-2.1.1/src/vcs_versioning/_types.py +0 -29
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/LICENSE.txt +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/MANIFEST.in +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/README.md +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/setup.cfg +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/setup.py +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/src/vcs_versioning/__init__.py +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/src/vcs_versioning/__main__.py +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/src/vcs_versioning/_backends/__init__.py +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/src/vcs_versioning/_backends/_discover_vcs.py +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/src/vcs_versioning/_cli/_args.py +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/src/vcs_versioning/_cli/git_archival_full.txt +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/src/vcs_versioning/_cli/git_archival_stable.txt +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/src/vcs_versioning/_config.py +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/src/vcs_versioning/_discover.py +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/src/vcs_versioning/_dump_version.py +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/src/vcs_versioning/_environment.py +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/src/vcs_versioning/_exceptions.py +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/src/vcs_versioning/_fallbacks.py +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/src/vcs_versioning/_file_finders/_hg.py +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/src/vcs_versioning/_file_finders/_jj.py +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/src/vcs_versioning/_integration.py +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/src/vcs_versioning/_integrator_helpers.py +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/src/vcs_versioning/_log.py +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/src/vcs_versioning/_modify_version.py +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/src/vcs_versioning/_node_utils.py +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/src/vcs_versioning/_paths.py +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/src/vcs_versioning/_project_overrides.py +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/src/vcs_versioning/_requirement_cls.py +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/src/vcs_versioning/_test_utils.py +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/src/vcs_versioning/_version_fields.py +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/src/vcs_versioning/_version_inference.py +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/src/vcs_versioning/_version_schemes/__init__.py +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/src/vcs_versioning/_version_schemes/_common.py +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/src/vcs_versioning/_version_schemes/_standard.py +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/src/vcs_versioning/_version_schemes/_towncrier.py +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/src/vcs_versioning/overrides.py +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/src/vcs_versioning/py.typed +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/src/vcs_versioning/test_api.py +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/src/vcs_versioning.egg-info/SOURCES.txt +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/src/vcs_versioning.egg-info/dependency_links.txt +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/src/vcs_versioning.egg-info/entry_points.txt +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/src/vcs_versioning.egg-info/top_level.txt +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/testing_vcs/__init__.py +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/testing_vcs/conftest.py +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/testing_vcs/test_better_root_errors.py +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/testing_vcs/test_chain_api.py +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/testing_vcs/test_compat.py +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/testing_vcs/test_config.py +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/testing_vcs/test_expect_parse.py +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/testing_vcs/test_file_finders.py +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/testing_vcs/test_git.py +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/testing_vcs/test_hg_git.py +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/testing_vcs/test_integrator_helpers.py +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/testing_vcs/test_internal_log_level.py +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/testing_vcs/test_jj.py +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/testing_vcs/test_mercurial.py +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/testing_vcs/test_overrides_env_reader.py +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/testing_vcs/test_project_overrides.py +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/testing_vcs/test_project_path.py +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/testing_vcs/test_regressions.py +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/testing_vcs/test_scm_metadata.py +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/testing_vcs/test_tag_config.py +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/testing_vcs/test_version.py +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/testing_vcs/test_version_scheme_towncrier.py +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/testing_vcs/test_version_schemes.py +0 -0
- {vcs_versioning-2.1.1 → vcs_versioning-2.2.0}/testing_vcs/test_workdir_api.py +0 -0
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
<!-- towncrier release notes start -->
|
|
4
4
|
|
|
5
|
+
## 2.2.0 (2026-06-24)
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Restore Python 3.8 and 3.9 support, re-enabling use as a build dependency for projects like scikit-build that still support these versions. ([#1445](https://github.com/pypa/setuptools-scm/issues/1445))
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
### Miscellaneous
|
|
13
|
+
|
|
14
|
+
- Move PKG-INFO discovery tests to setuptools-scm where the entry points are registered. ([#1446](https://github.com/pypa/setuptools-scm/issues/1446))
|
|
15
|
+
|
|
16
|
+
## 2.1.2 (2026-06-23)
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- 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))
|
|
21
|
+
|
|
5
22
|
## 2.1.1 (2026-06-23)
|
|
6
23
|
|
|
7
24
|
### Fixed
|
|
@@ -1,26 +1,28 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: vcs-versioning
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: the blessed package to manage your versions by vcs metadata
|
|
5
5
|
Author-email: Ronny Pfannschmidt <opensource@ronnypfannschmidt.de>
|
|
6
|
-
License
|
|
6
|
+
License: MIT
|
|
7
7
|
Project-URL: Documentation, https://github.com/pypa/setuptools-scm#readme
|
|
8
8
|
Project-URL: Issues, https://github.com/pypa/setuptools-scm/issues
|
|
9
9
|
Project-URL: Source, https://github.com/pypa/setuptools-scm
|
|
10
10
|
Classifier: Development Status :: 4 - Beta
|
|
11
11
|
Classifier: Programming Language :: Python
|
|
12
12
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
13
15
|
Classifier: Programming Language :: Python :: 3.10
|
|
14
16
|
Classifier: Programming Language :: Python :: 3.11
|
|
15
17
|
Classifier: Programming Language :: Python :: 3.12
|
|
16
18
|
Classifier: Programming Language :: Python :: 3.13
|
|
17
19
|
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
-
Requires-Python: >=3.
|
|
20
|
+
Requires-Python: >=3.8
|
|
19
21
|
Description-Content-Type: text/markdown
|
|
20
22
|
License-File: LICENSE.txt
|
|
21
23
|
Requires-Dist: packaging>=20
|
|
22
24
|
Requires-Dist: tomli>=1; python_version < "3.11"
|
|
23
|
-
Requires-Dist: typing-extensions; python_version < "3.11"
|
|
25
|
+
Requires-Dist: typing-extensions>=4.1; python_version < "3.11"
|
|
24
26
|
Dynamic: license-file
|
|
25
27
|
|
|
26
28
|
# vcs-versioning
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
[build-system]
|
|
7
7
|
build-backend = "setuptools.build_meta"
|
|
8
8
|
requires = [
|
|
9
|
-
"setuptools>=
|
|
9
|
+
"setuptools>=45",
|
|
10
10
|
"packaging>=20",
|
|
11
|
-
'typing-extensions; python_version < "3.11"',
|
|
11
|
+
'typing-extensions>=4.1; python_version < "3.11"',
|
|
12
12
|
]
|
|
13
13
|
|
|
14
14
|
[project]
|
|
@@ -17,15 +17,17 @@ description = "the blessed package to manage your versions by vcs metadata"
|
|
|
17
17
|
readme = "README.md"
|
|
18
18
|
keywords = [
|
|
19
19
|
]
|
|
20
|
-
license = "MIT"
|
|
20
|
+
license = {text = "MIT"}
|
|
21
21
|
authors = [
|
|
22
22
|
{ name = "Ronny Pfannschmidt", email = "opensource@ronnypfannschmidt.de" },
|
|
23
23
|
]
|
|
24
|
-
requires-python = ">=3.
|
|
24
|
+
requires-python = ">=3.8"
|
|
25
25
|
classifiers = [
|
|
26
26
|
"Development Status :: 4 - Beta",
|
|
27
27
|
"Programming Language :: Python",
|
|
28
28
|
"Programming Language :: Python :: 3 :: Only",
|
|
29
|
+
"Programming Language :: Python :: 3.8",
|
|
30
|
+
"Programming Language :: Python :: 3.9",
|
|
29
31
|
"Programming Language :: Python :: 3.10",
|
|
30
32
|
"Programming Language :: Python :: 3.11",
|
|
31
33
|
"Programming Language :: Python :: 3.12",
|
|
@@ -38,7 +40,7 @@ dynamic = [
|
|
|
38
40
|
dependencies = [
|
|
39
41
|
"packaging>=20",
|
|
40
42
|
'tomli>=1; python_version < "3.11"',
|
|
41
|
-
'typing-extensions; python_version < "3.11"',
|
|
43
|
+
'typing-extensions>=4.1; python_version < "3.11"',
|
|
42
44
|
]
|
|
43
45
|
|
|
44
46
|
[dependency-groups]
|
|
@@ -238,11 +238,15 @@ class GitWorkdir(Workdir):
|
|
|
238
238
|
return _git_parse_inner(config, self, pre_parse=effective_pre_parse)
|
|
239
239
|
|
|
240
240
|
def list_tracked_files(self, path: Path | str = "") -> list[str]:
|
|
241
|
-
"""List files tracked by git, honoring export-ignore.
|
|
241
|
+
"""List files tracked by git, honoring export-ignore.
|
|
242
|
+
|
|
243
|
+
When no path is given, scopes to ``project_root`` (not the VCS root)
|
|
244
|
+
so that monorepo projects only list their own files.
|
|
245
|
+
"""
|
|
242
246
|
from .._file_finders import scm_find_files
|
|
243
247
|
from .._file_finders._git import _git_ls_files_and_dirs
|
|
244
248
|
|
|
245
|
-
base = str(path) if path else str(self.
|
|
249
|
+
base = str(path) if path else str(self.project_root)
|
|
246
250
|
git_files, git_dirs = _git_ls_files_and_dirs(
|
|
247
251
|
str(self.path), timeout=self._subprocess_timeout
|
|
248
252
|
)
|
|
@@ -293,7 +293,7 @@ class HgWorkdir(Workdir):
|
|
|
293
293
|
from .._file_finders import scm_find_files
|
|
294
294
|
from .._file_finders._hg import _hg_ls_files_and_dirs
|
|
295
295
|
|
|
296
|
-
base = str(path) if path else str(self.
|
|
296
|
+
base = str(path) if path else str(self.project_root)
|
|
297
297
|
hg_files, hg_dirs = _hg_ls_files_and_dirs(
|
|
298
298
|
str(self.path),
|
|
299
299
|
hg_command=self._hg_command,
|
|
@@ -95,7 +95,7 @@ class GitWorkdirHgClient(GitWorkdir, HgWorkdir):
|
|
|
95
95
|
from .._file_finders import scm_find_files
|
|
96
96
|
from .._file_finders._hg import _hg_ls_files_and_dirs
|
|
97
97
|
|
|
98
|
-
base = str(path) if path else str(self.
|
|
98
|
+
base = str(path) if path else str(self.project_root)
|
|
99
99
|
hg_files, hg_dirs = _hg_ls_files_and_dirs(
|
|
100
100
|
str(self.path),
|
|
101
101
|
hg_command=self._hg_command,
|
|
@@ -263,7 +263,7 @@ class JjWorkdir(Workdir):
|
|
|
263
263
|
from .._file_finders import scm_find_files
|
|
264
264
|
from .._file_finders._jj import _jj_ls_files_and_dirs
|
|
265
265
|
|
|
266
|
-
base = str(path) if path else str(self.
|
|
266
|
+
base = str(path) if path else str(self.project_root)
|
|
267
267
|
jj_files, jj_dirs = _jj_ls_files_and_dirs(
|
|
268
268
|
str(self.path), timeout=self._subprocess_timeout
|
|
269
269
|
)
|
|
@@ -5,7 +5,7 @@ from dataclasses import dataclass
|
|
|
5
5
|
from dataclasses import field as dc_field
|
|
6
6
|
from datetime import date, datetime, timezone
|
|
7
7
|
from pathlib import Path
|
|
8
|
-
from typing import TYPE_CHECKING
|
|
8
|
+
from typing import TYPE_CHECKING, overload
|
|
9
9
|
|
|
10
10
|
from .._scm_version import ScmVersion
|
|
11
11
|
|
|
@@ -16,6 +16,37 @@ if TYPE_CHECKING:
|
|
|
16
16
|
log = logging.getLogger(__name__)
|
|
17
17
|
|
|
18
18
|
|
|
19
|
+
class _ProjectRootDescriptor:
|
|
20
|
+
"""Descriptor for ``project_root`` that defaults to ``path``.
|
|
21
|
+
|
|
22
|
+
Acts as default when no value has been set on the instance.
|
|
23
|
+
Stores explicitly assigned values in the instance ``__dict__``.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __set_name__(self, owner: type, name: str) -> None:
|
|
27
|
+
self._name = name
|
|
28
|
+
|
|
29
|
+
@overload
|
|
30
|
+
def __get__(self, obj: None, objtype: type) -> _ProjectRootDescriptor: ...
|
|
31
|
+
|
|
32
|
+
@overload
|
|
33
|
+
def __get__(self, obj: ScmWorkdir, objtype: type | None = None) -> Path: ...
|
|
34
|
+
|
|
35
|
+
def __get__(
|
|
36
|
+
self, obj: ScmWorkdir | None, objtype: type | None = None
|
|
37
|
+
) -> Path | _ProjectRootDescriptor:
|
|
38
|
+
if obj is None:
|
|
39
|
+
return self
|
|
40
|
+
value: Path | None = obj.__dict__.get(self._name)
|
|
41
|
+
if value is None:
|
|
42
|
+
return obj.path
|
|
43
|
+
return value
|
|
44
|
+
|
|
45
|
+
def __set__(self, obj: ScmWorkdir, value: Path | None) -> None:
|
|
46
|
+
if isinstance(value, Path):
|
|
47
|
+
obj.__dict__[self._name] = value
|
|
48
|
+
|
|
49
|
+
|
|
19
50
|
def get_latest_file_mtime(changed_files: list[str], base_path: Path) -> date | None:
|
|
20
51
|
"""Get the latest modification time of the given files.
|
|
21
52
|
|
|
@@ -61,15 +92,11 @@ class ScmWorkdir:
|
|
|
61
92
|
"""
|
|
62
93
|
|
|
63
94
|
path: Path
|
|
64
|
-
project_root: Path
|
|
95
|
+
project_root: Path = _ProjectRootDescriptor() # type: ignore[assignment]
|
|
65
96
|
|
|
66
97
|
_config: Configuration | None = dc_field(default=None, repr=False, compare=False)
|
|
67
98
|
"""Back-reference to the ``Configuration`` that discovered this workdir."""
|
|
68
99
|
|
|
69
|
-
def __post_init__(self) -> None:
|
|
70
|
-
if self.project_root is None:
|
|
71
|
-
self.project_root = self.path
|
|
72
|
-
|
|
73
100
|
@property
|
|
74
101
|
def _subprocess_timeout(self) -> int | None:
|
|
75
102
|
"""Subprocess timeout from ``config.env``.
|
|
@@ -95,7 +122,6 @@ class ScmWorkdir:
|
|
|
95
122
|
@property
|
|
96
123
|
def project_path(self) -> str:
|
|
97
124
|
"""Discovered relative path from VCS root to project directory."""
|
|
98
|
-
assert self.project_root is not None
|
|
99
125
|
if self.path == self.project_root:
|
|
100
126
|
return ""
|
|
101
127
|
from .._paths import relative_project_path
|
|
@@ -4,7 +4,11 @@ import json
|
|
|
4
4
|
import os
|
|
5
5
|
import sys
|
|
6
6
|
from collections.abc import Iterable
|
|
7
|
-
|
|
7
|
+
|
|
8
|
+
if sys.version_info >= (3, 9):
|
|
9
|
+
from importlib.resources import files
|
|
10
|
+
else:
|
|
11
|
+
from importlib.resources import read_text as _read_text
|
|
8
12
|
from pathlib import Path
|
|
9
13
|
from typing import TypedDict
|
|
10
14
|
|
|
@@ -182,7 +186,10 @@ def _create_archival_file(opts: CliNamespace, config: Configuration) -> int:
|
|
|
182
186
|
assert opts.archival_template is not None
|
|
183
187
|
|
|
184
188
|
# Load template content from package resources
|
|
185
|
-
|
|
189
|
+
if sys.version_info >= (3, 9):
|
|
190
|
+
content = files(__package__).joinpath(opts.archival_template).read_text("utf-8")
|
|
191
|
+
else:
|
|
192
|
+
content = _read_text(__package__, opts.archival_template, encoding="utf-8")
|
|
186
193
|
|
|
187
194
|
# Print appropriate message based on template
|
|
188
195
|
if opts.archival_template == "git_archival_stable.txt":
|
|
@@ -3,10 +3,38 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import os
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
import sys
|
|
7
|
+
from importlib.metadata import entry_points as _stdlib_entry_points
|
|
8
|
+
from typing import TYPE_CHECKING, Union
|
|
9
|
+
|
|
10
|
+
if sys.version_info >= (3, 10):
|
|
11
|
+
from typing import TypeAlias
|
|
12
|
+
else:
|
|
13
|
+
from typing_extensions import TypeAlias
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from importlib.metadata import EntryPoint
|
|
17
|
+
|
|
18
|
+
PathT: TypeAlias = os.PathLike[str] | str
|
|
19
|
+
else:
|
|
20
|
+
PathT: TypeAlias = Union[os.PathLike, str]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
if sys.version_info >= (3, 10):
|
|
24
|
+
from importlib.metadata import entry_points as entry_points
|
|
25
|
+
else:
|
|
26
|
+
|
|
27
|
+
def entry_points(
|
|
28
|
+
**params: str,
|
|
29
|
+
) -> list[EntryPoint]:
|
|
30
|
+
"""Backport of entry_points(group=...) for Python 3.8/3.9."""
|
|
31
|
+
groups = _stdlib_entry_points()
|
|
32
|
+
group = params.get("group", "")
|
|
33
|
+
name = params.get("name")
|
|
34
|
+
eps = list(groups.get(group, [])) # type: ignore[call-overload]
|
|
35
|
+
if name is not None:
|
|
36
|
+
eps = [ep for ep in eps if ep.name == name]
|
|
37
|
+
return eps
|
|
10
38
|
|
|
11
39
|
|
|
12
40
|
def normalize_path_for_assertion(path: str) -> str:
|
|
@@ -3,9 +3,10 @@ from __future__ import annotations
|
|
|
3
3
|
import logging
|
|
4
4
|
from collections.abc import Callable, Iterator
|
|
5
5
|
from importlib import metadata as im
|
|
6
|
-
from importlib.metadata import entry_points
|
|
7
6
|
from typing import TYPE_CHECKING, Any, cast
|
|
8
7
|
|
|
8
|
+
from ._compat import entry_points
|
|
9
|
+
|
|
9
10
|
__all__ = [
|
|
10
11
|
"entry_points",
|
|
11
12
|
"im",
|
|
@@ -65,7 +66,7 @@ def _iter_version_schemes(
|
|
|
65
66
|
or _get_from_object_reference_str(scheme_value, entrypoint),
|
|
66
67
|
)
|
|
67
68
|
|
|
68
|
-
if isinstance(scheme_value, list
|
|
69
|
+
if isinstance(scheme_value, (list, tuple)):
|
|
69
70
|
for variant in scheme_value:
|
|
70
71
|
if variant not in _memo:
|
|
71
72
|
_memo.add(variant)
|
|
@@ -34,6 +34,11 @@ class FallbackWorkdir:
|
|
|
34
34
|
_config: Configuration | None = dc_field(default=None, repr=False, compare=False)
|
|
35
35
|
"""Back-reference to the ``Configuration`` that discovered this workdir."""
|
|
36
36
|
|
|
37
|
+
@property
|
|
38
|
+
def project_root(self) -> Path:
|
|
39
|
+
"""Fallback workdirs always live at the project root."""
|
|
40
|
+
return self.path
|
|
41
|
+
|
|
37
42
|
@property
|
|
38
43
|
def config(self) -> Configuration:
|
|
39
44
|
if self._config is None:
|
|
@@ -76,8 +81,20 @@ class MetadataWorkdir(FallbackWorkdir):
|
|
|
76
81
|
data.node_date,
|
|
77
82
|
self.metadata_dir,
|
|
78
83
|
)
|
|
84
|
+
# The tag in scm_version.json is already a parsed version string
|
|
85
|
+
# (e.g. "1.5.5"), not a raw VCS tag (e.g. "cuda-pathfinder-v1.5.5").
|
|
86
|
+
# Convert to version_cls so _parse_tag skips tag_regex matching.
|
|
87
|
+
try:
|
|
88
|
+
tag = self.config.version_cls(data.tag)
|
|
89
|
+
except Exception:
|
|
90
|
+
log.warning(
|
|
91
|
+
"cannot parse stored tag %r in metadata at %s",
|
|
92
|
+
data.tag,
|
|
93
|
+
self.metadata_dir,
|
|
94
|
+
)
|
|
95
|
+
return None
|
|
79
96
|
return meta(
|
|
80
|
-
tag=
|
|
97
|
+
tag=tag,
|
|
81
98
|
distance=data.distance,
|
|
82
99
|
node=data.node,
|
|
83
100
|
dirty=data.dirty,
|
|
@@ -2,8 +2,13 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
4
|
import os
|
|
5
|
+
import sys
|
|
5
6
|
from collections.abc import Callable, Mapping
|
|
6
|
-
|
|
7
|
+
|
|
8
|
+
if sys.version_info >= (3, 10):
|
|
9
|
+
from typing import TypeGuard
|
|
10
|
+
else:
|
|
11
|
+
from typing_extensions import TypeGuard
|
|
7
12
|
|
|
8
13
|
from .. import _types as _t
|
|
9
14
|
from .._compat import norm_real
|
|
@@ -85,6 +85,9 @@ 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
|
+
toplevel = norm_real(toplevel)
|
|
88
91
|
git_files: set[str] = set()
|
|
89
92
|
git_dirs: set[str] = {toplevel}
|
|
90
93
|
for name in res.stdout.rstrip("\0").split("\0"):
|
|
@@ -90,7 +90,7 @@ def _resolve_version(config: Configuration) -> ScmVersion | None:
|
|
|
90
90
|
return None
|
|
91
91
|
|
|
92
92
|
|
|
93
|
-
def _warn_if_tracked(target: Path, root: Path) -> None:
|
|
93
|
+
def _warn_if_tracked(target: Path, root: Path, config: Configuration) -> None:
|
|
94
94
|
"""Warn when *target* is tracked in version control (#468).
|
|
95
95
|
|
|
96
96
|
Writing a version file that is tracked makes ``git describe --dirty``
|
|
@@ -101,7 +101,7 @@ def _warn_if_tracked(target: Path, root: Path) -> None:
|
|
|
101
101
|
|
|
102
102
|
for workdir_cls in (GitWorkdir, HgWorkdir):
|
|
103
103
|
try:
|
|
104
|
-
wd = workdir_cls.from_potential_worktree(root)
|
|
104
|
+
wd = workdir_cls.from_potential_worktree(root, config)
|
|
105
105
|
except Exception:
|
|
106
106
|
continue
|
|
107
107
|
if wd is not None and wd.is_file_tracked(target):
|
|
@@ -125,7 +125,7 @@ def write_version_files(
|
|
|
125
125
|
|
|
126
126
|
write_to = Path(config.write_to)
|
|
127
127
|
target = root / write_to if not write_to.is_absolute() else write_to
|
|
128
|
-
_warn_if_tracked(target, root)
|
|
128
|
+
_warn_if_tracked(target, root, config)
|
|
129
129
|
|
|
130
130
|
dump_version(
|
|
131
131
|
root=config.root,
|
|
@@ -142,7 +142,7 @@ def write_version_files(
|
|
|
142
142
|
# todo: use a better name than fallback root
|
|
143
143
|
assert config.relative_to is not None
|
|
144
144
|
target = Path(config.relative_to).parent.joinpath(version_file)
|
|
145
|
-
_warn_if_tracked(target, root)
|
|
145
|
+
_warn_if_tracked(target, root, config)
|
|
146
146
|
|
|
147
147
|
write_version_to_path(
|
|
148
148
|
target,
|
|
@@ -13,8 +13,6 @@ from __future__ import annotations
|
|
|
13
13
|
|
|
14
14
|
import logging
|
|
15
15
|
import warnings
|
|
16
|
-
from dataclasses import dataclass
|
|
17
|
-
from dataclasses import field as dc_field
|
|
18
16
|
from pathlib import Path
|
|
19
17
|
from typing import TYPE_CHECKING
|
|
20
18
|
|
|
@@ -29,7 +27,6 @@ if TYPE_CHECKING:
|
|
|
29
27
|
log = logging.getLogger(__name__)
|
|
30
28
|
|
|
31
29
|
|
|
32
|
-
@dataclass()
|
|
33
30
|
class LegacyParseWorkdir(ScmWorkdir):
|
|
34
31
|
"""Wraps a legacy ``config.parse`` callable as a workdir.
|
|
35
32
|
|
|
@@ -38,12 +35,17 @@ class LegacyParseWorkdir(ScmWorkdir):
|
|
|
38
35
|
entry-point group.
|
|
39
36
|
"""
|
|
40
37
|
|
|
41
|
-
|
|
38
|
+
parse_fn: ParseFunction
|
|
42
39
|
|
|
43
|
-
def
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
40
|
+
def __init__(
|
|
41
|
+
self,
|
|
42
|
+
path: Path,
|
|
43
|
+
*,
|
|
44
|
+
parse_fn: ParseFunction,
|
|
45
|
+
_config: Configuration | None = None,
|
|
46
|
+
) -> None:
|
|
47
|
+
super().__init__(path=path, _config=_config)
|
|
48
|
+
self.parse_fn = parse_fn
|
|
47
49
|
|
|
48
50
|
def get_scm_version(self) -> ScmVersion | None:
|
|
49
51
|
warnings.warn(
|
|
@@ -53,8 +55,7 @@ class LegacyParseWorkdir(ScmWorkdir):
|
|
|
53
55
|
DeprecationWarning,
|
|
54
56
|
stacklevel=2,
|
|
55
57
|
)
|
|
56
|
-
|
|
57
|
-
result = self._parse_fn(self.config.absolute_root, config=self.config)
|
|
58
|
+
result = self.parse_fn(self.config.absolute_root, config=self.config)
|
|
58
59
|
if result is not None and not isinstance(result, ScmVersion):
|
|
59
60
|
raise TypeError(
|
|
60
61
|
f"version parse result was {result!r}\n"
|
|
@@ -207,15 +207,25 @@ def _apply_metadata_overrides(
|
|
|
207
207
|
|
|
208
208
|
log.info("Applying metadata overrides: %s", metadata_overrides)
|
|
209
209
|
|
|
210
|
-
# Get
|
|
211
|
-
|
|
210
|
+
# Get valid field names from PretendMetadataDict for validation.
|
|
211
|
+
# Use __annotations__ keys directly instead of get_type_hints() to avoid
|
|
212
|
+
# evaluating PEP 604 forward references on Python <3.10.
|
|
213
|
+
valid_fields = frozenset(PretendMetadataDict.__annotations__)
|
|
214
|
+
|
|
215
|
+
# Try to resolve actual types for runtime validation (best-effort)
|
|
216
|
+
try:
|
|
217
|
+
field_types: dict[str, Any] | None = get_type_hints(PretendMetadataDict)
|
|
218
|
+
except TypeError:
|
|
219
|
+
field_types = None
|
|
212
220
|
|
|
213
221
|
# Apply each override individually using dataclasses.replace
|
|
214
222
|
result = scm_version
|
|
215
223
|
|
|
216
224
|
for field, value in metadata_overrides.items():
|
|
217
|
-
|
|
218
|
-
|
|
225
|
+
if field not in valid_fields:
|
|
226
|
+
continue
|
|
227
|
+
# Runtime type validation (only when type hints are resolvable)
|
|
228
|
+
if field_types is not None and field in field_types:
|
|
219
229
|
expected_type = field_types[field]
|
|
220
230
|
# Handle Optional/Union types (e.g., str | None)
|
|
221
231
|
if hasattr(expected_type, "__args__"):
|
|
@@ -32,6 +32,16 @@ class WorkdirProtocol(Protocol):
|
|
|
32
32
|
@property
|
|
33
33
|
def path(self) -> Path: ...
|
|
34
34
|
|
|
35
|
+
@property
|
|
36
|
+
def project_root(self) -> Path:
|
|
37
|
+
"""The project directory (where pyproject.toml lives).
|
|
38
|
+
|
|
39
|
+
For SCM workdirs this may differ from ``path`` (the VCS root).
|
|
40
|
+
For fallback workdirs this is always equal to ``path``.
|
|
41
|
+
Guaranteed non-None after construction.
|
|
42
|
+
"""
|
|
43
|
+
...
|
|
44
|
+
|
|
35
45
|
def get_scm_version(self) -> ScmVersion | None:
|
|
36
46
|
"""Raw version metadata before overrides / formatting."""
|
|
37
47
|
...
|
|
@@ -49,9 +59,6 @@ class WorkdirProtocol(Protocol):
|
|
|
49
59
|
class ScmWorkdirProtocol(WorkdirProtocol, Protocol):
|
|
50
60
|
"""Live VCS checkout: adds monorepo / nested-project fields."""
|
|
51
61
|
|
|
52
|
-
@property
|
|
53
|
-
def project_root(self) -> Path: ...
|
|
54
|
-
|
|
55
62
|
@property
|
|
56
63
|
def project_path(self) -> str:
|
|
57
64
|
"""POSIX relative path from VCS root to project_root (empty when equal)."""
|
|
@@ -9,7 +9,11 @@ import warnings
|
|
|
9
9
|
from collections.abc import Sequence
|
|
10
10
|
from dataclasses import dataclass
|
|
11
11
|
from pathlib import Path
|
|
12
|
-
|
|
12
|
+
|
|
13
|
+
if sys.version_info >= (3, 10):
|
|
14
|
+
from typing import TypeAlias
|
|
15
|
+
else:
|
|
16
|
+
from typing_extensions import TypeAlias
|
|
13
17
|
|
|
14
18
|
if sys.version_info >= (3, 11):
|
|
15
19
|
from typing import Self
|
|
@@ -158,7 +162,7 @@ class PyProjectData:
|
|
|
158
162
|
|
|
159
163
|
# Testing injection type for configuration reading
|
|
160
164
|
GivenPyProjectResult: TypeAlias = (
|
|
161
|
-
PyProjectData | InvalidTomlError | FileNotFoundError | None
|
|
165
|
+
"PyProjectData | InvalidTomlError | FileNotFoundError | None"
|
|
162
166
|
)
|
|
163
167
|
|
|
164
168
|
|
|
@@ -201,7 +205,7 @@ def read_pyproject(
|
|
|
201
205
|
if _given_result is not None:
|
|
202
206
|
if isinstance(_given_result, PyProjectData):
|
|
203
207
|
return _given_result
|
|
204
|
-
if isinstance(_given_result, InvalidTomlError
|
|
208
|
+
if isinstance(_given_result, (InvalidTomlError, FileNotFoundError)):
|
|
205
209
|
raise _given_result
|
|
206
210
|
|
|
207
211
|
if _given_definition is not None:
|
|
@@ -4,6 +4,7 @@ import logging
|
|
|
4
4
|
import os
|
|
5
5
|
import shlex
|
|
6
6
|
import subprocess
|
|
7
|
+
import sys
|
|
7
8
|
import textwrap
|
|
8
9
|
import warnings
|
|
9
10
|
from collections.abc import Callable, Mapping, Sequence
|
|
@@ -35,8 +36,13 @@ def _get_timeout(env: Mapping[str, str]) -> int:
|
|
|
35
36
|
PARSE_RESULT = TypeVar("PARSE_RESULT")
|
|
36
37
|
T = TypeVar("T")
|
|
37
38
|
|
|
39
|
+
if sys.version_info >= (3, 9):
|
|
40
|
+
_CompletedProcessBase = subprocess.CompletedProcess[str]
|
|
41
|
+
else:
|
|
42
|
+
_CompletedProcessBase = subprocess.CompletedProcess
|
|
38
43
|
|
|
39
|
-
|
|
44
|
+
|
|
45
|
+
class CompletedProcess(_CompletedProcessBase):
|
|
40
46
|
@classmethod
|
|
41
47
|
def from_raw(
|
|
42
48
|
cls, input: subprocess.CompletedProcess[str], strip: bool = True
|
|
@@ -99,13 +99,14 @@ def scm_version_data_from_scm_version(
|
|
|
99
99
|
scm_version: ScmVersion | VersionFields,
|
|
100
100
|
) -> ScmVersionData:
|
|
101
101
|
"""Build ``ScmVersionData`` from a live ``ScmVersion`` object."""
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
102
|
+
node_date = scm_version.node_date
|
|
103
|
+
node_date_str: str | None
|
|
104
|
+
if isinstance(node_date, date):
|
|
105
|
+
node_date_str = node_date.isoformat()
|
|
106
|
+
elif isinstance(node_date, str):
|
|
107
|
+
node_date_str = node_date
|
|
108
|
+
else:
|
|
109
|
+
node_date_str = None
|
|
109
110
|
return ScmVersionData(
|
|
110
111
|
tag=str(scm_version.tag),
|
|
111
112
|
distance=scm_version.distance,
|
|
@@ -9,10 +9,16 @@ from __future__ import annotations
|
|
|
9
9
|
|
|
10
10
|
import dataclasses
|
|
11
11
|
import logging
|
|
12
|
+
import sys
|
|
12
13
|
import warnings
|
|
13
14
|
from collections.abc import Callable
|
|
14
15
|
from datetime import date, datetime
|
|
15
|
-
from typing import TYPE_CHECKING, Any,
|
|
16
|
+
from typing import TYPE_CHECKING, Any, TypedDict
|
|
17
|
+
|
|
18
|
+
if sys.version_info >= (3, 10):
|
|
19
|
+
from typing import Concatenate, ParamSpec
|
|
20
|
+
else:
|
|
21
|
+
from typing_extensions import Concatenate, ParamSpec
|
|
16
22
|
|
|
17
23
|
from . import _config
|
|
18
24
|
from . import _version_cls as _v
|
|
@@ -20,8 +26,6 @@ from ._node_utils import _format_node_for_output
|
|
|
20
26
|
from ._version_cls import _Version
|
|
21
27
|
|
|
22
28
|
if TYPE_CHECKING:
|
|
23
|
-
import sys
|
|
24
|
-
|
|
25
29
|
if sys.version_info >= (3, 11):
|
|
26
30
|
from typing import Unpack
|
|
27
31
|
else:
|
|
@@ -347,20 +351,14 @@ class ScmVersion:
|
|
|
347
351
|
|
|
348
352
|
def _parse_tag(
|
|
349
353
|
tag: _Version | str, preformatted: bool, config: _config.Configuration
|
|
350
|
-
) -> _Version:
|
|
354
|
+
) -> _Version | None:
|
|
351
355
|
if preformatted:
|
|
352
|
-
# For preformatted versions, tag should already be validated as a version object
|
|
353
|
-
# String validation is handled in meta function before calling this
|
|
354
356
|
if isinstance(tag, str):
|
|
355
|
-
# This should not happen with enhanced meta, but kept for safety
|
|
356
357
|
return _v.NonNormalizedVersion(tag)
|
|
357
358
|
else:
|
|
358
|
-
# Already a version object (including test mocks), return as-is
|
|
359
359
|
return tag
|
|
360
360
|
elif not isinstance(tag, config.version_cls):
|
|
361
|
-
|
|
362
|
-
assert version is not None
|
|
363
|
-
return version
|
|
361
|
+
return tag_to_version(tag, config)
|
|
364
362
|
else:
|
|
365
363
|
return tag
|
|
366
364
|
|
|
@@ -389,18 +387,20 @@ def meta(
|
|
|
389
387
|
node_date: date | None = None,
|
|
390
388
|
time: datetime | None = None,
|
|
391
389
|
) -> ScmVersion:
|
|
392
|
-
parsed_version: _Version
|
|
393
|
-
# Enhanced string validation for preformatted versions
|
|
390
|
+
parsed_version: _Version | None
|
|
394
391
|
if preformatted and isinstance(tag, str):
|
|
395
|
-
# Validate PEP 440 compliance using NonNormalizedVersion
|
|
396
|
-
# Let validation errors bubble up to the caller
|
|
397
392
|
parsed_version = _v.NonNormalizedVersion(tag)
|
|
398
393
|
else:
|
|
399
|
-
# Use existing _parse_tag logic for non-preformatted or already validated inputs
|
|
400
394
|
parsed_version = _parse_tag(tag, preformatted, config)
|
|
401
395
|
|
|
396
|
+
if parsed_version is None:
|
|
397
|
+
raise ValueError(
|
|
398
|
+
f"Can't parse version from tag {tag!r}"
|
|
399
|
+
f" (tag_regex={config.tag.regex.pattern!r},"
|
|
400
|
+
f" tag_prefix={config.tag.prefix!r})"
|
|
401
|
+
)
|
|
402
|
+
|
|
402
403
|
log.info("version %s -> %s", tag, parsed_version)
|
|
403
|
-
assert parsed_version is not None, f"Can't parse version {tag}"
|
|
404
404
|
|
|
405
405
|
kwargs: _ScmVersionKwargs = {
|
|
406
406
|
"distance": distance,
|