Skip to content
Open
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
49 changes: 33 additions & 16 deletions commitizen/commands/bump.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,13 @@ class BumpArgs(Settings, total=False):
yes: bool


def _stage_updated_files(updated_files: list[str]) -> None:
c = git.add(*updated_files)
if c.return_code != 0:
err = c.err.strip() or c.out
raise BumpCommitFailedError(f'git.add error: "{err}"')


class Bump:
"""Show prompt for the user to create a guided commit."""

Expand Down Expand Up @@ -380,23 +387,33 @@ def __call__(self) -> None:
if self.arguments.get("version_files_only"):
raise ExpectedExit()

# FIXME: check if any changes have been staged
git.add(*updated_files)
c = git.commit(message, args=self._get_commit_args())
if self.retry and c.return_code != 0 and self.changelog_flag:
# 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")
git.add(*updated_files)
c = git.commit(message, args=self._get_commit_args())
if c.return_code != 0:
err = c.err.strip() or c.out
raise BumpCommitFailedError(f'2nd git.commit error: "{err}"')
if updated_files:
_stage_updated_files(updated_files)

for msg in (c.out, c.err):
if msg:
out_func = out.diagnostic if self.git_output_to_stderr else out.write
out_func(msg)
# When there is nothing for the bump to commit (e.g. ``version_provider
# = "scm"`` with no ``version_files`` and no ``--changelog``), skip the
# commit step and just tag ``HEAD``. Calling ``git commit`` here would
# fail with ``nothing to commit, working tree clean`` (#1530).
if not git.has_pending_changes():
out.info("No file changes; skipping bump commit and tagging HEAD.")
else:
c = git.commit(message, args=self._get_commit_args())
if self.retry and c.return_code != 0 and self.changelog_flag:
# 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")
_stage_updated_files(updated_files)
c = git.commit(message, args=self._get_commit_args())
if c.return_code != 0:
err = c.err.strip() or c.out
raise BumpCommitFailedError(f'2nd git.commit error: "{err}"')

for msg in (c.out, c.err):
if msg:
out_func = (
out.diagnostic if self.git_output_to_stderr else out.write
)
out_func(msg)

c = git.tag(
new_tag_version,
Expand Down
12 changes: 12 additions & 0 deletions commitizen/git.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,18 @@ def is_staging_clean() -> bool:
return not bool(c.out)


def has_pending_changes() -> bool:
"""Check whether there are any tracked-file changes for ``git commit -a`` to commit.

Returns ``True`` if there are staged or unstaged modifications to tracked
files, ``False`` if the working tree is clean for tracked files. Untracked
files are intentionally ignored — they would be ignored by ``git commit -a``
too.
"""
c = cmd.run(["git", "status", "--porcelain", "--untracked-files=no"])
return bool(c.out.strip())


def is_git_project() -> bool:
c = cmd.run(["git", "rev-parse", "--is-inside-work-tree"])
return c.out.strip() == "true"
Expand Down
71 changes: 71 additions & 0 deletions tests/commands/test_bump_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from commitizen import cmd, defaults, git, hooks
from commitizen.config.base_config import BaseConfig
from commitizen.exceptions import (
BumpCommitFailedError,
BumpTagFailedError,
CommitizenException,
CurrentVersionNotFoundError,
Expand Down Expand Up @@ -1375,6 +1376,76 @@ def test_bump_warn_but_dont_fail_on_invalid_tags(
assert git.tag_exist("0.4.3") is True


def test_bump_skips_commit_when_no_files_changed(
mocker: MockFixture, tmp_commitizen_project: Path, util: UtilFixture
):
"""Regression test for #1530: with ``version_provider = "scm"`` and no
``version_files`` / ``--changelog``, ``cz bump`` should not fail with
``nothing to commit, working tree clean`` -- it should just create the
tag on ``HEAD``.
"""
project_root = tmp_commitizen_project
tmp_commitizen_cfg_file = project_root / "pyproject.toml"
tmp_commitizen_cfg_file.write_text(
"\n".join(
[
"[tool.commitizen]",
'version_provider = "scm"',
'tag_format = "v$version"',
]
),
)
util.create_file_and_commit("feat: first feature")
add_mock = mocker.patch("commitizen.git.add")

util.run_cli("bump", "--yes")

assert git.tag_exist("v0.1.0") is True

util.create_file_and_commit("fix: second change")

util.run_cli("bump", "--yes")

assert git.tag_exist("v0.1.1") is True
add_mock.assert_not_called()


def test_bump_raises_when_git_add_fails(
mocker: MockFixture, tmp_commitizen_project: Path, util: UtilFixture
):
"""Regression test: ``cz bump`` reports a failing ``git.add``."""
project_root = tmp_commitizen_project
tmp_commitizen_cfg_file = project_root / "pyproject.toml"
tmp_commitizen_cfg_file.write_text(
"\n".join(
[
"[tool.commitizen]",
'version = "0.1.0"',
'tag_format = "v$version"',
'version_files = ["pyproject.toml:version"]',
]
),
)
util.create_file_and_commit("feat: first feature")

mocker.patch(
"commitizen.git.add",
return_value=cmd.Command(
out="",
err="fatal: pathspec did not match any files",
stdout=b"",
stderr=b"fatal: pathspec did not match any files",
return_code=128,
),
)

with pytest.raises(BumpCommitFailedError) as exc_info:
util.run_cli("bump", "--yes")

assert "git.add error" in str(exc_info.value)
assert "fatal: pathspec" in str(exc_info.value)


def test_is_initial_tag(mocker: MockFixture, tmp_commitizen_project, util: UtilFixture):
"""Test the _is_initial_tag method behavior."""
# Create a commit but no tags
Expand Down
Loading