gabriel / muse public
describe.py python
162 lines 5.3 KB
Raw
sha256:37af4ab271a64f11d7bf14b4871ebaaf5dd68fb0247521007c6b034d35aa01be Merge branch 'fix/hub-user-read-update-path' into dev Human 9 days ago
1 """Tag-based commit description for ``muse describe``.
2
3 Walks backward from a commit through the ancestor graph and finds the nearest
4 semantic version tag. Returns a human-readable label where the format depends
5 on the hop count:
6
7 - Exactly on a tag (distance=0): ``v1.0.0``
8 - N commits past a tag: ``v1.0.0-N-<abbrev hex>`` (no sha256: prefix)
9
10 When no eligible version tag is found (no stable tags, or no tags at all)
11 ``DescribeResult.tag`` is ``None`` and ``distance`` is ``None``.
12
13 Pre-release filtering::
14
15 include_pre=False (default) — only stable version tags are eligible
16 include_pre=True (--pre) — all version tags are eligible
17
18 Walk budget::
19
20 _MAX_WALK = 50_000 commits.
21
22 Tag-selection tie-breaking::
23
24 When multiple tags point to the same commit the one with the highest
25 semver precedence wins (``semver_key`` descending).
26 """
27
28 import logging
29 import pathlib
30 from typing import TypedDict
31
32 from muse.core.types import split_id
33 from muse.core.commits import read_commit
34 from muse.core.version_tags import list_version_tags, VersionTagRecord, semver_key
35
36 logger = logging.getLogger(__name__)
37
38 _MAX_WALK = 50_000
39
40
41 class DescribeResult(TypedDict):
42 """Result of describing a commit by its nearest version tag.
43
44 Fields:
45 commit_id: Full SHA-256 hex of the described commit.
46 tag: Nearest version tag string, or ``None`` if no tag was found.
47 distance: Hop count from the tag's commit to this commit, or ``None``.
48 short_sha: First ``abbrev`` hex characters of the commit ID (no algo prefix).
49 description: Human-readable description string, or ``None`` if no tag found.
50 exact: ``True`` when distance == 0 and tag is not ``None``.
51 """
52
53 commit_id: str
54 tag: str | None
55 distance: int | None
56 short_sha: str
57 description: str | None
58 exact: bool
59
60
61 def describe_commit(
62 root: pathlib.Path,
63 repo_id: str,
64 commit_id: str,
65 *,
66 include_pre: bool = False,
67 first_parent: bool = False,
68 abbrev: int = 8,
69 ) -> DescribeResult:
70 """Return a human-readable description of *commit_id* using version tags.
71
72 Walks backward from *commit_id* through the parent chain using BFS and
73 finds the nearest eligible version tag.
74
75 When *include_pre* is ``False`` (the default) only stable version tags
76 (no pre-release component) are eligible. Set *include_pre=True* to
77 include all version tags.
78
79 Args:
80 root: Repository root directory.
81 repo_id: Repository content ID (used to look up version tags).
82 commit_id: Starting commit to describe (typically HEAD).
83 include_pre: Include pre-release version tags as eligible reference points.
84 first_parent: Follow only the first-parent chain (skip merge second-parents).
85 abbrev: Length of the hex suffix in the description string (default 8).
86
87 Returns:
88 A :class:`DescribeResult` with the nearest tag name, hop count, and
89 formatted description string. When no eligible tag is found,
90 ``tag``, ``distance``, and ``description`` are all ``None``.
91 """
92 algo, hex_str = split_id(commit_id)
93 short_sha = hex_str[:abbrev]
94
95 _no_tag = DescribeResult(
96 commit_id=commit_id,
97 tag=None,
98 distance=None,
99 short_sha=short_sha,
100 description=None,
101 exact=False,
102 )
103
104 # Load all version tags and filter by pre-release preference.
105 all_vtags: list[VersionTagRecord] = list_version_tags(root)
106 if not include_pre:
107 all_vtags = [t for t in all_vtags if not t.semver["pre"]]
108
109 if not all_vtags:
110 return _no_tag
111
112 # Build commit_id → best version tag map. Multiple tags on the same
113 # commit: keep the one with the highest semver precedence.
114 tag_by_commit: dict[str, VersionTagRecord] = {}
115 for vtag in all_vtags:
116 existing = tag_by_commit.get(vtag.commit_id)
117 if existing is None or semver_key(vtag.semver) > semver_key(existing.semver):
118 tag_by_commit[vtag.commit_id] = vtag
119
120 if not tag_by_commit:
121 return _no_tag
122
123 # BFS backward tracking hop distance per node.
124 distances: dict[str, int] = {commit_id: 0}
125
126 def _adjacency(cid: str) -> list[str]:
127 rec = read_commit(root, cid)
128 if rec is None:
129 return []
130 parents = [p for p in (rec.parent_commit_id, rec.parent2_commit_id) if p]
131 if first_parent:
132 parents = parents[:1]
133 dist = distances.get(cid, 0)
134 for p in parents:
135 if p not in distances:
136 distances[p] = dist + 1
137 return parents
138
139 from muse.core.graph import walk_dag
140
141 nodes_yielded = 0
142 for cid in walk_dag(commit_id, _adjacency, max_nodes=_MAX_WALK):
143 nodes_yielded += 1
144 dist = distances.get(cid, 0)
145 if cid in tag_by_commit:
146 vtag = tag_by_commit[cid]
147 if dist == 0:
148 description = vtag.tag
149 else:
150 description = f"{vtag.tag}-{dist}-{short_sha}"
151 return DescribeResult(
152 commit_id=commit_id,
153 tag=vtag.tag,
154 distance=dist,
155 short_sha=short_sha,
156 description=description,
157 exact=(dist == 0),
158 )
159
160 if nodes_yielded >= _MAX_WALK:
161 logger.warning("⚠️ describe: reached %d-commit walk limit", _MAX_WALK)
162 return _no_tag
File History 11 commits
sha256:37af4ab271a64f11d7bf14b4871ebaaf5dd68fb0247521007c6b034d35aa01be Merge branch 'fix/hub-user-read-update-path' into dev Human 9 days ago
sha256:b7be56ec091919a612cffe7f3c8b600209d5155517443fdac0e16954c8c7d81f Merge 'task/git-export-ignored-file-deletion' into 'dev' — … Human 12 days ago
sha256:8de4334a98c945aace420969d389ad678aa926d4ab4e886b2ac4c4241cb3bf2b revert: keep pyproject.toml in canonical PEP 440 form Sonnet 4.6 patch 15 days ago
sha256:50bb615c573aa4b928fca75b60f82ba2a659910066507cec6a95c412ae22ccb9 docs: add issue docs for push have-negotiation bug (#55) an… Sonnet 4.6 18 days ago
sha256:d90d175cded68aae1d4ffcf4858917854195d0cd8ce1fe73cee4dbc02541cb74 chore: trigger push to surface null-OID paths Sonnet 4.6 24 days ago
sha256:e452ad9a6ace6ccc6d875a35e06caf9da5576a970c1c36133b69a891ce5fefa8 chore: prebuild timing test Sonnet 4.6 34 days ago
sha256:0008ab6695e3e064b3e236b24fd19e538fef6a588eb0d211622f4466d919c0b1 merge: pull staging/dev — advance to 0.2.0rc12 Sonnet 4.6 patch 36 days ago
sha256:9c33d61749fff814c5226d5386aa2af7064c2c02788594a25fdd709358132eea fix: _PROPOSAL_PREFIX_RESOLVE_LIMIT 200 → 100 to match hub … Sonnet 4.6 47 days ago
sha256:36c3cb3e76619d4c30a6d9bf81b5ec4ff148e30dcfed913e3114ca7b43b81c7e fix: rename objects→blobs in push client and all stale test… Sonnet 4.6 patch 50 days ago
sha256:c06a9b9b9fee26c68ea725b44d54b2c0a171301ce9de746d5b656617b4463a9a fix: repair four test failures from post-migration audit Sonnet 4.6 patch 56 days ago
sha256:1900655993c83c4107067375548a7be823e471d2515830842f1a12cba4bd3cdf fix: unified object store migration — idempotent writes, JS… Sonnet 4.6 minor 57 days ago