diff --git a/commitizen/bump.py b/commitizen/bump.py index e4120eb5b1..ccb1257e7d 100644 --- a/commitizen/bump.py +++ b/commitizen/bump.py @@ -108,7 +108,10 @@ def semver_generator(current_version: str, increment: str = None) -> str: def generate_version( - current_version: str, increment: str, prerelease: Optional[str] = None + current_version: str, + increment: str, + prerelease: Optional[str] = None, + is_local_version: bool = False, ) -> Version: """Based on the given increment a proper semver will be generated. @@ -121,11 +124,19 @@ def generate_version( MINOR 1.0.0 -> 1.1.0 MAJOR 1.0.0 -> 2.0.0 """ - pre_version = prerelease_generator(current_version, prerelease=prerelease) - semver = semver_generator(current_version, increment=increment) - # TODO: post version - # TODO: dev version - return Version(f"{semver}{pre_version}") + if is_local_version: + version = Version(current_version) + pre_version = prerelease_generator(str(version.local), prerelease=prerelease) + semver = semver_generator(str(version.local), increment=increment) + + return Version(f"{version.public}+{semver}{pre_version}") + else: + pre_version = prerelease_generator(current_version, prerelease=prerelease) + semver = semver_generator(current_version, increment=increment) + + # TODO: post version + # TODO: dev version + return Version(f"{semver}{pre_version}") def update_version_in_files( @@ -188,7 +199,7 @@ def create_tag(version: Union[Version, str], tag_format: Optional[str] = None): version = Version(version) if not tag_format: - return version.public + return str(version) major, minor, patch = version.release prerelease = "" diff --git a/commitizen/cli.py b/commitizen/cli.py index d09ba76d20..2f847ef8cd 100644 --- a/commitizen/cli.py +++ b/commitizen/cli.py @@ -81,6 +81,11 @@ "action": "store_true", "help": "bump version in the files from the config", }, + { + "name": "--local-version", + "action": "store_true", + "help": "bump only the local version portion", + }, { "name": ["--changelog", "-ch"], "action": "store_true", diff --git a/commitizen/commands/bump.py b/commitizen/commands/bump.py index 6a57fdb867..6a723daf1e 100644 --- a/commitizen/commands/bump.py +++ b/commitizen/commands/bump.py @@ -92,6 +92,7 @@ def __call__(self): # noqa: C901 increment: Optional[str] = self.arguments["increment"] prerelease: str = self.arguments["prerelease"] is_files_only: Optional[bool] = self.arguments["files_only"] + is_local_version: Optional[bool] = self.arguments["local_version"] current_tag_version: str = bump.create_tag( current_version, tag_format=tag_format @@ -117,7 +118,10 @@ def __call__(self): # noqa: C901 increment = None new_version = bump.generate_version( - current_version, increment, prerelease=prerelease + current_version, + increment, + prerelease=prerelease, + is_local_version=is_local_version, ) new_tag_version = bump.create_tag(new_version, tag_format=tag_format) message = bump.create_commit_message( @@ -140,7 +144,7 @@ def __call__(self): # noqa: C901 bump.update_version_in_files( current_version, - new_version.public, + str(new_version), version_files, check_consistency=self.check_consistency, ) @@ -159,7 +163,7 @@ def __call__(self): # noqa: C901 changelog_cmd() c = cmd.run(f"git add {changelog_cmd.file_name}") - self.config.set_key("version", new_version.public) + self.config.set_key("version", str(new_version)) c = git.commit(message, args=self._get_commit_args()) if c.return_code != 0: raise BumpCommitFailedError(f'git.commit error: "{c.err.strip()}"') diff --git a/docs/bump.md b/docs/bump.md index 3ff2d6a286..25a2c10352 100644 --- a/docs/bump.md +++ b/docs/bump.md @@ -54,7 +54,7 @@ Some examples: ```bash $ cz bump --help -usage: cz bump [-h] [--dry-run] [--files-only] [--changelog] [--no-verify] +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] @@ -67,6 +67,7 @@ optional arguments: --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 @@ -131,6 +132,24 @@ However, it will still update `pyproject.toml` and `src/__version__.py`. To fix it, you'll first `git checkout .` to reset to the status before trying to bump and update the version in `setup.py` to `1.21.0` + +### `--local-version` + +Bump the local portion of the version. + +```bash +cz bump --local-version +``` + +For example, if we have `pyproject.toml` + +```toml +[tool.commitizen] +version = "5.3.5+0.1.0" +``` + +If `--local-version` is used, it will bump only the local version `0.1.0` and keep the public version `5.3.5` intact, bumping to the version `5.3.5+0.2.0`. + ## Configuration ### `tag_format` diff --git a/tests/commands/test_bump_command.py b/tests/commands/test_bump_command.py index ce19c136bc..6818ddda09 100644 --- a/tests/commands/test_bump_command.py +++ b/tests/commands/test_bump_command.py @@ -243,6 +243,27 @@ def test_bump_files_only(mocker, tmp_commitizen_project): assert "0.3.0" in f.read() +def test_bump_local_version(mocker, tmp_commitizen_project): + tmp_version_file = tmp_commitizen_project.join("__version__.py") + tmp_version_file.write("4.5.1+0.1.0") + tmp_commitizen_cfg_file = tmp_commitizen_project.join("pyproject.toml") + tmp_commitizen_cfg_file.write( + f"[tool.commitizen]\n" + 'version="4.5.1+0.1.0"\n' + f'version_files = ["{str(tmp_version_file)}"]' + ) + + create_file_and_commit("feat: new user interface") + testargs = ["cz", "bump", "--yes", "--local-version"] + mocker.patch.object(sys, "argv", testargs) + cli.main() + tag_exists = git.tag_exist("4.5.1+0.2.0") + assert tag_exists is True + + with open(tmp_version_file, "r") as f: + assert "4.5.1+0.2.0" in f.read() + + def test_bump_dry_run(mocker, capsys, tmp_commitizen_project): create_file_and_commit("feat: new file") diff --git a/tests/test_bump_create_tag.py b/tests/test_bump_create_tag.py index b6e06bcf51..0d6ee60d69 100644 --- a/tests/test_bump_create_tag.py +++ b/tests/test_bump_create_tag.py @@ -10,6 +10,9 @@ (("1.2.3", "ver$major.$minor.$patch"), "ver1.2.3"), (("1.2.3a0", "ver$major.$minor.$patch.$prerelease"), "ver1.2.3.a0"), (("1.2.3rc2", "$major.$minor.$patch.$prerelease-majestic"), "1.2.3.rc2-majestic"), + (("1.2.3+1.0.0", "v$version"), "v1.2.3+1.0.0"), + (("1.2.3+1.0.0", "v$version-local"), "v1.2.3+1.0.0-local"), + (("1.2.3+1.0.0", "ver$major.$minor.$patch"), "ver1.2.3"), ] diff --git a/tests/test_bump_find_version.py b/tests/test_bump_find_version.py index ca1a6b57d5..1436d9bd1e 100644 --- a/tests/test_bump_find_version.py +++ b/tests/test_bump_find_version.py @@ -30,6 +30,12 @@ (("1.2.1", "MAJOR", None), "2.0.0"), ] +local_versions = [ + (("4.5.0+0.1.0", "PATCH", None), "4.5.0+0.1.1"), + (("4.5.0+0.1.1", "MINOR", None), "4.5.0+0.2.0"), + (("4.5.0+0.2.0", "MAJOR", None), "4.5.0+1.0.0"), +] + # this cases should be handled gracefully unexpected_cases = [ (("0.1.1rc0", None, "alpha"), "0.1.1a0"), @@ -73,3 +79,19 @@ def test_generate_version(test_input, expected): assert generate_version( current_version, increment=increment, prerelease=prerelease ) == Version(expected) + + +@pytest.mark.parametrize( + "test_input,expected", itertools.chain(local_versions), +) +def test_generate_version_local(test_input, expected): + current_version = test_input[0] + increment = test_input[1] + prerelease = test_input[2] + is_local_version = True + assert generate_version( + current_version, + increment=increment, + prerelease=prerelease, + is_local_version=is_local_version, + ) == Version(expected)