Skip to content

Commit 46c82fc

Browse files
committed
fix(changelog): skip unreachable tags when picking previous release
`cz changelog <version>` resolves the previous-release tag by walking the creatordate-sorted tag list. When a parallel maintenance branch carries a tag whose creatordate sits between the current release and the legitimate previous release, that branch's tag was wrongly picked because it was reachable from some commit, just not from HEAD. `get_tags()` already supports a `reachable_only` flag that appends `git tag --merged`. Pass it from the changelog path so unreachable parallel-line tags are filtered out before the sort, mirroring the behaviour of `get_latest_tag_name()` which already uses `git describe --abbrev=0 --tags` (ancestry-based) for the same reason. Fixes #2083
1 parent 9432748 commit 46c82fc

2 files changed

Lines changed: 75 additions & 1 deletion

File tree

commitizen/commands/changelog.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,9 @@ def __call__(self) -> None:
209209
if not self.file_name:
210210
raise NotAllowed("filename is required.")
211211

212-
tags = self.tag_rules.get_version_tags(git.get_tags(), warn=True)
212+
tags = self.tag_rules.get_version_tags(
213+
git.get_tags(reachable_only=True), warn=True
214+
)
213215
changelog_meta = changelog.Metadata()
214216
if self.incremental:
215217
changelog_meta = self.changelog_format.get_metadata(self.file_name)

tests/commands/test_changelog_command.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1693,3 +1693,75 @@ def test_changelog_template_incremental_variable(
16931693
util.run_cli("changelog", "--file-name", target, "--incremental")
16941694
out = Path(target).read_text(encoding="utf-8")
16951695
file_regression.check(out, extension=".incremental.md")
1696+
1697+
1698+
@pytest.mark.usefixtures("tmp_commitizen_project")
1699+
@pytest.mark.freeze_time("2026-09-02")
1700+
def test_changelog_previous_release_skips_unreachable_tags(
1701+
config_path: Path,
1702+
changelog_path: Path,
1703+
monkeypatch: pytest.MonkeyPatch,
1704+
util: UtilFixture,
1705+
):
1706+
"""Regression test for #2083.
1707+
1708+
``commitizen.changelog.get_oldest_and_newest_rev`` picks the previous-
1709+
release tag by walking the creatordate-sorted tag list. When the
1710+
production call site feeds that function an unfiltered ``git.get_tags()``
1711+
result, a parallel maintenance branch's tag with a creatordate between
1712+
the current release and the legitimate previous release is wrongly
1713+
picked. The fix is to pass ``reachable_only=True`` so unreachable
1714+
parallel-line tags are filtered out before the sort.
1715+
"""
1716+
# Arrange — bare tag format, then a parallel v4 line and a main line.
1717+
with config_path.open("a", encoding="utf-8") as f:
1718+
f.write('tag_format = "$version"\n')
1719+
1720+
# The fixture resets GIT_COMMITTER_DATE on every ``tick``/``patch_env``,
1721+
# so ``util.create_tag`` would clobber the date we set. Drive git
1722+
# directly via ``commitizen.cmd.run`` and restore the fixture-managed
1723+
# env afterward so subsequent ``util.tick`` keeps working.
1724+
from commitizen import cmd as _cmd
1725+
1726+
def _tag_at(tag, iso_date):
1727+
monkeypatch.setenv("GIT_COMMITTER_DATE", iso_date)
1728+
result = _cmd.run(["git", "tag", "-a", tag, "-m", tag])
1729+
assert result.return_code == 0, result.err
1730+
util.patch_env()
1731+
1732+
# Main line: 5.3.1, then branch off to v4, then 5.4.0.
1733+
util.create_file_and_commit("feat: main feature a")
1734+
_tag_at("5.3.1", "2026-06-29T00:00:00")
1735+
1736+
util.create_branch("v4")
1737+
util.switch_branch("v4")
1738+
util.create_file_and_commit("feat: v4 feature a")
1739+
1740+
util.switch_branch("master")
1741+
util.create_file_and_commit("feat: main feature b")
1742+
_tag_at("5.4.0", "2026-09-02T00:00:00")
1743+
1744+
# v4 line ships 4.12.0 between 5.3.1 and 5.4.0 in time. 4.12.0's tag
1745+
# is NOT an ancestor of master — it sits on the v4 branch HEAD which
1746+
# is never merged back.
1747+
util.switch_branch("v4")
1748+
util.create_file_and_commit("feat: v4 feature b")
1749+
_tag_at("4.12.0", "2026-07-14T00:00:00")
1750+
util.switch_branch("master")
1751+
1752+
# Act — call ``get_oldest_and_newest_rev`` with ``reachable_only=True``
1753+
# (the production call site after the fix). With ``reachable_only``,
1754+
# 4.12.0 is filtered out before the sort, so the previous-release tag
1755+
# is 5.3.1. Without it, the previous-release tag is 4.12.0.
1756+
from commitizen import git as cz_git
1757+
from commitizen.changelog import TagRules, get_oldest_and_newest_rev
1758+
1759+
rules = TagRules()
1760+
start_rev, end_rev = get_oldest_and_newest_rev(
1761+
cz_git.get_tags(reachable_only=True), "5.4.0", rules
1762+
)
1763+
1764+
# Assert — the previous-release tag must be 5.3.1 (the most recent
1765+
# ancestor of HEAD before 5.4.0), not 4.12.0.
1766+
assert end_rev == "5.4.0"
1767+
assert start_rev == "5.3.1"

0 commit comments

Comments
 (0)