diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ee3bcc372a..44c8499187 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -56,7 +56,7 @@ repos: - tomli - repo: https://github.com/commitizen-tools/commitizen - rev: v4.13.5 # automatically updated by Commitizen + rev: v4.13.6 # automatically updated by Commitizen hooks: - id: commitizen - id: commitizen-branch diff --git a/CHANGELOG.md b/CHANGELOG.md index 470ffc2817..601ab664b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## v4.13.6 (2026-02-07) + +### Fix + +- **bump**: preserve existing changelog header when `changelog_merge_prerelease` is used with `cz bump --changelog` (#1850) + ## v4.13.5 (2026-02-05) ### Fix diff --git a/commitizen/__version__.py b/commitizen/__version__.py index 4d0c7979a6..800ff94d6d 100644 --- a/commitizen/__version__.py +++ b/commitizen/__version__.py @@ -1 +1 @@ -__version__ = "4.13.5" +__version__ = "4.13.6" diff --git a/commitizen/commands/changelog.py b/commitizen/commands/changelog.py index 21a5abfe15..5093ed9f29 100644 --- a/commitizen/commands/changelog.py +++ b/commitizen/commands/changelog.py @@ -227,16 +227,32 @@ def __call__(self) -> None: latest_full_release_info = self.changelog_format.get_latest_full_release( self.file_name ) - if latest_full_release_info.index: - changelog_meta.unreleased_start = 0 + # 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) + 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 + ) + ): + # 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 - 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/pyproject.toml b/pyproject.toml index 4a753ceaa8..1d1851deb1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "commitizen" -version = "4.13.5" +version = "4.13.6" description = "Python commitizen client tool" authors = [{ name = "Santiago Fraire", email = "santiwilly@gmail.com" }] maintainers = [ diff --git a/tests/commands/test_bump_command.py b/tests/commands/test_bump_command.py index da7a12c0d7..ef950e7ca7 100644 --- a/tests/commands/test_bump_command.py +++ b/tests/commands/test_bump_command.py @@ -1494,11 +1494,57 @@ 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") -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") +@pytest.mark.freeze_time("2025-01-01") +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(f"changelog_merge_prerelease = {merge}\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( + dedent("""\ + # Changelog + + All notable changes to this project will be documented here. + + ## 0.1.0 (1970-01-01) + """) + ) + + 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") diff --git a/tests/commands/test_bump_command/test_changelog_merge_preserves_header_with_prerelease_merge_.md b/tests/commands/test_bump_command/test_changelog_merge_preserves_header_with_prerelease_merge_.md new file mode 100644 index 0000000000..c0ac9c5c9c --- /dev/null +++ b/tests/commands/test_bump_command/test_changelog_merge_preserves_header_with_prerelease_merge_.md @@ -0,0 +1,16 @@ +# Changelog + +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 + +- output glitch + +## 0.1.0 (1970-01-01) 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_preserves_header_without_prerelease_.md b/tests/commands/test_bump_command/test_changelog_merge_preserves_header_without_prerelease_.md new file mode 100644 index 0000000000..c0ac9c5c9c --- /dev/null +++ b/tests/commands/test_bump_command/test_changelog_merge_preserves_header_without_prerelease_.md @@ -0,0 +1,16 @@ +# Changelog + +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 + +- output glitch + +## 0.1.0 (1970-01-01) diff --git a/uv.lock b/uv.lock index c13d22556b..7198658490 100644 --- a/uv.lock +++ b/uv.lock @@ -195,7 +195,7 @@ wheels = [ [[package]] name = "commitizen" -version = "4.13.5" +version = "4.13.6" source = { editable = "." } dependencies = [ { name = "argcomplete" },