Skip to content

Commit c68a231

Browse files
fix: properly bump versions between prereleases
1 parent d1c06eb commit c68a231

File tree

3 files changed

+105
-7
lines changed

3 files changed

+105
-7
lines changed

commitizen/commands/bump.py

+49-3
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,11 @@
2323
NoVersionSpecifiedError,
2424
)
2525
from commitizen.providers import get_provider
26-
from commitizen.version_schemes import get_version_scheme, InvalidVersion
26+
from commitizen.version_schemes import (
27+
get_version_scheme,
28+
InvalidVersion,
29+
VersionProtocol,
30+
)
2731

2832
logger = getLogger("commitizen")
2933

@@ -215,15 +219,27 @@ def __call__(self): # noqa: C901
215219

216220
# Increment is removed when current and next version
217221
# are expected to be prereleases.
218-
if prerelease and current_version.is_prerelease:
219-
increment = None
222+
force_bump = False
223+
if current_version.is_prerelease:
224+
last_final = self.find_previous_final_version(current_version)
225+
if last_final is not None:
226+
commits = git.get_commits(last_final)
227+
increment = self.find_increment(commits)
228+
semver = last_final.increment_base(
229+
increment=increment, force_bump=True
230+
)
231+
if semver != current_version.base_version:
232+
force_bump = True
233+
elif prerelease:
234+
increment = None
220235

221236
new_version = current_version.bump(
222237
increment,
223238
prerelease=prerelease,
224239
prerelease_offset=prerelease_offset,
225240
devrelease=devrelease,
226241
is_local_version=is_local_version,
242+
force_bump=force_bump,
227243
)
228244

229245
new_tag_version = bump.normalize_tag(
@@ -375,3 +391,33 @@ def _get_commit_args(self):
375391
if self.no_verify:
376392
commit_args.append("--no-verify")
377393
return " ".join(commit_args)
394+
395+
def find_previous_final_version(
396+
self, current_version: VersionProtocol
397+
) -> VersionProtocol | None:
398+
tag_format: str = self.bump_settings["tag_format"]
399+
current = bump.normalize_tag(
400+
current_version,
401+
tag_format=tag_format,
402+
scheme=self.scheme,
403+
)
404+
405+
final_versions = []
406+
for tag in git.get_tag_names():
407+
assert tag
408+
try:
409+
version = self.scheme(tag)
410+
if not version.is_prerelease or tag == current:
411+
final_versions.append(version)
412+
except InvalidVersion:
413+
continue
414+
415+
if not final_versions:
416+
return None
417+
418+
final_versions = sorted(final_versions) # type: ignore [type-var]
419+
current_index = final_versions.index(current_version)
420+
previous_index = current_index - 1
421+
if previous_index < 0:
422+
return None
423+
return final_versions[previous_index]

commitizen/version_schemes.py

+20-4
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ def bump(
105105
prerelease_offset: int = 0,
106106
devrelease: int | None = None,
107107
is_local_version: bool = False,
108+
force_bump: bool = False,
108109
) -> Self:
109110
"""
110111
Based on the given increment, generate the next bumped version according to the version scheme
@@ -171,7 +172,9 @@ def generate_devrelease(self, devrelease: int | None) -> str:
171172

172173
return f"dev{devrelease}"
173174

174-
def increment_base(self, increment: str | None = None) -> str:
175+
def increment_base(
176+
self, increment: str | None = None, force_bump: bool = False
177+
) -> str:
175178
prev_release = list(self.release)
176179
increments = [MAJOR, MINOR, PATCH]
177180
base = dict(zip_longest(increments, prev_release, fillvalue=0))
@@ -180,7 +183,7 @@ def increment_base(self, increment: str | None = None) -> str:
180183
# must remove its prerelease tag,
181184
# so it doesn't matter the increment.
182185
# Example: 1.0.0a0 with PATCH/MINOR -> 1.0.0
183-
if not self.is_prerelease:
186+
if not self.is_prerelease or force_bump:
184187
if increment == MAJOR:
185188
base[MAJOR] += 1
186189
base[MINOR] = 0
@@ -200,6 +203,7 @@ def bump(
200203
prerelease_offset: int = 0,
201204
devrelease: int | None = None,
202205
is_local_version: bool = False,
206+
force_bump: bool = False,
203207
) -> Self:
204208
"""Based on the given increment a proper semver will be generated.
205209
@@ -217,9 +221,21 @@ def bump(
217221
local_version = self.scheme(self.local).bump(increment)
218222
return self.scheme(f"{self.public}+{local_version}") # type: ignore
219223
else:
220-
base = self.increment_base(increment)
224+
base = self.increment_base(increment, force_bump)
221225
dev_version = self.generate_devrelease(devrelease)
222-
pre_version = self.generate_prerelease(prerelease, offset=prerelease_offset)
226+
release = list(self.release)
227+
if len(release) < 3:
228+
release += [0] * (3 - len(release))
229+
current_semver = ".".join(str(part) for part in release)
230+
if base == current_semver:
231+
pre_version = self.generate_prerelease(
232+
prerelease, offset=prerelease_offset
233+
)
234+
else:
235+
base_version = cast(BaseVersion, self.scheme(base))
236+
pre_version = base_version.generate_prerelease(
237+
prerelease, offset=prerelease_offset
238+
)
223239
# TODO: post version
224240
return self.scheme(f"{base}{pre_version}{dev_version}") # type: ignore
225241

tests/commands/test_bump_command.py

+36
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,42 @@ def test_bump_command_prelease(mocker: MockFixture):
223223
assert tag_exists is True
224224

225225

226+
@pytest.mark.usefixtures("tmp_commitizen_project")
227+
def test_bump_command_prelease_increment(mocker: MockFixture):
228+
# FINAL RELEASE
229+
create_file_and_commit("fix: location")
230+
231+
testargs = ["cz", "bump", "--yes"]
232+
mocker.patch.object(sys, "argv", testargs)
233+
cli.main()
234+
assert git.tag_exist("0.1.1")
235+
236+
# PRERELEASE
237+
create_file_and_commit("fix: location")
238+
239+
testargs = ["cz", "bump", "--prerelease", "alpha", "--yes"]
240+
mocker.patch.object(sys, "argv", testargs)
241+
cli.main()
242+
243+
assert git.tag_exist("0.1.2a0")
244+
245+
create_file_and_commit("feat: location")
246+
247+
testargs = ["cz", "bump", "--prerelease", "alpha", "--yes"]
248+
mocker.patch.object(sys, "argv", testargs)
249+
cli.main()
250+
251+
assert git.tag_exist("0.2.0a0")
252+
253+
create_file_and_commit("feat!: breaking")
254+
255+
testargs = ["cz", "bump", "--prerelease", "alpha", "--yes"]
256+
mocker.patch.object(sys, "argv", testargs)
257+
cli.main()
258+
259+
assert git.tag_exist("1.0.0a0")
260+
261+
226262
@pytest.mark.usefixtures("tmp_commitizen_project")
227263
def test_bump_on_git_with_hooks_no_verify_disabled(mocker: MockFixture):
228264
"""Bump commit without --no-verify"""

0 commit comments

Comments
 (0)