diff --git a/commitizen/bump.py b/commitizen/bump.py index 7b621a6e1c..766ce1fbd2 100644 --- a/commitizen/bump.py +++ b/commitizen/bump.py @@ -45,7 +45,9 @@ def find_increment( return increment -def prerelease_generator(current_version: str, prerelease: Optional[str] = None) -> str: +def prerelease_generator( + current_version: str, prerelease: Optional[str] = None, offset: int = 0 +) -> str: """Generate prerelease X.YaN # Alpha release @@ -65,7 +67,7 @@ def prerelease_generator(current_version: str, prerelease: Optional[str] = None) prev_prerelease: int = version.pre[1] new_prerelease_number = prev_prerelease + 1 else: - new_prerelease_number = 0 + new_prerelease_number = offset pre_version = f"{prerelease}{new_prerelease_number}" return pre_version @@ -115,6 +117,7 @@ def generate_version( current_version: str, increment: str, prerelease: Optional[str] = None, + prerelease_offset: int = 0, devrelease: Optional[int] = None, is_local_version: bool = False, ) -> Version: @@ -132,13 +135,17 @@ def generate_version( if is_local_version: version = Version(current_version) dev_version = devrelease_generator(devrelease=devrelease) - pre_version = prerelease_generator(str(version.local), prerelease=prerelease) + pre_version = prerelease_generator( + str(version.local), prerelease=prerelease, offset=prerelease_offset + ) semver = semver_generator(str(version.local), increment=increment) return Version(f"{version.public}+{semver}{pre_version}{dev_version}") else: dev_version = devrelease_generator(devrelease=devrelease) - pre_version = prerelease_generator(current_version, prerelease=prerelease) + pre_version = prerelease_generator( + current_version, prerelease=prerelease, offset=prerelease_offset + ) semver = semver_generator(current_version, increment=increment) # TODO: post version diff --git a/commitizen/cli.py b/commitizen/cli.py index bbad4e8b61..28f7e8d927 100644 --- a/commitizen/cli.py +++ b/commitizen/cli.py @@ -189,6 +189,12 @@ "default": None, "help": "keep major version at zero, even for breaking changes", }, + { + "name": ["--prerelease-offset"], + "type": int, + "default": None, + "help": "start pre-releases with this offset", + }, { "name": "manual_version", "type": str, diff --git a/commitizen/commands/bump.py b/commitizen/commands/bump.py index 918dfaa002..808141c32f 100644 --- a/commitizen/commands/bump.py +++ b/commitizen/commands/bump.py @@ -46,6 +46,7 @@ def __init__(self, config: BaseConfig, arguments: dict): "gpg_sign", "annotated_tag", "major_version_zero", + "prerelease_offset", ] if arguments[key] is not None }, @@ -105,6 +106,7 @@ def __call__(self): # noqa: C901 bump_commit_message: str = self.bump_settings["bump_message"] version_files: List[str] = self.bump_settings["version_files"] major_version_zero: bool = self.bump_settings["major_version_zero"] + prerelease_offset: int = self.bump_settings["prerelease_offset"] dry_run: bool = self.arguments["dry_run"] is_yes: bool = self.arguments["yes"] @@ -135,6 +137,11 @@ def __call__(self): # noqa: C901 "--major-version-zero cannot be combined with MANUAL_VERSION" ) + if prerelease_offset: + raise NotAllowed( + "--prerelease-offset cannot be combined with MANUAL_VERSION" + ) + if major_version_zero: if not current_version.startswith("0."): raise NotAllowed( @@ -198,6 +205,7 @@ def __call__(self): # noqa: C901 current_version, increment, prerelease=prerelease, + prerelease_offset=prerelease_offset, devrelease=devrelease, is_local_version=is_local_version, ) diff --git a/commitizen/defaults.py b/commitizen/defaults.py index b18c5ac75c..f2447483e9 100644 --- a/commitizen/defaults.py +++ b/commitizen/defaults.py @@ -42,6 +42,7 @@ class Settings(TypedDict, total=False): major_version_zero: bool pre_bump_hooks: Optional[List[str]] post_bump_hooks: Optional[List[str]] + prerelease_offset: int name: str = "cz_conventional_commits" @@ -69,6 +70,7 @@ class Settings(TypedDict, total=False): "major_version_zero": False, "pre_bump_hooks": [], "post_bump_hooks": [], + "prerelease_offset": 0, } MAJOR = "MAJOR" diff --git a/docs/bump.md b/docs/bump.md index aa9839fc76..ca5bdf7fe2 100644 --- a/docs/bump.md +++ b/docs/bump.md @@ -95,6 +95,7 @@ options: Output changelog to the stdout --retry retry commit if it fails the 1st time --major-version-zero keep major version at zero, even for breaking changes + --prerelease-offset start pre-releases with this offset ``` ### `--files-only` @@ -304,7 +305,7 @@ The variables must be preceded by a `$` sign. Supported variables: | Variable | Description | -| ------------- | --------------------------------------------| +| ------------- | ------------------------------------------- | | `$version` | full generated version | | `$major` | MAJOR increment | | `$minor` | MINOR increment | @@ -464,6 +465,15 @@ release. During execution of the script, some environment variables are availabl post_bump_hooks = [ "scripts/slack_notification.sh" ] +### `prerelease_offset` + +Offset with which to start counting prereleses. + +Defaults to: `0` + +```toml +[tool.commitizen] +prerelease_offset = 1 ``` ## Custom bump diff --git a/docs/config.md b/docs/config.md index c6ee969be5..c6cf12b02f 100644 --- a/docs/config.md +++ b/docs/config.md @@ -2,24 +2,25 @@ ## Settings -| Variable | Type | Default | Description | -| -------------------------- | ------ | --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `name` | `str` | `"cz_conventional_commits"` | Name of the committing rules to use | -| `version` | `str` | `None` | Current version. Example: "0.1.2" | -| `version_files` | `list` | `[ ]` | Files were the version will be updated. A pattern to match a line, can also be specified, separated by `:` [See more][version_files] | -| `tag_format` | `str` | `None` | Format for the git tag, useful for old projects, that use a convention like `"v1.2.1"`. [See more][tag_format] | -| `update_changelog_on_bump` | `bool` | `false` | Create changelog when running `cz bump` | -| `gpg_sign` | `bool` | `false` | Use gpg signed tags instead of lightweight tags. | -| `annotated_tag` | `bool` | `false` | Use annotated tags instead of lightweight tags. [See difference][annotated-tags-vs-lightweight] | -| `bump_message` | `str` | `None` | Create custom commit message, useful to skip ci. [See more][bump_message] | -| `allow_abort` | `bool` | `false` | Disallow empty commit messages, useful in ci. [See more][allow_abort] | -| `changelog_file` | `str` | `CHANGELOG.md` | filename of exported changelog | -| `changelog_incremental` | `bool` | `false` | Update changelog with the missing versions. This is good if you don't want to replace previous versions in the file. Note: when doing `cz bump --changelog` this is automatically set to `true` | -| `changelog_start_rev` | `str` | `None` | Start from a given git rev to generate the changelog | -| `style` | `list` | see above | Style for the prompts (It will merge this value with default style.) [See More (Styling your prompts with your favorite colors)][additional-features] | -| `customize` | `dict` | `None` | **This is only supported when config through `toml`.** Custom rules for committing and bumping. [See more][customization] | -| `use_shortcuts` | `bool` | `false` | If enabled, commitizen will show keyboard shortcuts when selecting from a list. Define a `key` for each of your choices to set the key. [See more][shortcuts] | -| `major_version_zero` | `bool` | `false` | When true, breaking changes on a `0.x` will remain as a `0.x` version. On `false`, a breaking change will bump a `0.x` version to `1.0`. [major-version-zero] | +| Variable | Type | Default | Description | +| -------------------------- | ------ | --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `name` | `str` | `"cz_conventional_commits"` | Name of the committing rules to use | +| `version` | `str` | `None` | Current version. Example: "0.1.2" | +| `version_files` | `list` | `[ ]` | Files were the version will be updated. A pattern to match a line, can also be specified, separated by `:` [See more][version_files] | +| `tag_format` | `str` | `None` | Format for the git tag, useful for old projects, that use a convention like `"v1.2.1"`. [See more][tag_format] | +| `update_changelog_on_bump` | `bool` | `false` | Create changelog when running `cz bump` | +| `gpg_sign` | `bool` | `false` | Use gpg signed tags instead of lightweight tags. | +| `annotated_tag` | `bool` | `false` | Use annotated tags instead of lightweight tags. [See difference][annotated-tags-vs-lightweight] | +| `bump_message` | `str` | `None` | Create custom commit message, useful to skip ci. [See more][bump_message] | +| `allow_abort` | `bool` | `false` | Disallow empty commit messages, useful in ci. [See more][allow_abort] | +| `changelog_file` | `str` | `CHANGELOG.md` | filename of exported changelog | +| `changelog_incremental` | `bool` | `false` | Update changelog with the missing versions. This is good if you don't want to replace previous versions in the file. Note: when doing `cz bump --changelog` this is automatically set to `true` | +| `changelog_start_rev` | `str` | `None` | Start from a given git rev to generate the changelog | +| `style` | `list` | see above | Style for the prompts (It will merge this value with default style.) [See More (Styling your prompts with your favorite colors)][additional-features] | +| `customize` | `dict` | `None` | **This is only supported when config through `toml`.** Custom rules for committing and bumping. [See more][customization] | +| `use_shortcuts` | `bool` | `false` | If enabled, commitizen will show keyboard shortcuts when selecting from a list. Define a `key` for each of your choices to set the key. [See more][shortcuts] | +| `major_version_zero` | `bool` | `false` | When true, breaking changes on a `0.x` will remain as a `0.x` version. On `false`, a breaking change will bump a `0.x` version to `1.0`. [major-version-zero] | +| `prerelease_offset` | `int` | `0` | In special cases it may be necessary that a prerelease cannot start with a 0, e.g. in an embedded project the individual characters are encoded in bytes. This can be done by specifying an offset from which to start counting. [prerelease-offset] | ## pyproject.toml or .cz.toml @@ -115,6 +116,7 @@ commitizen: [tag_format]: bump.md#tag_format [bump_message]: bump.md#bump_message [major-version-zero]: bump.md#-major-version-zero +[prerelease-offset]: bump.md#-prerelease_offset [allow_abort]: check.md#allow-abort [additional-features]: https://github.com/tmbo/questionary#additional-features [customization]: customization.md diff --git a/tests/commands/test_bump_command.py b/tests/commands/test_bump_command.py index a9854a4b32..7b5a44b745 100644 --- a/tests/commands/test_bump_command.py +++ b/tests/commands/test_bump_command.py @@ -814,3 +814,20 @@ def test_bump_with_pre_bump_hooks( ), ] ) + + +@pytest.mark.usefixtures("tmp_commitizen_project") +def test_bump_manual_version_disallows_prerelease_offset(mocker): + create_file_and_commit("feat: new file") + + manual_version = "0.2.0" + testargs = ["cz", "bump", "--yes", "--prerelease-offset", "42", manual_version] + mocker.patch.object(sys, "argv", testargs) + + with pytest.raises(NotAllowed) as excinfo: + cli.main() + + expected_error_message = ( + "--prerelease-offset cannot be combined with MANUAL_VERSION" + ) + assert expected_error_message in str(excinfo.value) diff --git a/tests/test_bump_find_version.py b/tests/test_bump_find_version.py index d0ca58c0d9..f12b87f1eb 100644 --- a/tests/test_bump_find_version.py +++ b/tests/test_bump_find_version.py @@ -6,72 +6,78 @@ from commitizen.bump import generate_version simple_flow = [ - (("0.1.0", "PATCH", None, None), "0.1.1"), - (("0.1.0", "PATCH", None, 1), "0.1.1.dev1"), - (("0.1.1", "MINOR", None, None), "0.2.0"), - (("0.2.0", "MINOR", None, None), "0.3.0"), - (("0.2.0", "MINOR", None, 1), "0.3.0.dev1"), - (("0.3.0", "PATCH", None, None), "0.3.1"), - (("0.3.0", "PATCH", "alpha", None), "0.3.1a0"), - (("0.3.1a0", None, "alpha", None), "0.3.1a1"), - (("0.3.1a0", None, None, None), "0.3.1"), - (("0.3.1", "PATCH", None, None), "0.3.2"), - (("0.4.2", "MAJOR", "alpha", None), "1.0.0a0"), - (("1.0.0a0", None, "alpha", None), "1.0.0a1"), - (("1.0.0a1", None, "alpha", None), "1.0.0a2"), - (("1.0.0a1", None, "alpha", 1), "1.0.0a2.dev1"), - (("1.0.0a2.dev0", None, "alpha", 1), "1.0.0a3.dev1"), - (("1.0.0a2.dev0", None, "alpha", 0), "1.0.0a3.dev0"), - (("1.0.0a1", None, "beta", None), "1.0.0b0"), - (("1.0.0b0", None, "beta", None), "1.0.0b1"), - (("1.0.0b1", None, "rc", None), "1.0.0rc0"), - (("1.0.0rc0", None, "rc", None), "1.0.0rc1"), - (("1.0.0rc0", None, "rc", 1), "1.0.0rc1.dev1"), - (("1.0.0rc0", "PATCH", None, None), "1.0.0"), - (("1.0.0a3.dev0", None, "beta", None), "1.0.0b0"), - (("1.0.0", "PATCH", None, None), "1.0.1"), - (("1.0.1", "PATCH", None, None), "1.0.2"), - (("1.0.2", "MINOR", None, None), "1.1.0"), - (("1.1.0", "MINOR", None, None), "1.2.0"), - (("1.2.0", "PATCH", None, None), "1.2.1"), - (("1.2.1", "MAJOR", None, None), "2.0.0"), + (("0.1.0", "PATCH", None, 0, None), "0.1.1"), + (("0.1.0", "PATCH", None, 0, 1), "0.1.1.dev1"), + (("0.1.1", "MINOR", None, 0, None), "0.2.0"), + (("0.2.0", "MINOR", None, 0, None), "0.3.0"), + (("0.2.0", "MINOR", None, 0, 1), "0.3.0.dev1"), + (("0.3.0", "PATCH", None, 0, None), "0.3.1"), + (("0.3.0", "PATCH", "alpha", 0, None), "0.3.1a0"), + (("0.3.1a0", None, "alpha", 0, None), "0.3.1a1"), + (("0.3.0", "PATCH", "alpha", 1, None), "0.3.1a1"), + (("0.3.1a0", None, "alpha", 1, None), "0.3.1a1"), + (("0.3.1a0", None, None, 0, None), "0.3.1"), + (("0.3.1", "PATCH", None, 0, None), "0.3.2"), + (("0.4.2", "MAJOR", "alpha", 0, None), "1.0.0a0"), + (("1.0.0a0", None, "alpha", 0, None), "1.0.0a1"), + (("1.0.0a1", None, "alpha", 0, None), "1.0.0a2"), + (("1.0.0a1", None, "alpha", 0, 1), "1.0.0a2.dev1"), + (("1.0.0a2.dev0", None, "alpha", 0, 1), "1.0.0a3.dev1"), + (("1.0.0a2.dev0", None, "alpha", 0, 0), "1.0.0a3.dev0"), + (("1.0.0a1", None, "beta", 0, None), "1.0.0b0"), + (("1.0.0b0", None, "beta", 0, None), "1.0.0b1"), + (("1.0.0b1", None, "rc", 0, None), "1.0.0rc0"), + (("1.0.0rc0", None, "rc", 0, None), "1.0.0rc1"), + (("1.0.0rc0", None, "rc", 0, 1), "1.0.0rc1.dev1"), + (("1.0.0rc0", "PATCH", None, 0, None), "1.0.0"), + (("1.0.0a3.dev0", None, "beta", 0, None), "1.0.0b0"), + (("1.0.0", "PATCH", None, 0, None), "1.0.1"), + (("1.0.1", "PATCH", None, 0, None), "1.0.2"), + (("1.0.2", "MINOR", None, 0, None), "1.1.0"), + (("1.1.0", "MINOR", None, 0, None), "1.2.0"), + (("1.2.0", "PATCH", None, 0, None), "1.2.1"), + (("1.2.1", "MAJOR", None, 0, None), "2.0.0"), ] local_versions = [ - (("4.5.0+0.1.0", "PATCH", None, None), "4.5.0+0.1.1"), - (("4.5.0+0.1.1", "MINOR", None, None), "4.5.0+0.2.0"), - (("4.5.0+0.2.0", "MAJOR", None, None), "4.5.0+1.0.0"), + (("4.5.0+0.1.0", "PATCH", None, 0, None), "4.5.0+0.1.1"), + (("4.5.0+0.1.1", "MINOR", None, 0, None), "4.5.0+0.2.0"), + (("4.5.0+0.2.0", "MAJOR", None, 0, None), "4.5.0+1.0.0"), ] # this cases should be handled gracefully unexpected_cases = [ - (("0.1.1rc0", None, "alpha", None), "0.1.1a0"), - (("0.1.1b1", None, "alpha", None), "0.1.1a0"), + (("0.1.1rc0", None, "alpha", 0, None), "0.1.1a0"), + (("0.1.1b1", None, "alpha", 0, None), "0.1.1a0"), ] weird_cases = [ - (("1.1", "PATCH", None, None), "1.1.1"), - (("1", "MINOR", None, None), "1.1.0"), - (("1", "MAJOR", None, None), "2.0.0"), - (("1a0", None, "alpha", None), "1.0.0a1"), - (("1", None, "beta", None), "1.0.0b0"), - (("1beta", None, "beta", None), "1.0.0b1"), - (("1.0.0alpha1", None, "alpha", None), "1.0.0a2"), - (("1", None, "rc", None), "1.0.0rc0"), - (("1.0.0rc1+e20d7b57f3eb", "PATCH", None, None), "1.0.0"), + (("1.1", "PATCH", None, 0, None), "1.1.1"), + (("1", "MINOR", None, 0, None), "1.1.0"), + (("1", "MAJOR", None, 0, None), "2.0.0"), + (("1a0", None, "alpha", 0, None), "1.0.0a1"), + (("1a0", None, "alpha", 1, None), "1.0.0a1"), + (("1", None, "beta", 0, None), "1.0.0b0"), + (("1", None, "beta", 1, None), "1.0.0b1"), + (("1beta", None, "beta", 0, None), "1.0.0b1"), + (("1.0.0alpha1", None, "alpha", 0, None), "1.0.0a2"), + (("1", None, "rc", 0, None), "1.0.0rc0"), + (("1.0.0rc1+e20d7b57f3eb", "PATCH", None, 0, None), "1.0.0"), ] # test driven development tdd_cases = [ - (("0.1.1", "PATCH", None, None), "0.1.2"), - (("0.1.1", "MINOR", None, None), "0.2.0"), - (("2.1.1", "MAJOR", None, None), "3.0.0"), - (("0.9.0", "PATCH", "alpha", None), "0.9.1a0"), - (("0.9.0", "MINOR", "alpha", None), "0.10.0a0"), - (("0.9.0", "MAJOR", "alpha", None), "1.0.0a0"), - (("1.0.0a2", None, "beta", None), "1.0.0b0"), - (("1.0.0beta1", None, "rc", None), "1.0.0rc0"), - (("1.0.0rc1", None, "rc", None), "1.0.0rc2"), + (("0.1.1", "PATCH", None, 0, None), "0.1.2"), + (("0.1.1", "MINOR", None, 0, None), "0.2.0"), + (("2.1.1", "MAJOR", None, 0, None), "3.0.0"), + (("0.9.0", "PATCH", "alpha", 0, None), "0.9.1a0"), + (("0.9.0", "MINOR", "alpha", 0, None), "0.10.0a0"), + (("0.9.0", "MAJOR", "alpha", 0, None), "1.0.0a0"), + (("0.9.0", "MAJOR", "alpha", 1, None), "1.0.0a1"), + (("1.0.0a2", None, "beta", 0, None), "1.0.0b0"), + (("1.0.0a2", None, "beta", 1, None), "1.0.0b1"), + (("1.0.0beta1", None, "rc", 0, None), "1.0.0rc0"), + (("1.0.0rc1", None, "rc", 0, None), "1.0.0rc2"), ] @@ -83,12 +89,14 @@ def test_generate_version(test_input, expected): current_version = test_input[0] increment = test_input[1] prerelease = test_input[2] - devrelease = test_input[3] + prerelease_offset = test_input[3] + devrelease = test_input[4] assert ( generate_version( current_version, increment=increment, prerelease=prerelease, + prerelease_offset=prerelease_offset, devrelease=devrelease, ) == Version(expected) @@ -103,13 +111,15 @@ def test_generate_version_local(test_input, expected): current_version = test_input[0] increment = test_input[1] prerelease = test_input[2] - devrelease = test_input[3] + prerelease_offset = test_input[3] + devrelease = test_input[4] is_local_version = True assert ( generate_version( current_version, increment=increment, prerelease=prerelease, + prerelease_offset=prerelease_offset, devrelease=devrelease, is_local_version=is_local_version, ) diff --git a/tests/test_conf.py b/tests/test_conf.py index 65453b8269..d39de8a048 100644 --- a/tests/test_conf.py +++ b/tests/test_conf.py @@ -57,6 +57,7 @@ "major_version_zero": False, "pre_bump_hooks": ["scripts/generate_documentation.sh"], "post_bump_hooks": ["scripts/slack_notification.sh"], + "prerelease_offset": 0, } _new_settings = { @@ -75,6 +76,7 @@ "major_version_zero": False, "pre_bump_hooks": ["scripts/generate_documentation.sh"], "post_bump_hooks": ["scripts/slack_notification.sh"], + "prerelease_offset": 0, } _read_settings = {