|
21 | 21 |
|
22 | 22 | if TYPE_CHECKING: |
23 | 23 | import sys |
24 | | - from collections.abc import Iterable, Sequence |
| 24 | + from collections.abc import Iterable, Iterator, Sequence |
25 | 25 |
|
26 | 26 | # Self is Python 3.11+ but backported in typing-extensions |
27 | 27 | if sys.version_info < (3, 11): |
@@ -232,22 +232,26 @@ def find_tag_for( |
232 | 232 | # If the requested version is incomplete (e.g., "1.2"), try to find the latest |
233 | 233 | # matching tag that shares the provided prefix. |
234 | 234 | if len(release) < 3: |
235 | | - matching_versions: list[tuple[VersionProtocol, GitTag]] = [] |
236 | | - for tag in tags: |
237 | | - try: |
238 | | - tag_version = self.extract_version(tag) |
239 | | - except InvalidVersion: |
240 | | - continue |
241 | | - if tag_version.release[: len(release)] != release: |
242 | | - continue |
243 | | - matching_versions.append((tag_version, tag)) |
| 235 | + matching_versions = [ |
| 236 | + (tag_version, tag) |
| 237 | + for tag_version, tag in self._parse_tag_versions(tags) |
| 238 | + if tag_version.release[: len(release)] == release |
| 239 | + ] |
244 | 240 |
|
245 | 241 | if matching_versions: |
246 | 242 | _, latest_tag = max(matching_versions, key=lambda vt: vt[0]) |
247 | 243 | return latest_tag |
248 | 244 |
|
249 | 245 | possible_tags = set(self.normalize_tag(version, f) for f in self.tag_formats) |
250 | 246 | candidates = [t for t in tags if t.name in possible_tags] |
| 247 | + if not candidates: |
| 248 | + # Tag formats with regex-only parts (e.g. `\+.*`) cannot be rendered |
| 249 | + # by `normalize_tag`, so fall back to comparing extracted versions. |
| 250 | + candidates = [ |
| 251 | + tag |
| 252 | + for tag_version, tag in self._parse_tag_versions(tags) |
| 253 | + if tag_version == version |
| 254 | + ] |
251 | 255 | if len(candidates) > 1: |
252 | 256 | warnings.warn( |
253 | 257 | UserWarning( |
@@ -279,3 +283,19 @@ def _extract_version(self, match: re.Match[str]) -> str: |
279 | 283 | if devrelease := groups.get("devrelease"): |
280 | 284 | parts.append(devrelease) |
281 | 285 | return "".join(parts) |
| 286 | + |
| 287 | + def _parse_tag_versions( |
| 288 | + self, tags: Iterable[GitTag] |
| 289 | + ) -> Iterator[tuple[VersionProtocol, GitTag]]: |
| 290 | + """ |
| 291 | + Yield each tag with its extracted version, skipping invalid tags. |
| 292 | +
|
| 293 | + Used when searching tags by version: a tag that matches no tag format, |
| 294 | + or whose version is rejected by the version scheme, is unrelated to the |
| 295 | + search and must not abort it. |
| 296 | + """ |
| 297 | + for tag in tags: |
| 298 | + try: |
| 299 | + yield self.extract_version(tag), tag |
| 300 | + except InvalidVersion: |
| 301 | + continue |
0 commit comments