From 0c430aec258ebd0056a364c33e81be3cb658e0c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Ram=C3=ADrez=20Mondrag=C3=B3n?= Date: Wed, 4 Feb 2026 18:50:12 -0600 Subject: [PATCH 1/7] test: Add a failing test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Edgar Ramírez Mondragón --- tests/commands/test_bump_command.py | 40 +++++++++++++++++++ ...gelog_merge_prerelease_preserves_header.md | 15 +++++++ 2 files changed, 55 insertions(+) create mode 100644 tests/commands/test_bump_command/test_changelog_merge_prerelease_preserves_header.md diff --git a/tests/commands/test_bump_command.py b/tests/commands/test_bump_command.py index 6b8d903d68..c1eddd9aeb 100644 --- a/tests/commands/test_bump_command.py +++ b/tests/commands/test_bump_command.py @@ -1494,6 +1494,46 @@ def test_changelog_config_flag_merge_prerelease_only_prerelease_present( file_regression.check(out, extension=".md") +@pytest.mark.usefixtures("tmp_commitizen_project") +@pytest.mark.freeze_time("2025-01-01") +def test_changelog_merge_prerelease_preserves_header( + mocker: MockFixture, + util: UtilFixture, + changelog_path: Path, + config_path: Path, + file_regression: FileRegressionFixture, +): + """Test that merge_prerelease preserves existing changelog header.""" + with config_path.open("a") as f: + f.write("changelog_merge_prerelease = true\n") + f.write("update_changelog_on_bump = true\n") + f.write("annotated_tag = true\n") + + # Create initial version with changelog that has a header + util.create_file_and_commit("irrelevant commit") + mocker.patch("commitizen.git.GitTag.date", "1970-01-01") + git.tag("0.1.0") + + # Create a changelog with a header manually + changelog_path.write_text( + "# Changelog\n\nAll notable changes to this project will be documented here.\n\n## 0.1.0 (1970-01-01)\n" + ) + + util.create_file_and_commit("feat: add new output") + util.create_file_and_commit("fix: output glitch") + util.run_cli("bump", "--prerelease", "alpha", "--yes") + + util.run_cli("bump", "--changelog") + + with changelog_path.open() as f: + out = f.read() + + # The header should be preserved + assert out.startswith("# Changelog\n") + assert "All notable changes to this project will be documented here." in out + file_regression.check(out, extension=".md") + + @pytest.mark.usefixtures("tmp_commitizen_project") def test_bump_deprecate_files_only(util: UtilFixture): util.create_file_and_commit("feat: new file") diff --git a/tests/commands/test_bump_command/test_changelog_merge_prerelease_preserves_header.md b/tests/commands/test_bump_command/test_changelog_merge_prerelease_preserves_header.md new file mode 100644 index 0000000000..4f10662307 --- /dev/null +++ b/tests/commands/test_bump_command/test_changelog_merge_prerelease_preserves_header.md @@ -0,0 +1,15 @@ +# Changelog + +All notable changes to this project will be documented here. + +## 0.2.0 (2025-01-01) + +### Feat + +- add new output + +### Fix + +- output glitch + +## 0.1.0 (1970-01-01) From 681a433661bdd1bb1a16ce5cc69ad2831cbdd893 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Ram=C3=ADrez=20Mondrag=C3=B3n?= Date: Wed, 4 Feb 2026 18:52:06 -0600 Subject: [PATCH 2/7] fix(bump): Preserve existing changelog header when `changelog_merge_prerelease` is used with `cz bump --changelog` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Edgar Ramírez Mondragón --- commitizen/commands/changelog.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/commitizen/commands/changelog.py b/commitizen/commands/changelog.py index 777fd30493..ea0e56eaab 100644 --- a/commitizen/commands/changelog.py +++ b/commitizen/commands/changelog.py @@ -228,7 +228,11 @@ def __call__(self) -> None: self.file_name ) if latest_full_release_info.index: - changelog_meta.unreleased_start = 0 + # Use the existing unreleased_start if available (from get_metadata()). + # Otherwise, use the position of the first version entry (prerelease) + # to preserve the changelog header. + if changelog_meta.unreleased_start is None: + changelog_meta.unreleased_start = changelog_meta.latest_version_position changelog_meta.latest_version_position = latest_full_release_info.index changelog_meta.unreleased_end = latest_full_release_info.index - 1 From 778edcb035c16de0de8c9d94d3ba02ebb375c234 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Ram=C3=ADrez=20Mondrag=C3=B3n?= Date: Wed, 4 Feb 2026 19:32:59 -0600 Subject: [PATCH 3/7] fix(bump): Preserve existing changelog header when `changelog_merge_prerelease` is used with `cz bump --changelog`, and no prereleases exist MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Edgar Ramírez Mondragón --- commitizen/commands/changelog.py | 28 +++++++++--- tests/commands/test_bump_command.py | 45 +++++++++++++++++++ ...erge_prerelease_no_prereleases_to_merge.md | 19 ++++++++ 3 files changed, 85 insertions(+), 7 deletions(-) create mode 100644 tests/commands/test_bump_command/test_changelog_merge_prerelease_no_prereleases_to_merge.md diff --git a/commitizen/commands/changelog.py b/commitizen/commands/changelog.py index ea0e56eaab..994acfc674 100644 --- a/commitizen/commands/changelog.py +++ b/commitizen/commands/changelog.py @@ -227,20 +227,34 @@ def __call__(self) -> None: latest_full_release_info = self.changelog_format.get_latest_full_release( self.file_name ) - if latest_full_release_info.index: + # Determine if there are prereleases to merge: + # - Only prereleases in changelog (no full release found), OR + # - First version in changelog is before first full release (prereleases exist) + has_prereleases_to_merge = latest_full_release_info.index is not None and ( + latest_full_release_info.name is None + or ( + changelog_meta.latest_version_position is not None + and changelog_meta.latest_version_position + < latest_full_release_info.index + ) + ) + + if has_prereleases_to_merge and latest_full_release_info.index is not None: # Use the existing unreleased_start if available (from get_metadata()). # Otherwise, use the position of the first version entry (prerelease) # to preserve the changelog header. if changelog_meta.unreleased_start is None: - changelog_meta.unreleased_start = changelog_meta.latest_version_position + changelog_meta.unreleased_start = ( + changelog_meta.latest_version_position + ) changelog_meta.latest_version_position = latest_full_release_info.index changelog_meta.unreleased_end = latest_full_release_info.index - 1 - start_rev = latest_full_release_info.name or "" - if not start_rev and latest_full_release_info.index: - # Only pre-releases in changelog - changelog_meta.latest_version_position = None - changelog_meta.unreleased_end = latest_full_release_info.index + 1 + start_rev = latest_full_release_info.name or "" + if not start_rev: + # Only pre-releases in changelog + changelog_meta.latest_version_position = None + changelog_meta.unreleased_end = latest_full_release_info.index + 1 commits = git.get_commits(start=start_rev, end=end_rev, args="--topo-order") if not commits and ( diff --git a/tests/commands/test_bump_command.py b/tests/commands/test_bump_command.py index c1eddd9aeb..831d2b7c8a 100644 --- a/tests/commands/test_bump_command.py +++ b/tests/commands/test_bump_command.py @@ -1534,6 +1534,51 @@ def test_changelog_merge_prerelease_preserves_header( file_regression.check(out, extension=".md") +@pytest.mark.usefixtures("tmp_commitizen_project") +@pytest.mark.freeze_time("2025-01-01") +def test_changelog_merge_prerelease_no_prereleases_to_merge( + mocker: MockFixture, + util: UtilFixture, + changelog_path: Path, + config_path: Path, + file_regression: FileRegressionFixture, +): + """Test that merge_prerelease works correctly when there are no prereleases. + + When changelog_merge_prerelease is enabled but there are no prereleases to merge, + the normal incremental changelog behavior should apply and the existing changelog + content should be preserved. + """ + with config_path.open("a") as f: + f.write("changelog_merge_prerelease = true\n") + f.write("update_changelog_on_bump = true\n") + f.write("annotated_tag = true\n") + + # Create initial version with changelog that has a header + util.create_file_and_commit("feat: initial feature") + mocker.patch("commitizen.git.GitTag.date", "1970-01-01") + git.tag("0.1.0") + + # Create a changelog with a header manually + changelog_path.write_text( + "# Changelog\n\nAll notable changes.\n\n## 0.1.0 (1970-01-01)\n\n### Feat\n\n- initial feature\n" + ) + + # Add new commits and do a regular bump (no prerelease) + util.create_file_and_commit("feat: add new output") + util.create_file_and_commit("fix: output glitch") + util.run_cli("bump", "--changelog") + + with changelog_path.open() as f: + out = f.read() + + # The header and existing content should be preserved + assert out.startswith("# Changelog\n") + assert "All notable changes." in out + assert "## 0.1.0 (1970-01-01)" in out + file_regression.check(out, extension=".md") + + @pytest.mark.usefixtures("tmp_commitizen_project") def test_bump_deprecate_files_only(util: UtilFixture): util.create_file_and_commit("feat: new file") diff --git a/tests/commands/test_bump_command/test_changelog_merge_prerelease_no_prereleases_to_merge.md b/tests/commands/test_bump_command/test_changelog_merge_prerelease_no_prereleases_to_merge.md new file mode 100644 index 0000000000..cf63a78ea2 --- /dev/null +++ b/tests/commands/test_bump_command/test_changelog_merge_prerelease_no_prereleases_to_merge.md @@ -0,0 +1,19 @@ +# Changelog + +All notable changes. + +## 0.2.0 (2025-01-01) + +### Feat + +- add new output + +### Fix + +- output glitch + +## 0.1.0 (1970-01-01) + +### Feat + +- initial feature From 397abe7e50b5ebaf52b572110f79b29af3f9c1c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Ram=C3=ADrez=20Mondrag=C3=B3n?= Date: Fri, 6 Feb 2026 14:24:32 -0600 Subject: [PATCH 4/7] test: Remove redundant assertions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Edgar Ramírez Mondragón --- tests/commands/test_bump_command.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/commands/test_bump_command.py b/tests/commands/test_bump_command.py index 7cd268dcf0..8fabbf2fc5 100644 --- a/tests/commands/test_bump_command.py +++ b/tests/commands/test_bump_command.py @@ -1528,9 +1528,6 @@ def test_changelog_merge_prerelease_preserves_header( with changelog_path.open() as f: out = f.read() - # The header should be preserved - assert out.startswith("# Changelog\n") - assert "All notable changes to this project will be documented here." in out file_regression.check(out, extension=".md") @@ -1572,9 +1569,6 @@ def test_changelog_merge_prerelease_no_prereleases_to_merge( with changelog_path.open() as f: out = f.read() - # The header and existing content should be preserved - assert out.startswith("# Changelog\n") - assert "All notable changes." in out assert "## 0.1.0 (1970-01-01)" in out file_regression.check(out, extension=".md") From fbee6074c5ae7063ac01ad0c92510bbffbb1c882 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Ram=C3=ADrez=20Mondrag=C3=B3n?= Date: Fri, 6 Feb 2026 14:33:08 -0600 Subject: [PATCH 5/7] refactor: Inline condition and rely on comments to explain the logic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Edgar Ramírez Mondragón --- commitizen/commands/changelog.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/commitizen/commands/changelog.py b/commitizen/commands/changelog.py index 9ada548fd0..5093ed9f29 100644 --- a/commitizen/commands/changelog.py +++ b/commitizen/commands/changelog.py @@ -230,16 +230,14 @@ def __call__(self) -> None: # Determine if there are prereleases to merge: # - Only prereleases in changelog (no full release found), OR # - First version in changelog is before first full release (prereleases exist) - has_prereleases_to_merge = latest_full_release_info.index is not None and ( + if latest_full_release_info.index is not None and ( latest_full_release_info.name is None or ( changelog_meta.latest_version_position is not None and changelog_meta.latest_version_position < latest_full_release_info.index ) - ) - - if has_prereleases_to_merge and latest_full_release_info.index is not None: + ): # Use the existing unreleased_start if available (from get_metadata()). # Otherwise, use the position of the first version entry (prerelease) # to preserve the changelog header. From 85b1f902805de5f2c71b808b6f793f26ac755d7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Ram=C3=ADrez=20Mondrag=C3=B3n?= Date: Fri, 6 Feb 2026 14:50:48 -0600 Subject: [PATCH 6/7] test: Remove another redundant assertion --- tests/commands/test_bump_command.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/commands/test_bump_command.py b/tests/commands/test_bump_command.py index 8fabbf2fc5..1f2922872c 100644 --- a/tests/commands/test_bump_command.py +++ b/tests/commands/test_bump_command.py @@ -1569,7 +1569,6 @@ def test_changelog_merge_prerelease_no_prereleases_to_merge( with changelog_path.open() as f: out = f.read() - assert "## 0.1.0 (1970-01-01)" in out file_regression.check(out, extension=".md") From b9ed2c9ca84db600b765df6272eadc846b84b970 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Ram=C3=ADrez=20Mondrag=C3=B3n?= Date: Fri, 6 Feb 2026 19:39:20 -0600 Subject: [PATCH 7/7] test: Use a single parametrized test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Edgar Ramírez Mondragón --- tests/commands/test_bump_command.py | 76 ++++++------------- ...reserves_header_with_prerelease_merge_.md} | 1 + ...serves_header_with_prerelease_no_merge_.md | 21 +++++ ...e_preserves_header_without_prerelease_.md} | 7 +- 4 files changed, 46 insertions(+), 59 deletions(-) rename tests/commands/test_bump_command/{test_changelog_merge_prerelease_preserves_header.md => test_changelog_merge_preserves_header_with_prerelease_merge_.md} (82%) create mode 100644 tests/commands/test_bump_command/test_changelog_merge_preserves_header_with_prerelease_no_merge_.md rename tests/commands/test_bump_command/{test_changelog_merge_prerelease_no_prereleases_to_merge.md => test_changelog_merge_preserves_header_without_prerelease_.md} (53%) diff --git a/tests/commands/test_bump_command.py b/tests/commands/test_bump_command.py index 1f2922872c..ef950e7ca7 100644 --- a/tests/commands/test_bump_command.py +++ b/tests/commands/test_bump_command.py @@ -1494,18 +1494,28 @@ def test_changelog_config_flag_merge_prerelease_only_prerelease_present( file_regression.check(out, extension=".md") +@pytest.mark.parametrize( + ("prerelease", "merge"), + [ + pytest.param(True, "true", id="with_prerelease_merge"), + pytest.param(True, "false", id="with_prerelease_no_merge"), + pytest.param(False, "true", id="without_prerelease"), + ], +) @pytest.mark.usefixtures("tmp_commitizen_project") @pytest.mark.freeze_time("2025-01-01") -def test_changelog_merge_prerelease_preserves_header( +def test_changelog_merge_preserves_header( mocker: MockFixture, util: UtilFixture, changelog_path: Path, config_path: Path, file_regression: FileRegressionFixture, + prerelease: bool, + merge: str, ): """Test that merge_prerelease preserves existing changelog header.""" with config_path.open("a") as f: - f.write("changelog_merge_prerelease = true\n") + f.write(f"changelog_merge_prerelease = {merge}\n") f.write("update_changelog_on_bump = true\n") f.write("annotated_tag = true\n") @@ -1516,67 +1526,25 @@ def test_changelog_merge_prerelease_preserves_header( # Create a changelog with a header manually changelog_path.write_text( - "# Changelog\n\nAll notable changes to this project will be documented here.\n\n## 0.1.0 (1970-01-01)\n" - ) - - util.create_file_and_commit("feat: add new output") - util.create_file_and_commit("fix: output glitch") - util.run_cli("bump", "--prerelease", "alpha", "--yes") - - util.run_cli("bump", "--changelog") - - with changelog_path.open() as f: - out = f.read() - - file_regression.check(out, extension=".md") - - -@pytest.mark.usefixtures("tmp_commitizen_project") -@pytest.mark.freeze_time("2025-01-01") -def test_changelog_merge_prerelease_no_prereleases_to_merge( - mocker: MockFixture, - util: UtilFixture, - changelog_path: Path, - config_path: Path, - file_regression: FileRegressionFixture, -): - """Test that merge_prerelease works correctly when there are no prereleases. - - When changelog_merge_prerelease is enabled but there are no prereleases to merge, - the normal incremental changelog behavior should apply and the existing changelog - content should be preserved. - """ - with config_path.open("a") as f: - f.write("changelog_merge_prerelease = true\n") - f.write("update_changelog_on_bump = true\n") - f.write("annotated_tag = true\n") + dedent("""\ + # Changelog - # Create initial version with changelog that has a header - util.create_file_and_commit("feat: initial feature") - mocker.patch("commitizen.git.GitTag.date", "1970-01-01") - git.tag("0.1.0") + All notable changes to this project will be documented here. - # Create a changelog with a header manually - changelog_path.write_text( - "# Changelog\n\nAll notable changes.\n\n## 0.1.0 (1970-01-01)\n\n### Feat\n\n- initial feature\n" + ## 0.1.0 (1970-01-01) + """) ) - # Add new commits and do a regular bump (no prerelease) util.create_file_and_commit("feat: add new output") util.create_file_and_commit("fix: output glitch") + + if prerelease: + util.run_cli("bump", "--prerelease", "alpha", "--yes") + + util.create_file_and_commit("feat: new feature right before the bump") util.run_cli("bump", "--changelog") with changelog_path.open() as f: out = f.read() file_regression.check(out, extension=".md") - - -@pytest.mark.usefixtures("tmp_commitizen_project") -def test_bump_deprecate_files_only(util: UtilFixture): - util.create_file_and_commit("feat: new file") - with ( - pytest.warns(DeprecationWarning, match=r".*--files-only.*deprecated"), - pytest.raises(ExpectedExit), - ): - util.run_cli("bump", "--yes", "--files-only") diff --git a/tests/commands/test_bump_command/test_changelog_merge_prerelease_preserves_header.md b/tests/commands/test_bump_command/test_changelog_merge_preserves_header_with_prerelease_merge_.md similarity index 82% rename from tests/commands/test_bump_command/test_changelog_merge_prerelease_preserves_header.md rename to tests/commands/test_bump_command/test_changelog_merge_preserves_header_with_prerelease_merge_.md index 4f10662307..c0ac9c5c9c 100644 --- a/tests/commands/test_bump_command/test_changelog_merge_prerelease_preserves_header.md +++ b/tests/commands/test_bump_command/test_changelog_merge_preserves_header_with_prerelease_merge_.md @@ -6,6 +6,7 @@ All notable changes to this project will be documented here. ### Feat +- new feature right before the bump - add new output ### Fix diff --git a/tests/commands/test_bump_command/test_changelog_merge_preserves_header_with_prerelease_no_merge_.md b/tests/commands/test_bump_command/test_changelog_merge_preserves_header_with_prerelease_no_merge_.md new file mode 100644 index 0000000000..6058182503 --- /dev/null +++ b/tests/commands/test_bump_command/test_changelog_merge_preserves_header_with_prerelease_no_merge_.md @@ -0,0 +1,21 @@ +# Changelog + +All notable changes to this project will be documented here. + +## 0.2.0 (2025-01-01) + +### Feat + +- new feature right before the bump + +## 0.2.0a0 (2025-01-01) + +### Feat + +- add new output + +### Fix + +- output glitch + +## 0.1.0 (1970-01-01) diff --git a/tests/commands/test_bump_command/test_changelog_merge_prerelease_no_prereleases_to_merge.md b/tests/commands/test_bump_command/test_changelog_merge_preserves_header_without_prerelease_.md similarity index 53% rename from tests/commands/test_bump_command/test_changelog_merge_prerelease_no_prereleases_to_merge.md rename to tests/commands/test_bump_command/test_changelog_merge_preserves_header_without_prerelease_.md index cf63a78ea2..c0ac9c5c9c 100644 --- a/tests/commands/test_bump_command/test_changelog_merge_prerelease_no_prereleases_to_merge.md +++ b/tests/commands/test_bump_command/test_changelog_merge_preserves_header_without_prerelease_.md @@ -1,11 +1,12 @@ # Changelog -All notable changes. +All notable changes to this project will be documented here. ## 0.2.0 (2025-01-01) ### Feat +- new feature right before the bump - add new output ### Fix @@ -13,7 +14,3 @@ All notable changes. - output glitch ## 0.1.0 (1970-01-01) - -### Feat - -- initial feature