vcs-versioning 2.0.1__tar.gz → 2.1.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.
Files changed (97) hide show
  1. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/CHANGELOG.md +12 -0
  2. {vcs_versioning-2.0.1/src/vcs_versioning.egg-info → vcs_versioning-2.1.0}/PKG-INFO +1 -1
  3. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning/__init__.py +9 -3
  4. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning/_config.py +19 -18
  5. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning/_environment.py +82 -3
  6. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning/_get_version_impl.py +12 -0
  7. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning/_integrator_helpers.py +11 -5
  8. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning/_overrides.py +18 -18
  9. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning/_run_cmd.py +1 -0
  10. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning/_version_inference.py +9 -2
  11. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning/_worktree_discovery.py +20 -8
  12. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0/src/vcs_versioning.egg-info}/PKG-INFO +1 -1
  13. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/testing_vcs/test_chain_api.py +103 -0
  14. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/testing_vcs/test_regressions.py +24 -0
  15. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/testing_vcs/test_tag_config.py +21 -30
  16. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/testing_vcs/test_workdir_discovery.py +34 -0
  17. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/LICENSE.txt +0 -0
  18. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/MANIFEST.in +0 -0
  19. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/README.md +0 -0
  20. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/pyproject.toml +0 -0
  21. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/setup.cfg +0 -0
  22. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/setup.py +0 -0
  23. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning/__main__.py +0 -0
  24. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning/_backends/__init__.py +0 -0
  25. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning/_backends/_discover_vcs.py +0 -0
  26. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning/_backends/_git.py +0 -0
  27. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning/_backends/_hg.py +0 -0
  28. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning/_backends/_hg_git.py +0 -0
  29. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning/_backends/_jj.py +0 -0
  30. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning/_backends/_scm_workdir.py +0 -0
  31. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning/_cli/__init__.py +0 -0
  32. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning/_cli/_args.py +0 -0
  33. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning/_cli/git_archival_full.txt +0 -0
  34. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning/_cli/git_archival_stable.txt +0 -0
  35. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning/_compat.py +0 -0
  36. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning/_discover.py +0 -0
  37. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning/_dump_version.py +0 -0
  38. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning/_entrypoints.py +0 -0
  39. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning/_exceptions.py +0 -0
  40. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning/_fallback_workdir.py +0 -0
  41. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning/_fallbacks.py +0 -0
  42. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning/_file_finders/__init__.py +0 -0
  43. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning/_file_finders/_git.py +0 -0
  44. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning/_file_finders/_hg.py +0 -0
  45. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning/_file_finders/_jj.py +0 -0
  46. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning/_integration.py +0 -0
  47. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning/_legacy_parse.py +0 -0
  48. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning/_log.py +0 -0
  49. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning/_modify_version.py +0 -0
  50. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning/_node_utils.py +0 -0
  51. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning/_paths.py +0 -0
  52. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning/_project_overrides.py +0 -0
  53. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning/_protocols.py +0 -0
  54. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning/_pyproject_reading.py +0 -0
  55. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning/_requirement_cls.py +0 -0
  56. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning/_scm_metadata.py +0 -0
  57. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning/_scm_version.py +0 -0
  58. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning/_test_utils.py +0 -0
  59. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning/_toml.py +0 -0
  60. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning/_types.py +0 -0
  61. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning/_version_cls.py +0 -0
  62. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning/_version_fields.py +0 -0
  63. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning/_version_schemes/__init__.py +0 -0
  64. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning/_version_schemes/_common.py +0 -0
  65. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning/_version_schemes/_standard.py +0 -0
  66. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning/_version_schemes/_towncrier.py +0 -0
  67. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning/overrides.py +0 -0
  68. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning/py.typed +0 -0
  69. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning/test_api.py +0 -0
  70. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning.egg-info/SOURCES.txt +0 -0
  71. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning.egg-info/dependency_links.txt +0 -0
  72. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning.egg-info/entry_points.txt +0 -0
  73. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning.egg-info/requires.txt +0 -0
  74. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/src/vcs_versioning.egg-info/top_level.txt +0 -0
  75. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/testing_vcs/__init__.py +0 -0
  76. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/testing_vcs/conftest.py +0 -0
  77. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/testing_vcs/test_better_root_errors.py +0 -0
  78. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/testing_vcs/test_compat.py +0 -0
  79. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/testing_vcs/test_config.py +0 -0
  80. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/testing_vcs/test_expect_parse.py +0 -0
  81. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/testing_vcs/test_file_finders.py +0 -0
  82. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/testing_vcs/test_git.py +0 -0
  83. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/testing_vcs/test_hg_git.py +0 -0
  84. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/testing_vcs/test_integrator_helpers.py +0 -0
  85. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/testing_vcs/test_internal_log_level.py +0 -0
  86. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/testing_vcs/test_jj.py +0 -0
  87. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/testing_vcs/test_legacy_parse.py +0 -0
  88. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/testing_vcs/test_mercurial.py +0 -0
  89. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/testing_vcs/test_overrides_api.py +0 -0
  90. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/testing_vcs/test_overrides_env_reader.py +0 -0
  91. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/testing_vcs/test_project_overrides.py +0 -0
  92. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/testing_vcs/test_project_path.py +0 -0
  93. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/testing_vcs/test_scm_metadata.py +0 -0
  94. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/testing_vcs/test_version.py +0 -0
  95. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/testing_vcs/test_version_scheme_towncrier.py +0 -0
  96. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/testing_vcs/test_version_schemes.py +0 -0
  97. {vcs_versioning-2.0.1 → vcs_versioning-2.1.0}/testing_vcs/test_workdir_api.py +0 -0
@@ -2,6 +2,18 @@
2
2
 
3
3
  <!-- towncrier release notes start -->
4
4
 
5
+ ## 2.1.0 (2026-06-22)
6
+
7
+ ### Added
8
+
9
+ - Add `VcsEnvironment.build_config_from_pyproject`, `build_config_from_data`, and `pyproject_tool_names` methods for canonical env-first configuration creation. ([#1424](https://github.com/pypa/setuptools-scm/issues/1424))
10
+
11
+
12
+ ### Fixed
13
+
14
+ - Fix DeprecationWarning leak in pretend API by ensuring all public APIs attach VcsEnvironment to Configuration before accessing env-dependent properties. ([#1424](https://github.com/pypa/setuptools-scm/issues/1424))
15
+ - Fix fallback discovery so an unprocessed `.git_archival.txt` no longer shadows a valid `PKG-INFO` in PyPI sdists. ([#1431](https://github.com/pypa/setuptools-scm/issues/1431))
16
+
5
17
  ## 2.0.1 (2026-06-22)
6
18
 
7
19
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vcs-versioning
3
- Version: 2.0.1
3
+ Version: 2.1.0
4
4
  Summary: the blessed package to manage your versions by vcs metadata
5
5
  Author-email: Ronny Pfannschmidt <opensource@ronnypfannschmidt.de>
6
6
  License-Expression: MIT
@@ -25,6 +25,7 @@ def build_configuration_from_pyproject(
25
25
  pyproject_data: PyProjectData,
26
26
  *,
27
27
  dist_name: str | None = None,
28
+ env: VcsEnvironment | None = None,
28
29
  **integrator_overrides: Any,
29
30
  ) -> Configuration:
30
31
  """Build Configuration from PyProjectData with full workflow.
@@ -36,7 +37,7 @@ def build_configuration_from_pyproject(
36
37
  2. Determine dist_name (argument > pyproject.project_name)
37
38
  3. Apply integrator overrides (override config file)
38
39
  4. Apply environment TOML overrides (highest priority)
39
- 5. Create and validate Configuration instance
40
+ 5. Create and validate Configuration instance with VcsEnvironment attached
40
41
 
41
42
  Integrators create PyProjectData themselves:
42
43
 
@@ -73,6 +74,8 @@ def build_configuration_from_pyproject(
73
74
  Args:
74
75
  pyproject_data: Parsed pyproject data (integrator creates this)
75
76
  dist_name: Distribution name (overrides pyproject_data.project_name)
77
+ env: Optional VcsEnvironment. If None, resolves from the active
78
+ GlobalOverrides context or process environment.
76
79
  **integrator_overrides: Integrator-provided config overrides
77
80
  (override config file, but overridden by env)
78
81
 
@@ -88,9 +91,12 @@ def build_configuration_from_pyproject(
88
91
  This allows integrators to provide their own transformations
89
92
  while still respecting user environment variable overrides.
90
93
  """
91
- from ._integrator_helpers import build_configuration_from_pyproject_internal
94
+ from ._environment import resolve_runtime_env
92
95
 
93
- return build_configuration_from_pyproject_internal(
96
+ if env is None:
97
+ env = resolve_runtime_env()
98
+
99
+ return env.build_config_from_pyproject(
94
100
  pyproject_data=pyproject_data,
95
101
  dist_name=dist_name,
96
102
  **integrator_overrides,
@@ -347,27 +347,19 @@ class Configuration:
347
347
  self.tag, regex=_check_tag_regex(tag_regex)
348
348
  )
349
349
 
350
+ # TODO(#1429): re-introduce these warnings with non-conflicting logic
350
351
  if self.tag.strict is None:
351
- warnings.warn(
352
- "tag.strict is not set. Currently defaults to False (permissive "
353
- "tag matching). In a future major version the default will change "
354
- "to True (require tags to contain a dot). "
355
- "Set tag.strict = true or tag.strict = false explicitly in your "
356
- "[tool.setuptools_scm] / [tool.vcs-versioning] config to silence "
357
- "this warning.",
358
- FutureWarning,
359
- stacklevel=2,
352
+ log.debug(
353
+ "tag.strict is not set defaults to False (permissive tag matching)"
360
354
  )
361
355
 
362
356
  if (
363
357
  self.tag.prefix or self.tag.strict is not None
364
358
  ) and self.scm.git.describe_command is not None:
365
- warnings.warn(
359
+ log.debug(
366
360
  "Both tag.prefix/tag.strict and scm.git.describe_command are set. "
367
361
  "The explicit describe_command takes precedence; tag.prefix and "
368
- "tag.strict will have no effect on the git describe match pattern.",
369
- UserWarning,
370
- stacklevel=2,
362
+ "tag.strict will have no effect on the git describe match pattern."
371
363
  )
372
364
 
373
365
  self._resolved_paths = resolve_paths(
@@ -450,6 +442,7 @@ class Configuration:
450
442
  *,
451
443
  tool_names: tuple[str, ...] | None = None,
452
444
  env: Mapping[str, str] | None = None,
445
+ _env: VcsEnvironment | None = None,
453
446
  **kwargs: Any,
454
447
  ) -> Configuration:
455
448
  """
@@ -462,6 +455,7 @@ class Configuration:
462
455
  - dist_name: name of the distribution
463
456
  - tool_names: env-var prefix order for TOML overrides
464
457
  - env: environment mapping for TOML overrides (default: os.environ)
458
+ - _env: VcsEnvironment to attach to the resulting Configuration
465
459
  - **kwargs: additional keyword arguments to pass to the Configuration constructor
466
460
  """
467
461
 
@@ -485,14 +479,20 @@ class Configuration:
485
479
  args.update(project_overrides)
486
480
 
487
481
  # Env overrides: highest priority
488
- args.update(
489
- read_toml_overrides(args["dist_name"], tool_names=tool_names, env=env)
490
- )
491
- return cls.from_data(relative_to=relative_to, data=args)
482
+ if _env is not None:
483
+ args.update(_env.read_toml_overrides(args["dist_name"]))
484
+ else:
485
+ args.update(
486
+ read_toml_overrides(args["dist_name"], tool_names=tool_names, env=env)
487
+ )
488
+ return cls.from_data(relative_to=relative_to, data=args, _env=_env)
492
489
 
493
490
  @classmethod
494
491
  def from_data(
495
- cls, relative_to: str | os.PathLike[str], data: dict[str, Any]
492
+ cls,
493
+ relative_to: str | os.PathLike[str],
494
+ data: dict[str, Any],
495
+ _env: VcsEnvironment | None = None,
496
496
  ) -> Configuration:
497
497
  """
498
498
  given configuration data
@@ -526,6 +526,7 @@ class Configuration:
526
526
  version_cls=version_cls,
527
527
  tag=tag_config,
528
528
  scm=scm_config,
529
+ _env=_env,
529
530
  **data,
530
531
  )
531
532
 
@@ -22,7 +22,7 @@ from typing import TYPE_CHECKING, Any, Literal
22
22
  if TYPE_CHECKING:
23
23
  from pytest import MonkeyPatch
24
24
 
25
- from . import _config, overrides
25
+ from . import _config, _overrides, overrides
26
26
 
27
27
  log = logging.getLogger(__name__)
28
28
 
@@ -248,7 +248,86 @@ class VcsEnvironment:
248
248
  from ._config import Configuration
249
249
 
250
250
  config = Configuration.from_file(
251
- tool_names=self.tool_names, env=self._env, **kwargs
251
+ tool_names=self.tool_names, env=self._env, _env=self, **kwargs
252
252
  )
253
- object.__setattr__(config, "_env", self)
254
253
  return config
254
+
255
+ def build_config_from_data(
256
+ self,
257
+ relative_to: str | os.PathLike[str],
258
+ data: dict[str, Any],
259
+ ) -> _config.Configuration:
260
+ """Create a ``Configuration`` from pre-assembled data dict.
261
+
262
+ Use this when you have already extracted and merged configuration
263
+ data (e.g. from pyproject section + overrides) and want to build
264
+ a validated Configuration without re-reading files.
265
+ """
266
+ from ._config import Configuration
267
+
268
+ return Configuration.from_data(relative_to=relative_to, data=data, _env=self)
269
+
270
+ def build_config_from_pyproject(
271
+ self,
272
+ pyproject_data: Any,
273
+ *,
274
+ dist_name: str | None = None,
275
+ **integrator_overrides: Any,
276
+ ) -> _config.Configuration:
277
+ """Create a ``Configuration`` from PyProjectData with full workflow.
278
+
279
+ Canonical entry point for integrators. Orchestrates:
280
+ 1. Extract config from pyproject_data.section
281
+ 2. Determine dist_name
282
+ 3. Apply integrator overrides
283
+ 4. Apply environment TOML overrides
284
+ 5. Build and validate Configuration with this env attached
285
+ """
286
+ from ._integrator_helpers import build_configuration_from_pyproject_internal
287
+
288
+ return build_configuration_from_pyproject_internal(
289
+ pyproject_data=pyproject_data,
290
+ dist_name=dist_name,
291
+ env=self,
292
+ **integrator_overrides,
293
+ )
294
+
295
+ def pyproject_tool_names(self) -> list[str]:
296
+ """Derive TOML section names from env-var prefixes.
297
+
298
+ Maps env-var prefixes to their canonical pyproject [tool.X] section
299
+ names. The ``VCS_VERSIONING`` prefix always maps to ``vcs-versioning``
300
+ (with dash). Other prefixes are lowercased with underscores preserved.
301
+
302
+ Examples:
303
+ - ``SETUPTOOLS_SCM`` -> ``setuptools_scm``
304
+ - ``VCS_VERSIONING`` -> ``vcs-versioning``
305
+ - ``HATCH_VCS`` -> ``hatch_vcs``
306
+
307
+ .. todo::
308
+ This uses special-case mapping (VCS_VERSIONING -> vcs-versioning).
309
+ The tool names should be made properly configurable via an explicit
310
+ mapping parameter on VcsEnvironment rather than guessing from
311
+ env-var prefix casing conventions.
312
+ """
313
+ result: list[str] = []
314
+ for name in self.tool_names:
315
+ if name == "VCS_VERSIONING":
316
+ result.append("vcs-versioning")
317
+ else:
318
+ result.append(name.lower())
319
+ return result
320
+
321
+ def read_toml_overrides(
322
+ self, dist_name: str | None
323
+ ) -> _overrides.ConfigOverridesDict:
324
+ """Read TOML config overrides from environment variables.
325
+
326
+ Uses this environment's tool_names and env dict, delegating to
327
+ the standalone ``read_toml_overrides`` function.
328
+ """
329
+ from ._overrides import read_toml_overrides as _read_toml_overrides
330
+
331
+ return _read_toml_overrides(
332
+ dist_name, tool_names=self.tool_names, env=self._env
333
+ )
@@ -29,6 +29,18 @@ EMPTY_TAG_REGEX_DEPRECATION = DeprecationWarning(
29
29
  log = logging.getLogger(__name__)
30
30
 
31
31
 
32
+ def parse_version(config: Configuration) -> ScmVersion | None:
33
+ """Backward-compat shim for setuptools-scm <=10.0.x.
34
+
35
+ Those releases import ``parse_version`` from this module. The function
36
+ was inlined during the 10.1 / vcs-versioning 2.0 refactor, but we keep
37
+ the name importable so that older setuptools-scm pins still work with
38
+ newer vcs-versioning releases.
39
+ """
40
+ scm_version = _resolve_version(config)
41
+ return _apply_metadata_overrides(scm_version, config)
42
+
43
+
32
44
  def _finalize(
33
45
  scm_version: ScmVersion,
34
46
  config: Configuration,
@@ -13,6 +13,7 @@ from typing import TYPE_CHECKING, Any
13
13
 
14
14
  if TYPE_CHECKING:
15
15
  from ._config import Configuration
16
+ from ._environment import VcsEnvironment
16
17
  from ._pyproject_reading import PyProjectData
17
18
 
18
19
  log = logging.getLogger(__name__)
@@ -22,6 +23,7 @@ def build_configuration_from_pyproject_internal(
22
23
  pyproject_data: PyProjectData,
23
24
  *,
24
25
  dist_name: str | None = None,
26
+ env: VcsEnvironment | None = None,
25
27
  **integrator_overrides: Any,
26
28
  ) -> Configuration:
27
29
  """Build Configuration with complete workflow orchestration.
@@ -34,7 +36,7 @@ def build_configuration_from_pyproject_internal(
34
36
  2. Determine dist_name (argument > pyproject.project_name)
35
37
  3. Merge integrator overrides (override config file)
36
38
  4. Read and apply env TOML overrides (highest priority)
37
- 5. Build Configuration with proper validation
39
+ 5. Build Configuration with proper validation and VcsEnvironment attached
38
40
 
39
41
  Priority order (highest to lowest):
40
42
  1. Environment TOML overrides (TOOL_OVERRIDES_FOR_DIST, TOOL_OVERRIDES)
@@ -45,6 +47,8 @@ def build_configuration_from_pyproject_internal(
45
47
  Args:
46
48
  pyproject_data: Parsed pyproject data from PyProjectData.from_file() or manual composition
47
49
  dist_name: Distribution name for env var lookups (overrides pyproject_data.project_name)
50
+ env: Optional VcsEnvironment. If None, resolves from the active
51
+ GlobalOverrides context or process environment.
48
52
  **integrator_overrides: Integrator-provided config overrides
49
53
  (override config file, but overridden by env)
50
54
 
@@ -67,9 +71,12 @@ def build_configuration_from_pyproject_internal(
67
71
  """
68
72
  # Import here to avoid circular dependencies
69
73
  from ._config import Configuration
70
- from ._overrides import read_toml_overrides
74
+ from ._environment import resolve_runtime_env
71
75
  from ._pyproject_reading import get_args_for_pyproject
72
76
 
77
+ if env is None:
78
+ env = resolve_runtime_env()
79
+
73
80
  # Step 1: Get base config from pyproject section
74
81
  # This also handles dist_name resolution
75
82
  log.debug(
@@ -96,8 +103,7 @@ def build_configuration_from_pyproject_internal(
96
103
  config_data.update(integrator_overrides)
97
104
 
98
105
  # Step 4: Apply environment TOML overrides (highest priority)
99
- tool_names = (pyproject_data.tool_name.upper().replace("-", "_"),)
100
- env_overrides = read_toml_overrides(actual_dist_name, tool_names=tool_names)
106
+ env_overrides = env.read_toml_overrides(actual_dist_name)
101
107
  if env_overrides:
102
108
  log.debug("Applying environment TOML overrides: %s", list(env_overrides.keys()))
103
109
  config_data.update(env_overrides)
@@ -106,7 +112,7 @@ def build_configuration_from_pyproject_internal(
106
112
  relative_to = pyproject_data.path
107
113
  log.debug("Building Configuration with relative_to=%s", relative_to)
108
114
 
109
- return Configuration.from_data(relative_to=relative_to, data=config_data)
115
+ return Configuration.from_data(relative_to=relative_to, data=config_data, _env=env)
110
116
 
111
117
 
112
118
  __all__ = [
@@ -153,18 +153,18 @@ def _read_pretended_metadata_for(
153
153
  Returns a dictionary with metadata field overrides like:
154
154
  {"node": "g1337beef", "distance": 4}
155
155
  """
156
- from .overrides import EnvReader
156
+ log.debug("dist name: %s", config.dist_name)
157
157
 
158
158
  if env is None:
159
- env = config.env._env
160
-
161
- log.debug("dist name: %s", config.dist_name)
159
+ reader = config.env.make_reader(config.dist_name)
160
+ else:
161
+ from .overrides import EnvReader
162
162
 
163
- reader = EnvReader(
164
- tools_names=config.env.tool_names,
165
- env=env,
166
- dist_name=config.dist_name,
167
- )
163
+ reader = EnvReader(
164
+ tools_names=config.env.tool_names,
165
+ env=env,
166
+ dist_name=config.dist_name,
167
+ )
168
168
 
169
169
  try:
170
170
  metadata_overrides = reader.read_toml(
@@ -259,18 +259,18 @@ def _read_pretended_version_for(
259
259
  tries ``SETUPTOOLS_SCM_PRETEND_VERSION``
260
260
  and ``SETUPTOOLS_SCM_PRETEND_VERSION_FOR_$UPPERCASE_DIST_NAME``
261
261
  """
262
- from .overrides import EnvReader
262
+ log.debug("dist name: %s", config.dist_name)
263
263
 
264
264
  if env is None:
265
- env = config.env._env
266
-
267
- log.debug("dist name: %s", config.dist_name)
265
+ reader = config.env.make_reader(config.dist_name)
266
+ else:
267
+ from .overrides import EnvReader
268
268
 
269
- reader = EnvReader(
270
- tools_names=config.env.tool_names,
271
- env=env,
272
- dist_name=config.dist_name,
273
- )
269
+ reader = EnvReader(
270
+ tools_names=config.env.tool_names,
271
+ env=env,
272
+ dist_name=config.dist_name,
273
+ )
274
274
  pretended = reader.read("PRETEND_VERSION")
275
275
 
276
276
  if pretended:
@@ -166,6 +166,7 @@ def run(
166
166
  ),
167
167
  text=True,
168
168
  encoding="utf-8",
169
+ errors="surrogateescape",
169
170
  timeout=timeout,
170
171
  )
171
172
 
@@ -5,6 +5,7 @@ from __future__ import annotations
5
5
  from typing import TYPE_CHECKING, Any
6
6
 
7
7
  if TYPE_CHECKING:
8
+ from ._environment import VcsEnvironment
8
9
  from ._pyproject_reading import PyProjectData
9
10
 
10
11
 
@@ -14,6 +15,7 @@ def infer_version_string(
14
15
  overrides: dict[str, Any] | None = None,
15
16
  *,
16
17
  force_write_version_files: bool = False,
18
+ env: VcsEnvironment | None = None,
17
19
  ) -> str:
18
20
  """
19
21
  Compute the inferred version string from the given inputs.
@@ -27,6 +29,8 @@ def infer_version_string(
27
29
  pyproject_data: Parsed PyProjectData (may be constructed via for_testing())
28
30
  overrides: Optional override configuration (same keys as [tool.setuptools_scm])
29
31
  force_write_version_files: When True, apply write_to/version_file effects
32
+ env: Optional VcsEnvironment. If None, resolves from the active
33
+ GlobalOverrides context or process environment.
30
34
 
31
35
  Returns:
32
36
  The computed version string.
@@ -34,10 +38,13 @@ def infer_version_string(
34
38
  Raises:
35
39
  SystemExit: If version cannot be determined (via _version_missing)
36
40
  """
37
- from ._config import Configuration
41
+ from ._environment import resolve_runtime_env
38
42
  from ._get_version_impl import _get_version, _version_missing
39
43
 
40
- config = Configuration.from_file(
44
+ if env is None:
45
+ env = resolve_runtime_env()
46
+
47
+ config = env.build_config(
41
48
  dist_name=dist_name, pyproject_data=pyproject_data, **(overrides or {})
42
49
  )
43
50
 
@@ -70,7 +70,10 @@ def discover_workdir(config: Configuration) -> AnyWorkdir | None:
70
70
  - ScmWorkdir result: verify project_path, return immediately.
71
71
  - FallbackWorkdir result: stash as candidate, keep probing for SCM.
72
72
  2. Fallback phase: probe ``project_dir`` (if different from scm root).
73
- 3. Return best stashed FallbackWorkdir if no SCM found.
73
+ 3. Try each stashed FallbackWorkdir in discovery order; return the first
74
+ whose ``get_scm_version()`` is not None. This prevents an
75
+ unprocessed ``.git_archival.txt`` from shadowing a valid ``PKG-INFO``
76
+ (see :issue:`1431`).
74
77
  4. Try StaticWorkdir from config.fallback_version / parentdir_prefix_version.
75
78
  5. Return None.
76
79
  """
@@ -92,7 +95,7 @@ def discover_workdir(config: Configuration) -> AnyWorkdir | None:
92
95
  project_dir = config._resolved_paths.project_dir
93
96
  scm_root_hint = config._resolved_paths.scm_probe_root
94
97
 
95
- fallback_candidate: FallbackWorkdir | None = None
98
+ fallback_candidates: list[FallbackWorkdir] = []
96
99
 
97
100
  def _accept_scm(result: ScmWorkdir, ep_name: str) -> ScmWorkdir:
98
101
  result.project_root = project_dir
@@ -109,7 +112,6 @@ def discover_workdir(config: Configuration) -> AnyWorkdir | None:
109
112
  return result
110
113
 
111
114
  def _probe_dir(current_dir: Path, *, accept_scm: bool) -> ScmWorkdir | None:
112
- nonlocal fallback_candidate
113
115
  for ep_name, factory in factories:
114
116
  try:
115
117
  result = factory(current_dir, config=config)
@@ -122,7 +124,7 @@ def discover_workdir(config: Configuration) -> AnyWorkdir | None:
122
124
  continue
123
125
  if accept_scm and isinstance(result, ScmWorkdir):
124
126
  return _accept_scm(result, ep_name)
125
- if isinstance(result, FallbackWorkdir) and fallback_candidate is None:
127
+ if isinstance(result, FallbackWorkdir):
126
128
  result._config = config
127
129
  log.debug(
128
130
  "stashed fallback workdir %s from factory %s at %s",
@@ -130,7 +132,7 @@ def discover_workdir(config: Configuration) -> AnyWorkdir | None:
130
132
  ep_name,
131
133
  current_dir,
132
134
  )
133
- fallback_candidate = result
135
+ fallback_candidates.append(result)
134
136
  return None
135
137
 
136
138
  # Phase 1: SCM probes at scm_root_hint (the declared root) and optionally parents.
@@ -146,9 +148,19 @@ def discover_workdir(config: Configuration) -> AnyWorkdir | None:
146
148
  if project_dir != scm_root_hint:
147
149
  _probe_dir(project_dir, accept_scm=False)
148
150
 
149
- if fallback_candidate is not None:
150
- log.info("using fallback workdir %s", type(fallback_candidate).__name__)
151
- return fallback_candidate
151
+ # Try each fallback candidate until one can provide a version (#1431).
152
+ # Earlier discovery code stashed all matching fallback workdirs; an
153
+ # unprocessed .git_archival.txt (raw $Format placeholders) would
154
+ # shadow a valid PKG-INFO if we only kept the first candidate.
155
+ for candidate in fallback_candidates:
156
+ if candidate.get_scm_version() is not None:
157
+ log.info("using fallback workdir %s", type(candidate).__name__)
158
+ return candidate
159
+ if fallback_candidates:
160
+ log.debug(
161
+ "all %d fallback candidates returned None for get_scm_version",
162
+ len(fallback_candidates),
163
+ )
152
164
 
153
165
  static = StaticWorkdir(path=project_dir, _config=config)
154
166
  if static.get_scm_version() is not None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vcs-versioning
3
- Version: 2.0.1
3
+ Version: 2.1.0
4
4
  Summary: the blessed package to manage your versions by vcs metadata
5
5
  Author-email: Ronny Pfannschmidt <opensource@ronnypfannschmidt.de>
6
6
  License-Expression: MIT
@@ -304,3 +304,106 @@ class TestFrozenLegacyConfig:
304
304
  frozen = FrozenLegacyConfig(config)
305
305
  with pytest.raises((AttributeError, dataclasses.FrozenInstanceError)):
306
306
  frozen.version_scheme = "something" # type: ignore[attr-defined]
307
+
308
+
309
+ class TestInferVersionStringEnv:
310
+ """Tests that infer_version_string properly resolves and attaches VcsEnvironment."""
311
+
312
+ def _pyproject(self) -> PyProjectData:
313
+ return PyProjectData.for_testing(
314
+ tool_name="vcs-versioning",
315
+ is_required=True,
316
+ section_present=True,
317
+ project_present=True,
318
+ project_name="test-pkg",
319
+ )
320
+
321
+ def test_no_deprecation_warning_with_pretend_version(
322
+ self, monkeypatch: pytest.MonkeyPatch
323
+ ) -> None:
324
+ """infer_version_string must not leak DeprecationWarning for missing env."""
325
+ import warnings
326
+
327
+ from vcs_versioning._version_inference import infer_version_string
328
+
329
+ monkeypatch.setenv("SETUPTOOLS_SCM_PRETEND_VERSION", "1.2.3")
330
+
331
+ with warnings.catch_warnings():
332
+ warnings.filterwarnings("error", category=DeprecationWarning)
333
+ result = infer_version_string("test-pkg", self._pyproject())
334
+
335
+ assert result == "1.2.3"
336
+
337
+ def test_resolves_env_from_global_overrides_context(
338
+ self, monkeypatch: pytest.MonkeyPatch
339
+ ) -> None:
340
+ """infer_version_string respects the active GlobalOverrides context."""
341
+ from vcs_versioning._version_inference import infer_version_string
342
+
343
+ monkeypatch.setenv("MYTOOL_PRETEND_VERSION", "9.8.7")
344
+
345
+ with GlobalOverrides.from_env("MYTOOL"):
346
+ result = infer_version_string("test-pkg", self._pyproject())
347
+
348
+ assert result == "9.8.7"
349
+
350
+ def test_accepts_explicit_env(self, monkeypatch: pytest.MonkeyPatch) -> None:
351
+ """infer_version_string uses an explicitly passed VcsEnvironment."""
352
+ from vcs_versioning._version_inference import infer_version_string
353
+
354
+ env_mapping = {"VCS_VERSIONING_PRETEND_VERSION": "4.5.6"}
355
+ env = VcsEnvironment.from_env(env=env_mapping)
356
+
357
+ result = infer_version_string("test-pkg", self._pyproject(), env=env)
358
+ assert result == "4.5.6"
359
+
360
+
361
+ class TestBuildConfigurationFromPyprojectEnv:
362
+ """Tests that build_configuration_from_pyproject attaches VcsEnvironment."""
363
+
364
+ def _pyproject(self) -> PyProjectData:
365
+ return PyProjectData.for_testing(
366
+ tool_name="vcs-versioning",
367
+ is_required=True,
368
+ section_present=True,
369
+ project_present=True,
370
+ project_name="test-pkg",
371
+ )
372
+
373
+ def test_no_deprecation_warning(self) -> None:
374
+ """build_configuration_from_pyproject must not leak DeprecationWarning."""
375
+ import warnings
376
+
377
+ from vcs_versioning import build_configuration_from_pyproject
378
+
379
+ with warnings.catch_warnings():
380
+ warnings.filterwarnings("error", category=DeprecationWarning)
381
+ config = build_configuration_from_pyproject(
382
+ self._pyproject(), dist_name="test-pkg"
383
+ )
384
+
385
+ assert config._env is not None
386
+
387
+ def test_resolves_env_from_global_overrides_context(self) -> None:
388
+ """build_configuration_from_pyproject uses active GlobalOverrides."""
389
+ from vcs_versioning import build_configuration_from_pyproject
390
+
391
+ with GlobalOverrides.from_env("MYTOOL", env={}):
392
+ config = build_configuration_from_pyproject(
393
+ self._pyproject(), dist_name="test-pkg"
394
+ )
395
+
396
+ assert config._env is not None
397
+ assert "MYTOOL" in config._env.tool_names
398
+
399
+ def test_accepts_explicit_env(self) -> None:
400
+ """build_configuration_from_pyproject passes explicit env through."""
401
+ from vcs_versioning import build_configuration_from_pyproject
402
+
403
+ env = VcsEnvironment.from_env("CUSTOM", env={})
404
+
405
+ config = build_configuration_from_pyproject(
406
+ self._pyproject(), dist_name="test-pkg", env=env
407
+ )
408
+
409
+ assert config._env is env
@@ -138,6 +138,30 @@ def test_no_warn_when_version_file_not_tracked(tmp_path: Path, scm: str) -> None
138
138
  assert tracked_warnings == []
139
139
 
140
140
 
141
+ @pytest.mark.issue(1423)
142
+ def test_parse_version_importable_from_get_version_impl() -> None:
143
+ """setuptools-scm <=10.0.x imports parse_version from this module."""
144
+ from vcs_versioning._get_version_impl import parse_version
145
+
146
+ assert callable(parse_version)
147
+
148
+
149
+ @pytest.mark.issue(1423)
150
+ def test_parse_version_shim_returns_version(tmp_path: Path) -> None:
151
+ """The compat shim should resolve and return a ScmVersion."""
152
+ wd = WorkDir(tmp_path).setup_git()
153
+ wd.add_and_commit("initial")
154
+ wd("git tag -a v1.0.0 -m v1.0.0")
155
+
156
+ c = Configuration(root=tmp_path)
157
+
158
+ from vcs_versioning._get_version_impl import parse_version
159
+ from vcs_versioning._scm_version import ScmVersion
160
+
161
+ result = parse_version(c)
162
+ assert isinstance(result, ScmVersion)
163
+
164
+
141
165
  @pytest.mark.parametrize(
142
166
  ("input", "expected"),
143
167
  [
@@ -4,7 +4,6 @@ from __future__ import annotations
4
4
 
5
5
  import warnings
6
6
 
7
- import pytest
8
7
  from vcs_versioning import Configuration
9
8
  from vcs_versioning._config import TagConfiguration
10
9
  from vcs_versioning._scm_version import tag_to_version
@@ -59,8 +58,10 @@ class TestDescribeMatchGlob:
59
58
 
60
59
 
61
60
  class TestTagStrictWarning:
62
- def test_none_strict_emits_future_warning(self) -> None:
63
- with pytest.warns(FutureWarning, match="tag.strict is not set"):
61
+ def test_none_strict_no_warning(self) -> None:
62
+ """tag.strict=None no longer warns (suppressed per #1422, see #1429)."""
63
+ with warnings.catch_warnings():
64
+ warnings.simplefilter("error", FutureWarning)
64
65
  Configuration(tag=TagConfiguration(strict=None))
65
66
 
66
67
  def test_true_strict_no_warning(self) -> None:
@@ -78,17 +79,13 @@ class TestTagPrefixStripping:
78
79
  """Test that tag.prefix is stripped before tag_regex matching."""
79
80
 
80
81
  def test_prefix_stripped_from_tag(self) -> None:
81
- with warnings.catch_warnings():
82
- warnings.simplefilter("ignore", FutureWarning)
83
- config = Configuration(tag=TagConfiguration(prefix="hatchling-v"))
82
+ config = Configuration(tag=TagConfiguration(prefix="hatchling-v"))
84
83
  version = tag_to_version("hatchling-v1.0.0", config)
85
84
  assert version is not None
86
85
  assert str(version) == "1.0.0"
87
86
 
88
87
  def test_no_prefix_no_stripping(self) -> None:
89
- with warnings.catch_warnings():
90
- warnings.simplefilter("ignore", FutureWarning)
91
- config = Configuration(tag=TagConfiguration(prefix=""))
88
+ config = Configuration(tag=TagConfiguration(prefix=""))
92
89
  version = tag_to_version("v1.0.0", config)
93
90
  assert version is not None
94
91
  assert str(version) == "1.0.0"
@@ -100,17 +97,13 @@ class TestTagPrefixStripping:
100
97
  may still parse -- but the prefix is NOT stripped, meaning
101
98
  git describe --match would have already filtered it out in practice.
102
99
  """
103
- with warnings.catch_warnings():
104
- warnings.simplefilter("ignore", FutureWarning)
105
- config = Configuration(tag=TagConfiguration(prefix="other-"))
100
+ config = Configuration(tag=TagConfiguration(prefix="other-"))
106
101
  version = tag_to_version("hatchling-v1.0.0", config)
107
102
  # Default tag_regex matches and extracts "v1.0.0" from dashed prefix
108
103
  assert version is not None
109
104
 
110
105
  def test_prefix_v_strips_v(self) -> None:
111
- with warnings.catch_warnings():
112
- warnings.simplefilter("ignore", FutureWarning)
113
- config = Configuration(tag=TagConfiguration(prefix="v"))
106
+ config = Configuration(tag=TagConfiguration(prefix="v"))
114
107
  version = tag_to_version("v1.2.3", config)
115
108
  assert version is not None
116
109
  assert str(version) == "1.2.3"
@@ -120,20 +113,19 @@ class TestConfigFromData:
120
113
  """Test that Configuration.from_data correctly parses tag config."""
121
114
 
122
115
  def test_tag_in_from_data(self) -> None:
123
- with warnings.catch_warnings():
124
- warnings.simplefilter("ignore", FutureWarning)
125
- config = Configuration.from_data(
126
- relative_to=".",
127
- data={
128
- "dist_name": "test",
129
- "tag": {"prefix": "mylib-", "strict": True},
130
- },
131
- )
116
+ config = Configuration.from_data(
117
+ relative_to=".",
118
+ data={
119
+ "dist_name": "test",
120
+ "tag": {"prefix": "mylib-", "strict": True},
121
+ },
122
+ )
132
123
  assert config.tag.prefix == "mylib-"
133
124
  assert config.tag.strict is True
134
125
 
135
126
  def test_no_tag_in_from_data(self) -> None:
136
- with pytest.warns(FutureWarning, match="tag.strict"):
127
+ with warnings.catch_warnings():
128
+ warnings.simplefilter("error", FutureWarning)
137
129
  config = Configuration.from_data(
138
130
  relative_to=".",
139
131
  data={"dist_name": "test"},
@@ -143,13 +135,12 @@ class TestConfigFromData:
143
135
 
144
136
 
145
137
  class TestDescribeCommandConflictWarning:
146
- def test_warns_when_prefix_and_describe_command_both_set(self) -> None:
138
+ def test_no_warning_when_prefix_and_describe_command_both_set(self) -> None:
139
+ """Suppressed per #1422, see #1429 for follow-up."""
147
140
  from vcs_versioning._config import GitConfiguration, ScmConfiguration
148
141
 
149
- with pytest.warns(
150
- UserWarning,
151
- match="Both tag.prefix/tag.strict and scm.git.describe_command are set",
152
- ):
142
+ with warnings.catch_warnings():
143
+ warnings.simplefilter("error", UserWarning)
153
144
  Configuration(
154
145
  tag=TagConfiguration(prefix="v", strict=True),
155
146
  scm=ScmConfiguration(
@@ -177,6 +177,40 @@ class TestProjectPathVerificationInDiscovery:
177
177
  discover_workdir(config)
178
178
 
179
179
 
180
+ class TestFallbackPriority:
181
+ @pytest.mark.issue(1431)
182
+ def test_unprocessed_archival_falls_through_to_pkginfo(
183
+ self, tmp_path: Path
184
+ ) -> None:
185
+ """Unprocessed .git_archival.txt must not shadow a valid PKG-INFO.
186
+
187
+ PyPI sdists contain both files: a .git_archival.txt with raw
188
+ ``$Format:...`` placeholders (never substituted because the sdist
189
+ was built by setuptools, not ``git archive``) and a PKG-INFO with
190
+ the correct version. Before the fix, the archival fallback was
191
+ stashed as the sole candidate and its ``get_scm_version()`` returned
192
+ None, causing a LookupError.
193
+ """
194
+ (tmp_path / ".git_archival.txt").write_text(
195
+ "node: $Format:%H$\n"
196
+ "node-date: $Format:%cI$\n"
197
+ "describe-name: $Format:%(describe:tags=true)$\n"
198
+ "ref-names: $Format:%D$\n",
199
+ encoding="utf-8",
200
+ )
201
+ (tmp_path / "PKG-INFO").write_text(
202
+ "Metadata-Version: 2.1\nName: my-pkg\nVersion: 1.2.3\n",
203
+ encoding="utf-8",
204
+ )
205
+ config = Configuration(relative_to=str(tmp_path / "pyproject.toml"))
206
+ result = discover_workdir(config)
207
+ assert result is not None
208
+ assert isinstance(result, PkgInfoWorkdir)
209
+ version = result.get_scm_version()
210
+ assert version is not None
211
+ assert str(version.tag) == "1.2.3"
212
+
213
+
180
214
  class TestFallbackWorkdirDiscoveryFactories:
181
215
  def test_discover_archival_git(self, tmp_path: Path) -> None:
182
216
  from vcs_versioning._fallback_workdir import discover_archival
File without changes
File without changes
File without changes