Skip to content

Commit 8dcfac0

Browse files
authored
Merge pull request #591 from jakob-keller/bump/manual-version
Bump: support manual version
2 parents 4f4fb4d + 2ba0b73 commit 8dcfac0

File tree

5 files changed

+147
-40
lines changed

5 files changed

+147
-40
lines changed

Diff for: commitizen/cli.py

+7
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,13 @@
183183
"default": False,
184184
"help": "retry commit if it fails the 1st time",
185185
},
186+
{
187+
"name": "manual_version",
188+
"type": str,
189+
"nargs": "?",
190+
"help": "bump to the given version (e.g: 1.5.3)",
191+
"metavar": "MANUAL_VERSION",
192+
},
186193
],
187194
},
188195
{

Diff for: commitizen/commands/bump.py

+60-35
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from typing import List, Optional
33

44
import questionary
5-
from packaging.version import Version
5+
from packaging.version import InvalidVersion, Version
66

77
from commitizen import bump, cmd, factory, git, out
88
from commitizen.commands.changelog import Changelog
@@ -12,10 +12,12 @@
1212
BumpTagFailedError,
1313
DryRunExit,
1414
ExpectedExit,
15+
InvalidManualVersion,
1516
NoCommitsFoundError,
1617
NoneIncrementExit,
1718
NoPatternMapError,
1819
NotAGitProjectError,
20+
NotAllowed,
1921
NoVersionSpecifiedError,
2022
)
2123

@@ -102,10 +104,26 @@ def __call__(self): # noqa: C901
102104
dry_run: bool = self.arguments["dry_run"]
103105
is_yes: bool = self.arguments["yes"]
104106
increment: Optional[str] = self.arguments["increment"]
105-
prerelease: str = self.arguments["prerelease"]
107+
prerelease: Optional[str] = self.arguments["prerelease"]
106108
devrelease: Optional[int] = self.arguments["devrelease"]
107109
is_files_only: Optional[bool] = self.arguments["files_only"]
108110
is_local_version: Optional[bool] = self.arguments["local_version"]
111+
manual_version = self.arguments["manual_version"]
112+
113+
if manual_version:
114+
if increment:
115+
raise NotAllowed("--increment cannot be combined with MANUAL_VERSION")
116+
117+
if prerelease:
118+
raise NotAllowed("--prerelease cannot be combined with MANUAL_VERSION")
119+
120+
if devrelease is not None:
121+
raise NotAllowed("--devrelease cannot be combined with MANUAL_VERSION")
122+
123+
if is_local_version:
124+
raise NotAllowed(
125+
"--local-version cannot be combined with MANUAL_VERSION"
126+
)
109127

110128
current_tag_version: str = bump.normalize_tag(
111129
current_version, tag_format=tag_format
@@ -127,46 +145,53 @@ def __call__(self): # noqa: C901
127145
if not commits and not current_version_instance.is_prerelease:
128146
raise NoCommitsFoundError("[NO_COMMITS_FOUND]\n" "No new commits found.")
129147

130-
if increment is None:
131-
increment = self.find_increment(commits)
132-
133-
# It may happen that there are commits, but they are not elegible
134-
# for an increment, this generates a problem when using prerelease (#281)
135-
if (
136-
prerelease
137-
and increment is None
138-
and not current_version_instance.is_prerelease
139-
):
140-
raise NoCommitsFoundError(
141-
"[NO_COMMITS_FOUND]\n"
142-
"No commits found to generate a pre-release.\n"
143-
"To avoid this error, manually specify the type of increment with `--increment`"
144-
)
145-
146-
# Increment is removed when current and next version
147-
# are expected to be prereleases.
148-
if prerelease and current_version_instance.is_prerelease:
149-
increment = None
148+
if manual_version:
149+
try:
150+
new_version = Version(manual_version)
151+
except InvalidVersion as exc:
152+
raise InvalidManualVersion(
153+
"[INVALID_MANUAL_VERSION]\n"
154+
f"Invalid manual version: '{manual_version}'"
155+
) from exc
156+
else:
157+
if increment is None:
158+
increment = self.find_increment(commits)
159+
160+
# It may happen that there are commits, but they are not eligible
161+
# for an increment, this generates a problem when using prerelease (#281)
162+
if (
163+
prerelease
164+
and increment is None
165+
and not current_version_instance.is_prerelease
166+
):
167+
raise NoCommitsFoundError(
168+
"[NO_COMMITS_FOUND]\n"
169+
"No commits found to generate a pre-release.\n"
170+
"To avoid this error, manually specify the type of increment with `--increment`"
171+
)
150172

151-
new_version = bump.generate_version(
152-
current_version,
153-
increment,
154-
prerelease=prerelease,
155-
devrelease=devrelease,
156-
is_local_version=is_local_version,
157-
)
173+
# Increment is removed when current and next version
174+
# are expected to be prereleases.
175+
if prerelease and current_version_instance.is_prerelease:
176+
increment = None
177+
178+
new_version = bump.generate_version(
179+
current_version,
180+
increment,
181+
prerelease=prerelease,
182+
devrelease=devrelease,
183+
is_local_version=is_local_version,
184+
)
158185

159186
new_tag_version = bump.normalize_tag(new_version, tag_format=tag_format)
160187
message = bump.create_commit_message(
161188
current_version, new_version, bump_commit_message
162189
)
163190

164191
# Report found information
165-
information = (
166-
f"{message}\n"
167-
f"tag to create: {new_tag_version}\n"
168-
f"increment detected: {increment}\n"
169-
)
192+
information = f"{message}\n" f"tag to create: {new_tag_version}\n"
193+
if increment:
194+
information += f"increment detected: {increment}\n"
170195

171196
if self.changelog_to_stdout:
172197
# When the changelog goes to stdout, we want to send
@@ -179,7 +204,7 @@ def __call__(self): # noqa: C901
179204
if increment is None and new_tag_version == current_tag_version:
180205
raise NoneIncrementExit(
181206
"[NO_COMMITS_TO_BUMP]\n"
182-
"The commits found are not elegible to be bumped"
207+
"The commits found are not eligible to be bumped"
183208
)
184209

185210
if self.changelog:

Diff for: commitizen/exceptions.py

+5
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ class ExitCode(enum.IntEnum):
2828
NO_INCREMENT = 21
2929
UNRECOGNIZED_CHARACTERSET_ENCODING = 22
3030
GIT_COMMAND_ERROR = 23
31+
INVALID_MANUAL_VERSION = 24
3132

3233

3334
class CommitizenException(Exception):
@@ -158,3 +159,7 @@ class CharacterSetDecodeError(CommitizenException):
158159

159160
class GitCommandError(CommitizenException):
160161
exit_code = ExitCode.GIT_COMMAND_ERROR
162+
163+
164+
class InvalidManualVersion(CommitizenException):
165+
exit_code = ExitCode.INVALID_MANUAL_VERSION

Diff for: docs/bump.md

+9-5
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,13 @@ Some examples:
5656
$ cz bump --help
5757
usage: cz bump [-h] [--dry-run] [--files-only] [--local-version] [--changelog]
5858
[--no-verify] [--yes] [--tag-format TAG_FORMAT]
59-
[--bump-message BUMP_MESSAGE] [--increment {MAJOR,MINOR,PATCH}]
60-
[--prerelease {alpha,beta,rc}] [--devrelease {DEV}]
59+
[--bump-message BUMP_MESSAGE] [--prerelease {alpha,beta,rc}]
60+
[--devrelease DEVRELEASE] [--increment {MAJOR,MINOR,PATCH}]
6161
[--check-consistency] [--annotated-tag] [--gpg-sign]
62-
[--changelog-to-stdout] [--retry]
62+
[--changelog-to-stdout] [--retry] [MANUAL_VERSION]
63+
64+
positional arguments:
65+
MANUAL_VERSION bump to the given version (e.g: 1.5.3)
6366

6467
options:
6568
-h, --help show this help message and exit
@@ -78,14 +81,15 @@ options:
7881
when working with CI
7982
--prerelease {alpha,beta,rc}, -pr {alpha,beta,rc}
8083
choose type of prerelease
81-
--devrelease {DEV} specify dev release
84+
--devrelease DEVRELEASE, -d DEVRELEASE
85+
specify non-negative integer for dev. release
8286
--increment {MAJOR,MINOR,PATCH}
8387
manually specify the desired increment
8488
--check-consistency, -cc
8589
check consistency among versions defined in commitizen
8690
configuration and version_files
87-
--gpg-sign, -s create a signed tag instead of lightweight one or annotated tag
8891
--annotated-tag, -at create annotated tag instead of lightweight one
92+
--gpg-sign, -s sign tag instead of lightweight one
8993
--changelog-to-stdout
9094
Output changelog to the stdout
9195
--retry retry commit if it fails the 1st time

Diff for: tests/commands/test_bump_command.py

+66
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,12 @@
1414
DryRunExit,
1515
ExitCode,
1616
ExpectedExit,
17+
InvalidManualVersion,
1718
NoCommitsFoundError,
1819
NoneIncrementExit,
1920
NoPatternMapError,
2021
NotAGitProjectError,
22+
NotAllowed,
2123
NoVersionSpecifiedError,
2224
)
2325
from tests.utils import create_file_and_commit
@@ -614,3 +616,67 @@ def test_bump_changelog_command_commits_untracked_changelog_and_version_files(
614616
commit_file_names = git.get_filenames_in_commit()
615617
assert "CHANGELOG.md" in commit_file_names
616618
assert version_filepath in commit_file_names
619+
620+
621+
@pytest.mark.parametrize(
622+
"testargs",
623+
[
624+
["cz", "bump", "--local-version", "1.2.3"],
625+
["cz", "bump", "--prerelease", "rc", "1.2.3"],
626+
["cz", "bump", "--devrelease", "0", "1.2.3"],
627+
["cz", "bump", "--devrelease", "1", "1.2.3"],
628+
["cz", "bump", "--increment", "PATCH", "1.2.3"],
629+
],
630+
)
631+
def test_bump_invalid_manual_args_raises_exception(mocker, testargs):
632+
mocker.patch.object(sys, "argv", testargs)
633+
634+
with pytest.raises(NotAllowed):
635+
cli.main()
636+
637+
638+
@pytest.mark.usefixtures("tmp_commitizen_project")
639+
@pytest.mark.parametrize(
640+
"manual_version",
641+
[
642+
"noversion",
643+
"1.2..3",
644+
],
645+
)
646+
def test_bump_invalid_manual_version_raises_exception(mocker, manual_version):
647+
create_file_and_commit("feat: new file")
648+
649+
testargs = ["cz", "bump", "--yes", manual_version]
650+
mocker.patch.object(sys, "argv", testargs)
651+
652+
with pytest.raises(InvalidManualVersion) as excinfo:
653+
cli.main()
654+
655+
expected_error_message = (
656+
"[INVALID_MANUAL_VERSION]\n" f"Invalid manual version: '{manual_version}'"
657+
)
658+
assert expected_error_message in str(excinfo.value)
659+
660+
661+
@pytest.mark.usefixtures("tmp_commitizen_project")
662+
@pytest.mark.parametrize(
663+
"manual_version",
664+
[
665+
"0.0.1",
666+
"0.1.0rc2",
667+
"0.1.0.dev2",
668+
"0.1.0+1.0.0",
669+
"0.1.0rc2.dev2+1.0.0",
670+
"0.1.1",
671+
"0.2.0",
672+
"1.0.0",
673+
],
674+
)
675+
def test_bump_manual_version(mocker, manual_version):
676+
create_file_and_commit("feat: new file")
677+
678+
testargs = ["cz", "bump", "--yes", manual_version]
679+
mocker.patch.object(sys, "argv", testargs)
680+
cli.main()
681+
tag_exists = git.tag_exist(manual_version)
682+
assert tag_exists is True

0 commit comments

Comments
 (0)