Skip to content

Commit 8bf6c38

Browse files
authored
Fix mike version sorting (#171)
For the same major and minor, pre-releases were sorted as newer than stable releases, but this is not correct. This PR fixes that. It also makes the sorting of the `versions.json` file more robust, as it doesn't parse and re-serialize the version object, but instead only uses the `version` field for sorting, and the rest of the fields are dumping verbatim without any rewriting. This means that if new versions of `mike` add more fields to the JSON, we won't need to update the sorting code, as it will just work.
2 parents b8ee525 + 0ee52d5 commit 8bf6c38

File tree

4 files changed

+201
-63
lines changed

4 files changed

+201
-63
lines changed

RELEASE_NOTES.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,16 @@
22

33
## Summary
44

5-
This release adds support for `pylint` 3, so downstream projects can upgrade their `pylint` version.
5+
This release fixes a bug in `mike` version sorting.
66

77
## Upgrading
88

9-
If upgrading `pylint` you might get a few new check errors.
9+
- `frequenz.repo.config.mkdocs.mike.`: The `sort_versions()` function now takes plain `str`s as arguments instead of `MikeVersionInfo` objects.
1010

1111
### Cookiecutter template
1212

1313
There is no need to regenerate any templates with this release.
1414

1515
## Bug Fixes
1616

17-
- `mkdocs`: The `conftest` module is now properly hidden from the documentation again.
17+
- CI / `mkdocs`: `mike` version sorting now properly sort pre-releases as older than stable releases for the same major and minor version.

src/frequenz/repo/config/cli/version/mike/sort.py

Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,48 +4,35 @@
44
"""Sort `mike`'s `version.json` file with a custom order."""
55

66

7-
import dataclasses
87
import json
98
import sys
10-
from typing import TextIO
9+
from typing import Any, TextIO
1110

1211
from .... import github
13-
from ....mkdocs.mike import MikeVersionInfo, sort_mike_versions
12+
from ....mkdocs.mike import sort_mike_versions
1413

1514

16-
def _sort(stream_in: TextIO, stream_out: TextIO) -> None:
17-
"""Sort the versions in the given in stream to the given out stream.
18-
19-
Args:
20-
stream_in: The stream to read the versions from.
21-
stream_out: The stream to write the sorted versions to.
22-
"""
23-
versions = json.load(stream_in)
24-
sorted_versions = sort_mike_versions([MikeVersionInfo(**v) for v in versions])
25-
json.dump(sorted_versions, stream_out, separators=(",", ":"))
26-
27-
28-
def _load_and_sort_versions_from(stream: TextIO) -> list[MikeVersionInfo]:
29-
"""Load the versions from the given stream.
15+
def _load_and_sort_versions_from(stream: TextIO) -> dict[str, dict[str, Any]]:
16+
"""Load the versions from the given stream and sort them.
3017
3118
Args:
3219
stream: The stream to read the versions from.
3320
3421
Returns:
35-
The loaded versions.
22+
The sorted loaded versions.
3623
"""
37-
versions = [MikeVersionInfo(**v) for v in json.load(stream)]
38-
return sort_mike_versions(versions)
24+
versions = {v["version"]: v for v in json.load(stream)}
25+
return {v: versions[v] for v in sort_mike_versions(list(versions.keys()))}
3926

4027

41-
def _dump_versions_to(versions: list[MikeVersionInfo], stream: TextIO) -> None:
28+
def _dump_versions_to(versions: dict[str, dict[str, Any]], stream: TextIO) -> None:
4229
"""Dump the versions to the given stream.
4330
4431
Args:
4532
versions: The versions to dump.
4633
stream: The stream to write the versions to.
4734
"""
48-
json.dump([dataclasses.asdict(v) for v in versions], stream, separators=(",", ":"))
35+
json.dump(list(versions.values()), stream, separators=(",", ":"))
4936

5037

5138
def main() -> None:

src/frequenz/repo/config/mkdocs/mike.py

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -147,8 +147,8 @@ def _to_fake_sortable_semver(version: str) -> str:
147147
148148
The following transformations are applied:
149149
150+
- `vX.Y-pre` -> `X.Y.0-pre`
150151
- `vX.Y` -> `X.Y.0`
151-
- `vX.Y-pre` -> `X.Y.99999`
152152
- `vX.Y-dev` -> `X.Y.999999`
153153
154154
The idea is to convert the version string to a semver string that can be sorted
@@ -161,7 +161,7 @@ def _to_fake_sortable_semver(version: str) -> str:
161161
The converted version string.
162162
"""
163163
version = _stable_to_semver_re.sub(r"\1.\2.0", version)
164-
version = _pre_to_semver_re.sub(r"\1.\2.99999", version)
164+
version = _pre_to_semver_re.sub(r"\1.\2.0-pre", version)
165165
version = _dev_to_semver_re.sub(r"\1.\2.999999", version)
166166
if version.startswith("v"):
167167
version = version[1:]
@@ -175,17 +175,17 @@ def compare_mike_version(version1: str, version2: str) -> int:
175175
176176
- Versions are first compared by major version (`X`).
177177
- If they have the same major, then they are compared by minor version (`Y`).
178-
- If they have the same major and minor, then pre-releases (`vX.Y-pre`) are
179-
considered bigger than stable versions (`vX.Y`) and development versions
178+
- If they have the same major and minor, then stable versions (`vX.Y`) are
179+
considered bigger than pre-releases (`vX.Y-pre`) and development versions
180180
(`vX.Y-dev`) are considered bigger than pre-releases.
181181
- Any other version not matching `vX.Y(-pre|-dev)?` is considered to be bigger than
182182
the matching versions.
183183
- Not matching versions are compared alphabetically.
184184
185185
Example:
186186
187-
`v1.0` < `v1.0-pre` < `v1.0-dev` < `v1.1` < `v2.0` < `v2.0-pre` < `v2.0-dev`
188-
< `whatever` < `x`.
187+
`v1.0-pre` < `v1.0` < `v1.0-dev` < `v1.1` < `v2.0-pre` < `v2.0` < `v2.0-dev`
188+
< `whatever` < `x`.
189189
190190
Args:
191191
version1: The first version to compare.
@@ -210,9 +210,7 @@ def compare_mike_version(version1: str, version2: str) -> int:
210210
return -1 if version1 < version2 else 1
211211

212212

213-
def sort_mike_versions(
214-
versions: list[MikeVersionInfo], *, reverse: bool = True
215-
) -> list[MikeVersionInfo]:
213+
def sort_mike_versions(versions: list[str], *, reverse: bool = True) -> list[str]:
216214
"""Sort `mike`'s `version.json` file with a custom order.
217215
218216
The `version` keys are expected as follows:
@@ -227,16 +225,17 @@ def sort_mike_versions(
227225
- Versions are first sorted by major version (`X`).
228226
- Inside a major version group, versions are sorted by minor version (`Y`).
229227
- For the same major and minor version, development versions (`-dev`) considered
230-
the latest for that major version group, then pre-release versions (`-pre`), and
231-
finally stable versions.
228+
the latest for that major version group, then stable versions, and finally
229+
pre-release versions (`-pre`).
232230
- Other versions appear first and are sorted alphabetically.
233231
234232
The versions are sorted in-place using
235233
[`compare_mike_version()`][frequenz.repo.config.mkdocs.mike.compare_mike_version].
236234
237235
Example:
238236
239-
`z`, `whatever`, `v2.1-dev`, `v2.1-pre`, `v2.0`, `v1.1-dev`, `v1.0-dev`, `v1.0`
237+
`z`, `whatever`, `v2.1-dev`, `v2.1`, `v2.1-pre`, `v2.0`, `v1.1-dev`, `v1.0-dev`,
238+
`v1.0`
240239
241240
Args:
242241
versions: The list of versions to sort.
@@ -245,9 +244,5 @@ def sort_mike_versions(
245244
Returns:
246245
The sorted list of versions.
247246
"""
248-
249-
def compare(ver1: MikeVersionInfo, ver2: MikeVersionInfo) -> int:
250-
return compare_mike_version(ver1.version, ver2.version)
251-
252-
versions.sort(key=functools.cmp_to_key(compare), reverse=reverse)
247+
versions.sort(key=functools.cmp_to_key(compare_mike_version), reverse=reverse)
253248
return versions

0 commit comments

Comments
 (0)