Skip to content

Commit ca11490

Browse files
Deprecate git_describe_command in favor of scm.git.describe_command
- implement backward compatibility handling - Update documentation and tests to reflect changes
1 parent ed3f677 commit ca11490

File tree

6 files changed

+185
-10
lines changed

6 files changed

+185
-10
lines changed

docs/config.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ Callables or other Python objects have to be passed in `setup.py` (via the `use_
9292
this is a function for advanced use and you should be
9393
familiar with the `setuptools-scm` internals to use it.
9494

95-
`git_describe_command`
95+
`scm.git.describe_command`
9696
: This command will be used instead the default `git describe --long` command.
9797

9898
Defaults to the value set by [setuptools_scm.git.DEFAULT_DESCRIBE][]
@@ -106,11 +106,16 @@ Callables or other Python objects have to be passed in `setup.py` (via the `use_
106106
- `"fetch_on_shallow"`: Automatically fetches to rectify shallow repositories
107107
- `"fail_on_missing_submodules"`: Fails when submodules are defined but not initialized
108108

109-
The `"fail_on_missing_submodules"` option is useful to prevent packaging incomplete
109+
The `"fail_on_missing_submodules"` option is useful to prevent packaging incomplete
110110
projects when submodules are required for a complete build.
111111

112112
Note: This setting is overridden by any explicit `pre_parse` parameter passed to the git parse function.
113113

114+
`git_describe_command` (deprecated)
115+
: **Deprecated since 8.4.0**: Use `scm.git.describe_command` instead.
116+
117+
This field is maintained for backward compatibility but will issue a deprecation warning when used.
118+
114119
`normalize`
115120
: A boolean flag indicating if the version string should be normalized.
116121
Defaults to `True`. Setting this to `False` is equivalent to setting

docs/usage.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,10 @@ dynamic = ["version"]
4040
# Configure custom options here (version schemes, file writing, etc.)
4141
version_file = "src/mypackage/_version.py"
4242

43-
# Example: Fail if submodules are not initialized (useful for projects requiring submodules)
43+
# Example: Git-specific configuration
4444
[tool.setuptools_scm.scm.git]
45-
pre_parse = "fail_on_missing_submodules"
45+
pre_parse = "fail_on_missing_submodules" # Fail if submodules are not initialized
46+
describe_command = "git describe --dirty --tags --long --exclude *js*" # Custom describe command
4647
```
4748

4849
Both approaches will work with projects that support PEP 518 ([pip](https://pypi.org/project/pip) and

src/setuptools_scm/_config.py

Lines changed: 93 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,57 @@
3030

3131
log = _log.log.getChild("config")
3232

33+
34+
def _is_called_from_dataclasses() -> bool:
35+
"""Check if the current call is from the dataclasses module."""
36+
import inspect
37+
38+
frame = inspect.currentframe()
39+
try:
40+
# Walk up to 7 frames to check for dataclasses calls
41+
current_frame = frame
42+
assert current_frame is not None
43+
for _ in range(7):
44+
current_frame = current_frame.f_back
45+
if current_frame is None:
46+
break
47+
if "dataclasses.py" in current_frame.f_code.co_filename:
48+
return True
49+
return False
50+
finally:
51+
del frame
52+
53+
54+
class _GitDescribeCommandDescriptor:
55+
"""Data descriptor for deprecated git_describe_command field."""
56+
57+
def __get__(
58+
self, obj: Configuration | None, objtype: type[Configuration] | None = None
59+
) -> _t.CMD_TYPE | None:
60+
if obj is None:
61+
return self # type: ignore[return-value]
62+
63+
# Only warn if not being called by dataclasses.replace or similar introspection
64+
is_from_dataclasses = _is_called_from_dataclasses()
65+
if not is_from_dataclasses:
66+
warnings.warn(
67+
"Configuration field 'git_describe_command' is deprecated. "
68+
"Use 'scm.git.describe_command' instead.",
69+
DeprecationWarning,
70+
stacklevel=2,
71+
)
72+
return obj.scm.git.describe_command
73+
74+
def __set__(self, obj: Configuration, value: _t.CMD_TYPE | None) -> None:
75+
warnings.warn(
76+
"Configuration field 'git_describe_command' is deprecated. "
77+
"Use 'scm.git.describe_command' instead.",
78+
DeprecationWarning,
79+
stacklevel=2,
80+
)
81+
obj.scm.git.describe_command = value
82+
83+
3384
DEFAULT_TAG_REGEX = re.compile(
3485
r"^(?:[\w-]+-)?(?P<version>[vV]?\d+(?:\.\d+){0,2}[^\+]*)(?:\+.*)?$"
3586
)
@@ -101,6 +152,7 @@ class GitConfiguration:
101152
pre_parse: git.GitPreParse = dataclasses.field(
102153
default_factory=lambda: _get_default_git_pre_parse()
103154
)
155+
describe_command: _t.CMD_TYPE | None = None
104156

105157
@classmethod
106158
def from_data(cls, data: dict[str, Any]) -> GitConfiguration:
@@ -158,7 +210,10 @@ class Configuration:
158210
version_file: _t.PathT | None = None
159211
version_file_template: str | None = None
160212
parse: ParseFunction | None = None
161-
git_describe_command: _t.CMD_TYPE | None = None
213+
git_describe_command: dataclasses.InitVar[_t.CMD_TYPE | None] = (
214+
_GitDescribeCommandDescriptor()
215+
)
216+
162217
dist_name: str | None = None
163218
version_cls: type[_VersionT] = _Version
164219
search_parent_directories: bool = False
@@ -170,9 +225,42 @@ class Configuration:
170225
default_factory=lambda: ScmConfiguration()
171226
)
172227

173-
def __post_init__(self) -> None:
228+
# Deprecated fields (handled in __post_init__)
229+
230+
def __post_init__(self, git_describe_command: _t.CMD_TYPE | None) -> None:
174231
self.tag_regex = _check_tag_regex(self.tag_regex)
175232

233+
# Handle deprecated git_describe_command
234+
# Check if it's a descriptor object (happens when no value is passed)
235+
if git_describe_command is not None and not isinstance(
236+
git_describe_command, _GitDescribeCommandDescriptor
237+
):
238+
# Check if this is being called from dataclasses
239+
is_from_dataclasses = _is_called_from_dataclasses()
240+
241+
same_value = (
242+
self.scm.git.describe_command is not None
243+
and self.scm.git.describe_command == git_describe_command
244+
)
245+
246+
if is_from_dataclasses and same_value:
247+
# Ignore the passed value - it's from dataclasses.replace() with same value
248+
pass
249+
else:
250+
warnings.warn(
251+
"Configuration field 'git_describe_command' is deprecated. "
252+
"Use 'scm.git.describe_command' instead.",
253+
DeprecationWarning,
254+
stacklevel=2,
255+
)
256+
# Check for conflicts
257+
if self.scm.git.describe_command is not None:
258+
raise ValueError(
259+
"Cannot specify both 'git_describe_command' (deprecated) and "
260+
"'scm.git.describe_command'. Please use only 'scm.git.describe_command'."
261+
)
262+
self.scm.git.describe_command = git_describe_command
263+
176264
@property
177265
def absolute_root(self) -> str:
178266
return _check_absolute_root(self.root, self.relative_to)
@@ -227,6 +315,9 @@ def from_data(
227315

228316
# Handle nested SCM configuration
229317
scm_data = data.pop("scm", {})
318+
319+
# Handle nested SCM configuration
320+
230321
scm_config = ScmConfiguration.from_data(scm_data)
231322

232323
return cls(

src/setuptools_scm/_get_version_impl.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ def get_version(
154154
version_cls: Any | None = None,
155155
normalize: bool = True,
156156
search_parent_directories: bool = False,
157+
scm: dict[str, Any] | None = None,
157158
) -> str:
158159
"""
159160
If supplied, relative_to should be a file from which root may
@@ -165,7 +166,19 @@ def get_version(
165166
version_cls = _validate_version_cls(version_cls, normalize)
166167
del normalize
167168
tag_regex = parse_tag_regex(tag_regex)
168-
config = Configuration(**locals())
169+
170+
# Handle scm parameter by converting it to ScmConfiguration
171+
if scm is not None:
172+
scm_config = _config.ScmConfiguration.from_data(scm)
173+
else:
174+
scm_config = _config.ScmConfiguration()
175+
176+
# Remove scm from locals() since we handle it separately
177+
config_params = locals().copy()
178+
config_params.pop("scm", None)
179+
config_params.pop("scm_config", None)
180+
181+
config = _config.Configuration(scm=scm_config, **config_params)
169182
maybe_version = _get_version(config, force_write_version_files=True)
170183

171184
if maybe_version is None:

src/setuptools_scm/git.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -330,8 +330,8 @@ def version_from_describe(
330330
config: Configuration,
331331
describe_command: _t.CMD_TYPE | None,
332332
) -> ScmVersion | None:
333-
if config.git_describe_command is not None:
334-
describe_command = config.git_describe_command
333+
if config.scm.git.describe_command is not None:
334+
describe_command = config.scm.git.describe_command
335335

336336
if describe_command is not None:
337337
if isinstance(describe_command, str):

testing/test_git.py

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -435,7 +435,11 @@ def test_not_matching_tags(wd: WorkDir) -> None:
435435
wd.commit_testfile()
436436
assert wd.get_version(
437437
tag_regex=r"^apache-arrow-([\.0-9]+)$",
438-
git_describe_command="git describe --dirty --tags --long --exclude *js* ",
438+
scm={
439+
"git": {
440+
"describe_command": "git describe --dirty --tags --long --exclude *js* "
441+
}
442+
},
439443
).startswith("0.11.2")
440444

441445

@@ -758,3 +762,64 @@ def test_invalid_git_pre_parse_raises_error() -> None:
758762
ValueError, match="Invalid git pre_parse function 'invalid_function'"
759763
):
760764
Configuration.from_data(relative_to=".", data=invalid_config_data)
765+
766+
767+
def test_git_describe_command_backward_compatibility() -> None:
768+
"""Test backward compatibility for git_describe_command configuration."""
769+
# Test old configuration style still works with deprecation warning
770+
old_config_data = {
771+
"git_describe_command": "git describe --dirty --tags --long --exclude *js*"
772+
}
773+
774+
with pytest.warns(DeprecationWarning, match=r"git_describe_command.*deprecated"):
775+
config = Configuration.from_data(relative_to=".", data=old_config_data)
776+
777+
# Verify it was migrated to the new location
778+
assert (
779+
config.scm.git.describe_command
780+
== "git describe --dirty --tags --long --exclude *js*"
781+
)
782+
783+
784+
def test_git_describe_command_from_data_conflict() -> None:
785+
"""Test that specifying both old and new configuration in from_data raises ValueError."""
786+
# Both old and new configuration specified - should raise ValueError
787+
mixed_config_data = {
788+
"git_describe_command": "old command",
789+
"scm": {"git": {"describe_command": "new command"}},
790+
}
791+
792+
# The Configuration constructor should handle the conflict detection
793+
with pytest.warns(DeprecationWarning, match=r"git_describe_command.*deprecated"):
794+
with pytest.raises(
795+
ValueError, match=r"Cannot specify both.*git_describe_command"
796+
):
797+
Configuration.from_data(relative_to=".", data=mixed_config_data)
798+
799+
800+
def test_git_describe_command_init_argument_deprecation() -> None:
801+
"""Test that passing git_describe_command as init argument issues deprecation warning."""
802+
# Test init argument
803+
with pytest.warns(DeprecationWarning, match=r"git_describe_command.*deprecated"):
804+
config = Configuration(git_describe_command="test command")
805+
806+
# Verify the value was migrated to the new location
807+
assert config.scm.git.describe_command == "test command"
808+
809+
810+
def test_git_describe_command_init_conflict() -> None:
811+
"""Test that specifying both old and new configuration raises ValueError."""
812+
from setuptools_scm._config import GitConfiguration
813+
from setuptools_scm._config import ScmConfiguration
814+
815+
# Both old init arg and new configuration specified - should raise ValueError
816+
with pytest.warns(DeprecationWarning, match=r"git_describe_command.*deprecated"):
817+
with pytest.raises(
818+
ValueError, match=r"Cannot specify both.*git_describe_command"
819+
):
820+
Configuration(
821+
git_describe_command="old command",
822+
scm=ScmConfiguration(
823+
git=GitConfiguration(describe_command="new command")
824+
),
825+
)

0 commit comments

Comments
 (0)