Skip to content

Commit 8218176

Browse files
fix #1052: use consistent node hash length
1 parent 7501fd3 commit 8218176

File tree

8 files changed

+81
-12
lines changed

8 files changed

+81
-12
lines changed

.cursor/rules/test-running.mdc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,7 @@ alwaysApply: true
66

77
use `uv run pytest` to run tests
88
use uv to manage dependencies
9+
10+
follow preexisting conventions in the project
11+
12+
- use the fixtures

src/setuptools_scm/_integration/dump_version.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,29 @@
66

77
from .. import _types as _t
88
from .._log import log as parent_log
9+
from .._node_utils import _format_node_for_output
910
from .._version_cls import _version_as_tuple
1011
from ..version import ScmVersion
1112

1213
log = parent_log.getChild("dump_version")
1314

15+
16+
class _TemplateScmVersion:
17+
"""Wrapper for ScmVersion that formats node for template output."""
18+
19+
def __init__(self, scm_version: ScmVersion) -> None:
20+
self._scm_version = scm_version
21+
22+
def __getattr__(self, name: str) -> object:
23+
# Delegate all attribute access to the wrapped ScmVersion
24+
return getattr(self._scm_version, name)
25+
26+
@property
27+
def node(self) -> str | None:
28+
"""Return the node formatted for output."""
29+
return _format_node_for_output(self._scm_version.node)
30+
31+
1432
TEMPLATES = {
1533
".py": """\
1634
# file generated by setuptools-scm
@@ -101,10 +119,12 @@ def write_version_to_path(
101119
log.debug("dump %s into %s", version, target)
102120
version_tuple = _version_as_tuple(version)
103121
if scm_version is not None:
122+
# Wrap ScmVersion to provide formatted node for templates
123+
template_scm_version = _TemplateScmVersion(scm_version)
104124
content = final_template.format(
105125
version=version,
106126
version_tuple=version_tuple,
107-
scm_version=scm_version,
127+
scm_version=template_scm_version,
108128
)
109129
else:
110130
content = final_template.format(version=version, version_tuple=version_tuple)

src/setuptools_scm/_node_utils.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
"""Private utilities for consistent node ID handling across SCM backends."""
2+
3+
from __future__ import annotations
4+
5+
# Standard node ID length used across all SCM backends
6+
_NODE_ID_LENGTH = 10
7+
8+
9+
def _slice_node_id(node_id: str) -> str:
10+
"""
11+
Slice a node ID to a consistent length.
12+
13+
This ensures that all SCM backends (git, mercurial, archival)
14+
return the same length node IDs for consistency.
15+
16+
Args:
17+
node_id: The full node ID/hash from the SCM
18+
19+
Returns:
20+
The node ID sliced to the standard length
21+
"""
22+
return node_id[:_NODE_ID_LENGTH]
23+
24+
25+
def _format_node_for_output(node_id: str | None) -> str | None:
26+
"""
27+
Format a node ID for output, applying consistent slicing.
28+
29+
Args:
30+
node_id: The full node ID/hash from the SCM or None
31+
32+
Returns:
33+
The node ID sliced to standard length for output, or None if input was None
34+
"""
35+
if node_id is None:
36+
return None
37+
38+
# Handle mercurial nodes with 'h' prefix
39+
if node_id.startswith("h"):
40+
# For mercurial nodes, slice the part after 'h' and reconstruct
41+
hg_hash = node_id[1:] # Remove 'h' prefix
42+
sliced_hash = _slice_node_id(hg_hash)
43+
return "h" + sliced_hash
44+
45+
# For git nodes (with or without 'g' prefix) and others
46+
return _slice_node_id(node_id)

src/setuptools_scm/git.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import dataclasses
44
import logging
5-
import operator
65
import os
76
import re
87
import shlex
@@ -47,6 +46,7 @@
4746
"--dirty",
4847
"--tags",
4948
"--long",
49+
"--abbrev=40",
5050
"--match",
5151
"*[0-9]*",
5252
]
@@ -174,12 +174,10 @@ def fetch_shallow(self) -> None:
174174
run_git(["fetch", "--unshallow"], self.path, check=True, timeout=240)
175175

176176
def node(self) -> str | None:
177-
unsafe_short_node = operator.itemgetter(slice(7))
178-
179177
return run_git(
180178
["rev-parse", "--verify", "--quiet", "HEAD"], self.path
181179
).parse_success(
182-
parse=unsafe_short_node,
180+
parse=str,
183181
)
184182

185183
def count_all_nodes(self) -> int:

src/setuptools_scm/hg.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ def get_meta(self, config: Configuration) -> ScmVersion | None:
6666
if self._is_initial_node(node):
6767
return self._create_initial_meta(config, dirty, branch, node_date)
6868

69-
node = "h" + node[:7]
69+
node = "h" + node
7070
tags = self._parse_tags(tags_str)
7171

7272
# Try to get version from current tags
@@ -285,7 +285,7 @@ def parse(root: _t.PathT, config: Configuration) -> ScmVersion | None:
285285

286286
def archival_to_version(data: dict[str, str], config: Configuration) -> ScmVersion:
287287
log.debug("data %s", data)
288-
node = data.get("node", "")[:12]
288+
node = data.get("node", "")
289289
if node:
290290
node = "h" + node
291291
if "tag" in data:

src/setuptools_scm/hg_git.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ def node(self) -> str | None:
119119

120120
return hg_node
121121

122-
return git_node[:7]
122+
return git_node
123123

124124
def count_all_nodes(self) -> int:
125125
res = run_hg(["log", "-r", "ancestors(.)", "-T", "."], cwd=self.path)

src/setuptools_scm/version.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
from . import _entrypoints
1818
from . import _modify_version
19+
from ._node_utils import _format_node_for_output
1920

2021
if TYPE_CHECKING:
2122
import sys
@@ -170,7 +171,7 @@ def format_with(self, fmt: str, **kw: object) -> str:
170171
time=self.time,
171172
tag=self.tag,
172173
distance=self.distance,
173-
node=self.node,
174+
node=_format_node_for_output(self.node),
174175
dirty=self.dirty,
175176
branch=self.branch,
176177
node_date=self.node_date,

testing/test_mercurial.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,12 @@ def wd(wd: WorkDir) -> WorkDir:
3131

3232
archival_mapping = {
3333
"1.0": {"tag": "1.0"},
34-
"1.1.0.dev3+h000000000000": {
34+
"1.1.0.dev3+h0000000000": {
3535
"latesttag": "1.0",
3636
"latesttagdistance": "3",
3737
"node": "0" * 20,
3838
},
39-
"1.0.1.dev3+h000000000000": {
39+
"1.0.1.dev3+h0000000000": {
4040
"latesttag": "1.0.0",
4141
"latesttagdistance": "3",
4242
"branch": "1.0",
@@ -163,7 +163,7 @@ def test_version_from_archival(wd: WorkDir) -> None:
163163
""",
164164
)
165165

166-
assert wd.get_version() == "0.2.dev3+h000000000000"
166+
assert wd.get_version() == "0.2.dev3+h0000000000"
167167

168168

169169
@pytest.mark.issue("#72")

0 commit comments

Comments
 (0)