Skip to content

fix: respect pre-commit reformats when bumping #505

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions commitizen/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,12 @@
"default": False,
"help": "Output changelog to the stdout",
},
{
"name": ["--retry"],
"action": "store_true",
"default": False,
"help": "retry commit if it fails the 1st time",
},
],
},
{
Expand Down
14 changes: 12 additions & 2 deletions commitizen/commands/bump.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from logging import getLogger
from typing import List, Optional

import questionary
Expand All @@ -18,6 +19,8 @@
NoVersionSpecifiedError,
)

logger = getLogger("commitizen")


class Bump:
"""Show prompt for the user to create a guided commit."""
Expand Down Expand Up @@ -49,6 +52,7 @@ def __init__(self, config: BaseConfig, arguments: dict):
self.changelog_to_stdout = arguments["changelog_to_stdout"]
self.no_verify = arguments["no_verify"]
self.check_consistency = arguments["check_consistency"]
self.retry = arguments["retry"]

def is_initial_tag(self, current_tag_version: str, is_yes: bool = False) -> bool:
"""Check if reading the whole git tree up to HEAD is needed."""
Expand Down Expand Up @@ -209,16 +213,22 @@ def __call__(self): # noqa: C901
},
)
changelog_cmd()
c = cmd.run(f"git add {changelog_cmd.file_name}")
c = cmd.run(f"git add {changelog_cmd.file_name} {' '.join(version_files)}")

self.config.set_key("version", str(new_version))

if is_files_only:
raise ExpectedExit()

c = git.commit(message, args=self._get_commit_args())
if self.retry and c.return_code != 0 and self.changelog:
# Maybe pre-commit reformatted some files? Retry once
logger.debug("1st git.commit error: %s", c.err)
logger.info("1st commit attempt failed; retrying once")
cmd.run(f"git add {changelog_cmd.file_name} {' '.join(version_files)}")
c = git.commit(message, args=self._get_commit_args())
if c.return_code != 0:
raise BumpCommitFailedError(f'git.commit error: "{c.err.strip()}"')
raise BumpCommitFailedError(f'2nd git.commit error: "{c.err.strip()}"')
c = git.tag(
new_tag_version,
annotated=self.bump_settings.get("annotated_tag", False)
Expand Down
37 changes: 25 additions & 12 deletions docs/bump.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,32 +54,38 @@ Some examples:

```bash
$ cz bump --help
usage: cz bump [-h] [--dry-run] [--files-only] [--changelog] [--no-verify] [--local-version]
[--yes] [--tag-format TAG_FORMAT] [--bump-message BUMP_MESSAGE]
[--prerelease {alpha,beta,rc}]
[--increment {MAJOR,MINOR,PATCH}] [--check-consistency] [--annotated-tag]
usage: cz bump [-h] [--dry-run] [--files-only] [--local-version] [--changelog]
[--no-verify] [--yes] [--tag-format TAG_FORMAT]
[--bump-message BUMP_MESSAGE] [--prerelease {alpha,beta,rc}]
[--increment {MAJOR,MINOR,PATCH}] [--check-consistency]
[--annotated-tag] [--changelog-to-stdout] [--retry]

optional arguments:
options:
-h, --help show this help message and exit
--dry-run show output to stdout, no commit, no modified files
--files-only bump version in the files from the config
--local-version bump only the local version portion
--changelog, -ch generate the changelog for the newest version
--no-verify this option bypasses the pre-commit and commit-msg hooks
--no-verify this option bypasses the pre-commit and commit-msg
hooks
--yes accept automatically questions done
--local-version bump the local portion of the version
--tag-format TAG_FORMAT
the format used to tag the commit and read it, use it in existing projects, wrap
around simple quotes
the format used to tag the commit and read it, use it
in existing projects, wrap around simple quotes
--bump-message BUMP_MESSAGE
template used to create the release commit, useful when working with CI
template used to create the release commit, useful
when working with CI
--prerelease {alpha,beta,rc}, -pr {alpha,beta,rc}
choose type of prerelease
--increment {MAJOR,MINOR,PATCH}
manually specify the desired increment
--check-consistency, -cc
check consistency among versions defined in commitizen configuration and
version_files
check consistency among versions defined in commitizen
configuration and version_files
--annotated-tag, -at create annotated tag instead of lightweight one
--changelog-to-stdout
Output changelog to the stdout
--retry retry commit if it fails the 1st time
```

### `--files-only`
Expand Down Expand Up @@ -179,6 +185,13 @@ Example:
cz bump --changelog --changelog-to-stdout > body.md
```

### `--retry`

If you use tools like [pre-commit](https://pre-commit.com/), add this flag.
It will retry the commit if it fails the 1st time.

Useful to combine with code formatters, like [Prettier](https://prettier.io/).

## Avoid raising errors

Some situations from commitizen rise an exit code different than 0.
Expand Down
79 changes: 78 additions & 1 deletion tests/test_bump_create_commit_message.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import sys
from pathlib import Path
from textwrap import dedent

import pytest
from packaging.version import Version

from commitizen import bump
from commitizen import bump, cli, cmd, exceptions

conversion = [
(
Expand All @@ -20,3 +24,76 @@ def test_create_tag(test_input, expected):
Version(current_version), Version(new_version), message_template
)
assert new_tag == expected


@pytest.mark.parametrize("retry", (True, False))
def test_bump_pre_commit_changelog(tmp_commitizen_project, mocker, freezer, retry):
freezer.move_to("2022-04-01")
testargs = ["cz", "bump", "--changelog", "--yes"]
if retry:
testargs.append("--retry")
else:
pytest.xfail("it will fail because pre-commit will reformat CHANGELOG.md")
mocker.patch.object(sys, "argv", testargs)
with tmp_commitizen_project.as_cwd():
# Configure prettier as a pre-commit hook
Path(".pre-commit-config.yaml").write_text(
"""
repos:
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v2.6.2
hooks:
- id: prettier
stages: [commit]
"""
)
# Prettier inherits editorconfig
Path(".editorconfig").write_text(
"""
[*]
indent_size = 4
"""
)
cmd.run("git add -A")
cmd.run("git commit -m 'fix: _test'")
cmd.run("pre-commit install")
cli.main()
# Pre-commit fixed last line adding extra indent and "\" char
assert Path("CHANGELOG.md").read_text() == dedent(
"""\
## 0.1.1 (2022-04-01)

### Fix

- \\_test
"""
)


@pytest.mark.parametrize("retry", (True, False))
def test_bump_pre_commit_changelog_fails_always(
tmp_commitizen_project, mocker, freezer, retry
):
freezer.move_to("2022-04-01")
testargs = ["cz", "bump", "--changelog", "--yes"]
if retry:
testargs.append("--retry")
mocker.patch.object(sys, "argv", testargs)
with tmp_commitizen_project.as_cwd():
Path(".pre-commit-config.yaml").write_text(
"""
repos:
- repo: local
hooks:
- id: forbid-changelog
name: changelogs are forbidden
entry: changelogs are forbidden
language: fail
files: CHANGELOG.md
"""
)
cmd.run("git add -A")
cmd.run("git commit -m 'feat: forbid changelogs'")
cmd.run("pre-commit install")
with pytest.raises(exceptions.BumpCommitFailedError):
cli.main()