Skip to content

Commit 2f7917d

Browse files
committed
feat: Collapse RC changelogs when a production release exists.
- Filter out RC tags in `git.release_tags` if a production tag exists for that version. - Pass the production flag to `update_changelog` in `validate_pr` and `create_release`.
1 parent 532c6f7 commit 2f7917d

6 files changed

Lines changed: 95 additions & 33 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,6 @@ Full release of ci-tools for testing the production release workflows.
99
#### Bug Fixes
1010

1111
- Improve release progress tracking and publication links. ([a00ac1b3](https://github.com/TokTok/ci-tools/commit/a00ac1b3848043c8da83a89b70e190dbd4929d58))
12-
13-
<a name="v0.8.3-rc.1"></a>
14-
15-
## v0.8.3-rc.1 (2026-02-06)
16-
17-
### Release notes
18-
19-
Some pre-release notes here.
20-
21-
#### Bug Fixes
22-
2312
- Don't crash the todo list update when there are no releases. ([34d2d3a9](https://github.com/TokTok/ci-tools/commit/34d2d3a967620c66a0dbe76205d35042f343209f))
2413
- Correct the path prefixes in release tarballs. ([f69e34f8](https://github.com/TokTok/ci-tools/commit/f69e34f8f54cffcc3b20176b8da4f9021a0797ac))
2514
- Add missing python dependencies in the slim image. ([299d574c](https://github.com/TokTok/ci-tools/commit/299d574c96e54645a4376a519ee2d3d696fba8e0))
@@ -73,18 +62,6 @@ Prod release for ci-tools.
7362
- Run release-soon on everything except push. ([8d52ffa9](https://github.com/TokTok/ci-tools/commit/8d52ffa94e12cc23075fde7d19ae86a10fa91c2b))
7463
- Don't run release-soon check on pushes, only pulls. ([138a61e8](https://github.com/TokTok/ci-tools/commit/138a61e8e66b874849a5e5d82c2607280376c664))
7564
- Output a zero-length file when changelog is empty. ([2337240f](https://github.com/TokTok/ci-tools/commit/2337240fb4840361a1c085b6c91476c3f9c27b43))
76-
- **Security:** Don't pass tokens to repo-local code. ([887ba6cf](https://github.com/TokTok/ci-tools/commit/887ba6cf826e0c43013d90e421047a85b17bd75a))
77-
78-
<a name="v0.8.1-rc.1"></a>
79-
80-
## v0.8.1-rc.1 (2025-02-16)
81-
82-
### Release notes
83-
84-
First release of ci-tools made by ci-tools releaser scripts. Release candidate first.
85-
86-
#### Bug Fixes
87-
8865
- Reviewable config was broken. ([73df8058](https://github.com/TokTok/ci-tools/commit/73df8058eaa6bb34a05411e50b27039447355dc5))
8966
- Forcibly trigger CI on automation pull requests. ([4bcdf84b](https://github.com/TokTok/ci-tools/commit/4bcdf84b4bbc9e23c3a5d9fb89cce3a50b8be373))
9067
- Fix self-name detection for real now. ([782a44c9](https://github.com/TokTok/ci-tools/commit/782a44c9cf025a042e6c301ea1bd55a412dc2616))
@@ -114,6 +91,7 @@ First release of ci-tools made by ci-tools releaser scripts. Release candidate f
11491
- Don't evaluate BUILD.bazel. ([cdc378fd](https://github.com/TokTok/ci-tools/commit/cdc378fd2bbcb636db0a331e03bf0585b2339ea4))
11592
- Fail the `hackage_upload` script on upload failure. ([0c2126d6](https://github.com/TokTok/ci-tools/commit/0c2126d6a22f551c6430def25d1da327ab628ecc))
11693
- Fix hackage upload URL for package candidates. ([563e21cd](https://github.com/TokTok/ci-tools/commit/563e21cdcf71f5719d370e21f880a5d89b9a4a52))
94+
- **Security:** Don't pass tokens to repo-local code. ([887ba6cf](https://github.com/TokTok/ci-tools/commit/887ba6cf826e0c43013d90e421047a85b17bd75a))
11795

11896
#### Features
11997

tools/create_release.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,12 @@ def stage_gitignore(self) -> None:
399399
s.ok(f"Added '{path.strip()}' to {gitignore}")
400400

401401
def stage_validate(self) -> None:
402-
validate_pr.main(validate_pr.Config(commit=not self.config.verify))
402+
validate_pr.main(
403+
validate_pr.Config(
404+
commit=not self.config.verify,
405+
release=self.config.production,
406+
)
407+
)
403408

404409
def extract_issue_release_notes(self, body: str) -> str:
405410
"""Extract the release notes from the issue body."""

tools/lib/git.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -149,16 +149,22 @@ def current_branch(self) -> str:
149149

150150
def release_tags(self, with_rc: bool = True) -> list[str]:
151151
tags = self._run_output(["tag", "--merged"]).splitlines()
152-
return sorted(
153-
(
154-
tag
155-
for tag in tags
156-
if re.match(VERSION_REGEX, tag) and (with_rc or "-rc." not in tag)
157-
),
152+
all_tags = sorted(
153+
(tag for tag in tags if re.match(VERSION_REGEX, tag)),
158154
reverse=True,
159155
key=parse_version,
160156
)
161157

158+
if not with_rc:
159+
return [t for t in all_tags if "-rc." not in t]
160+
161+
prod_versions = {t for t in all_tags if "-rc." not in t}
162+
return [
163+
t
164+
for t in all_tags
165+
if "-rc." not in t or t.split("-rc.")[0] not in prod_versions
166+
]
167+
162168
def release_tag_exists(self, tag: str) -> bool:
163169
"""Check if a tag exists."""
164170
return tag in self.release_tags()

tools/lib/git_test.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# SPDX-License-Identifier: GPL-3.0-or-later
22
# Copyright © 2026 The TokTok team
33
import unittest
4+
import unittest.mock
45

56
from lib import git
67

@@ -30,5 +31,28 @@ def test_comparison(self) -> None:
3031
self.assertNotEqual(git.parse_version("v1.2.3"), git.parse_version("v1.2.4"))
3132

3233

34+
class TestReleaseTags(unittest.TestCase):
35+
def test_release_tags_filtering(self) -> None:
36+
g = git.Git()
37+
g._run_output = unittest.mock.MagicMock( # type: ignore
38+
return_value="v1.0.0\nv1.0.0-rc.1\nv1.0.0-rc.2\nv0.9.0"
39+
)
40+
41+
# Desired behavior: v1.0.0-rc.1 and v1.0.0-rc.2 should be filtered because v1.0.0 exists
42+
tags = g.release_tags(with_rc=True)
43+
self.assertNotIn("v1.0.0-rc.1", tags)
44+
self.assertNotIn("v1.0.0-rc.2", tags)
45+
self.assertIn("v1.0.0", tags)
46+
self.assertIn("v0.9.0", tags)
47+
48+
# RC only version should still show RCs
49+
g._run_output = unittest.mock.MagicMock( # type: ignore
50+
return_value="v1.1.0-rc.1\nv1.0.0"
51+
)
52+
tags = g.release_tags(with_rc=True)
53+
self.assertIn("v1.1.0-rc.1", tags)
54+
self.assertIn("v1.0.0", tags)
55+
56+
3357
if __name__ == "__main__":
3458
unittest.main()

tools/validate_pr.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,13 @@ def check_changelog(failures: list[str], config: Config) -> None:
288288
with stage.Stage(
289289
"Changelog", "The changelog should be up-to-date", failures
290290
) as check:
291-
update_changelog.main()
291+
clog_config = update_changelog.parse_config(update_changelog.read_clog_toml())
292+
if config.release:
293+
clog_config.production = True
294+
elif re.match(git.RELEASE_BRANCH_REGEX, github.head_ref()):
295+
clog_config.production = "-rc." not in github.head_ref()
296+
297+
update_changelog.main(clog_config)
292298
if has_diff(config, "CHANGELOG.md"):
293299
if config.commit:
294300
git.add("CHANGELOG.md")

tools/validate_pr_test.py

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,52 @@
11
# SPDX-License-Identifier: GPL-3.0-or-later
22
# Copyright © 2026 The TokTok team
33
import unittest
4+
import unittest.mock
45

5-
from validate_pr import (parse_toxcore_version, parse_version_diff,
6-
parse_weblate_prs)
6+
from validate_pr import (Config, check_changelog, parse_toxcore_version,
7+
parse_version_diff, parse_weblate_prs)
8+
9+
10+
class TestCheckChangelog(unittest.TestCase):
11+
@unittest.mock.patch("update_changelog.main")
12+
@unittest.mock.patch("update_changelog.read_clog_toml", return_value={})
13+
@unittest.mock.patch("update_changelog.parse_config")
14+
@unittest.mock.patch("validate_pr.github.head_ref")
15+
@unittest.mock.patch("validate_pr.has_diff", return_value=False)
16+
@unittest.mock.patch("validate_pr.stage.Stage")
17+
def test_check_changelog_production(
18+
self,
19+
mock_stage: unittest.mock.MagicMock,
20+
mock_has_diff: unittest.mock.MagicMock,
21+
mock_head_ref: unittest.mock.MagicMock,
22+
mock_parse_config: unittest.mock.MagicMock,
23+
mock_read_clog_toml: unittest.mock.MagicMock,
24+
mock_clog_main: unittest.mock.MagicMock,
25+
) -> None:
26+
clog_config = unittest.mock.MagicMock()
27+
clog_config.production = False
28+
mock_parse_config.return_value = clog_config
29+
30+
# 1. Release config set to True
31+
mock_head_ref.return_value = "some-branch"
32+
config = Config(commit=False, release=True)
33+
check_changelog([], config)
34+
self.assertTrue(clog_config.production)
35+
mock_clog_main.assert_called_with(clog_config)
36+
37+
# 2. Release config False, but branch is a production release branch
38+
clog_config.production = False
39+
mock_head_ref.return_value = "release/v1.0.0"
40+
config = Config(commit=False, release=False)
41+
check_changelog([], config)
42+
self.assertTrue(clog_config.production)
43+
44+
# 3. Release config False, branch is an RC release branch
45+
clog_config.production = False
46+
mock_head_ref.return_value = "release/v1.0.0-rc.1"
47+
config = Config(commit=False, release=False)
48+
check_changelog([], config)
49+
self.assertFalse(clog_config.production)
750

851

952
class TestValidatePRLogic(unittest.TestCase):

0 commit comments

Comments
 (0)