diff --git a/commitizen/commands/bump.py b/commitizen/commands/bump.py index 3e92775e28..7c576e4f50 100644 --- a/commitizen/commands/bump.py +++ b/commitizen/commands/bump.py @@ -102,7 +102,10 @@ def __init__(self, config: BaseConfig, arguments: BumpArgs) -> None: self.git_output_to_stderr = arguments["git_output_to_stderr"] self.no_verify = arguments["no_verify"] self.check_consistency = arguments["check_consistency"] - self.retry = arguments["retry"] + # retry if --retry is passed or retry_bump_after_failure is set in config + self.retry = arguments["retry"] or self.config.settings.get( + "retry_bump_after_failure", False + ) self.pre_bump_hooks = self.config.settings["pre_bump_hooks"] self.post_bump_hooks = self.config.settings["post_bump_hooks"] deprecated_version_type = arguments.get("version_type") diff --git a/commitizen/defaults.py b/commitizen/defaults.py index 40dcee5620..b64a6ce428 100644 --- a/commitizen/defaults.py +++ b/commitizen/defaults.py @@ -55,6 +55,7 @@ class Settings(TypedDict, total=False): pre_bump_hooks: list[str] | None prerelease_offset: int retry_after_failure: bool + retry_bump_after_failure: bool # same as retry_after_failure, but for cz bump style: list[tuple[str, str]] tag_format: str template: str | None @@ -90,6 +91,7 @@ class Settings(TypedDict, total=False): "ignored_tag_formats": [], "bump_message": None, # bumped v$current_version to $new_version "retry_after_failure": False, + "retry_bump_after_failure": False, "allow_abort": False, "allowed_prefixes": [ "Merge", diff --git a/docs/config/bump.md b/docs/config/bump.md index 7263f8066e..642ee367d9 100644 --- a/docs/config/bump.md +++ b/docs/config/bump.md @@ -126,6 +126,17 @@ prerelease_offset = 1 !!! note Under some circumstances, a prerelease cannot start with `0`-for example, in embedded projects where individual characters are encoded as bytes. You can specify an offset from which to start counting. +## `retry_bump_after_failure` + +When set to `true`, `cz bump` is equivalent to `cz bump --retry`. If the bump commit fails the first time (for example because a pre-commit hook reformatted a file), it is retried once. The retry only happens when the changelog is also updated (`--changelog` or `update_changelog_on_bump = true`). + +If not specified, defaults to `false`. + +```toml title="pyproject.toml" +[tool.commitizen] +retry_bump_after_failure = true +``` + ## `tag_format` See [`--tag-format`](../commands/bump.md#-tag-format). diff --git a/docs/config/configuration_file.md b/docs/config/configuration_file.md index 172cbce1a3..2174b32d2e 100644 --- a/docs/config/configuration_file.md +++ b/docs/config/configuration_file.md @@ -235,7 +235,7 @@ Key configuration categories include: - **Version Management**: `version`, `version_provider`, `version_scheme`, `version_files` - **Tagging**: `tag_format`, `legacy_tag_formats`, `ignored_tag_formats`, `gpg_sign`, `annotated_tag` - **Changelog**: `changelog_file`, `changelog_format`, `changelog_incremental`, `update_changelog_on_bump` -- **Bumping**: `bump_message`, `major_version_zero`, `prerelease_offset`, `pre_bump_hooks`, `post_bump_hooks` +- **Bumping**: `bump_message`, `major_version_zero`, `prerelease_offset`, `pre_bump_hooks`, `post_bump_hooks`, `retry_bump_after_failure` - **Commit Validation**: `allowed_prefixes`, `message_length_limit`, `allow_abort`, `retry_after_failure` - **Customization**: `customize`, `style`, `use_shortcuts`, `template`, `extras` diff --git a/tests/commands/test_bump_command.py b/tests/commands/test_bump_command.py index 2e3921deab..c4f608528e 100644 --- a/tests/commands/test_bump_command.py +++ b/tests/commands/test_bump_command.py @@ -1708,3 +1708,22 @@ def test_bump_allow_no_commit_issue( util.run_cli( "bump", "--allow-no-commit", "--prerelease", "rc" ) # Should not fail when changelog generation runs with no new commits + + +def test_bump_retry_bump_after_failure_config( + mocker: MockFixture, util: UtilFixture, pyproject: Path +): + """'cz bump' retries a failed commit when retry_bump_after_failure is true""" + with pyproject.open("a", encoding="utf-8") as f: + f.write("retry_bump_after_failure = true\n") + util.create_file_and_commit("feat: new file") + + # first commit fails (like when a pre-commit hook edits a file), second works + failed = cmd.Command("", "hook failed", b"", b"", 1) + passed = cmd.Command("", "", b"", b"", 0) + commit_mock = mocker.patch.object(git, "commit", side_effect=[failed, passed]) + + # bump only retries the commit when it also updates the changelog + util.run_cli("bump", "--changelog", "--yes") + + assert commit_mock.call_count == 2 diff --git a/tests/test_conf.py b/tests/test_conf.py index 15be0630aa..0bf433beeb 100644 --- a/tests/test_conf.py +++ b/tests/test_conf.py @@ -85,6 +85,7 @@ "ignored_tag_formats": [], "bump_message": None, "retry_after_failure": False, + "retry_bump_after_failure": False, "allow_abort": False, "allowed_prefixes": [ "Merge", @@ -126,6 +127,7 @@ "ignored_tag_formats": [], "bump_message": None, "retry_after_failure": False, + "retry_bump_after_failure": False, "allow_abort": False, "allowed_prefixes": [ "Merge",