Skip to content

Commit 8522b8c

Browse files
eduardocardosojenstroeger
authored andcommitted
fix: properly bump versions between prereleases
1 parent e9647c7 commit 8522b8c

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
@@ -24,7 +24,11 @@
2424
)
2525
from commitizen.changelog_formats import get_changelog_format
2626
from commitizen.providers import get_provider
27-
from commitizen.version_schemes import InvalidVersion, get_version_scheme
27+
from commitizen.version_schemes import (
28+
get_version_scheme,
29+
InvalidVersion,
30+
VersionProtocol,
31+
)
2832

2933
logger = getLogger("commitizen")
3034

@@ -228,15 +232,27 @@ def __call__(self): # noqa: C901
228232

229233
# Increment is removed when current and next version
230234
# are expected to be prereleases.
231-
if prerelease and current_version.is_prerelease:
232-
increment = None
235+
force_bump = False
236+
if current_version.is_prerelease:
237+
last_final = self.find_previous_final_version(current_version)
238+
if last_final is not None:
239+
commits = git.get_commits(last_final)
240+
increment = self.find_increment(commits)
241+
semver = last_final.increment_base(
242+
increment=increment, force_bump=True
243+
)
244+
if semver != current_version.base_version:
245+
force_bump = True
246+
elif prerelease:
247+
increment = None
233248

234249
new_version = current_version.bump(
235250
increment,
236251
prerelease=prerelease,
237252
prerelease_offset=prerelease_offset,
238253
devrelease=devrelease,
239254
is_local_version=is_local_version,
255+
force_bump=force_bump,
240256
)
241257

242258
new_tag_version = bump.normalize_tag(
@@ -395,3 +411,33 @@ def _get_commit_args(self):
395411
if self.no_verify:
396412
commit_args.append("--no-verify")
397413
return " ".join(commit_args)
414+
415+
def find_previous_final_version(
416+
self, current_version: VersionProtocol
417+
) -> VersionProtocol | None:
418+
tag_format: str = self.bump_settings["tag_format"]
419+
current = bump.normalize_tag(
420+
current_version,
421+
tag_format=tag_format,
422+
scheme=self.scheme,
423+
)
424+
425+
final_versions = []
426+
for tag in git.get_tag_names():
427+
assert tag
428+
try:
429+
version = self.scheme(tag)
430+
if not version.is_prerelease or tag == current:
431+
final_versions.append(version)
432+
except InvalidVersion:
433+
continue
434+
435+
if not final_versions:
436+
return None
437+
438+
final_versions = sorted(final_versions) # type: ignore [type-var]
439+
current_index = final_versions.index(current_version)
440+
previous_index = current_index - 1
441+
if previous_index < 0:
442+
return None
443+
return final_versions[previous_index]

commitizen/version_schemes.py

+20-4
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ def bump(
100100
prerelease_offset: int = 0,
101101
devrelease: int | None = None,
102102
is_local_version: bool = False,
103+
force_bump: bool = False,
103104
) -> Self:
104105
"""
105106
Based on the given increment, generate the next bumped version according to the version scheme
@@ -166,7 +167,9 @@ def generate_devrelease(self, devrelease: int | None) -> str:
166167

167168
return f"dev{devrelease}"
168169

169-
def increment_base(self, increment: str | None = None) -> str:
170+
def increment_base(
171+
self, increment: str | None = None, force_bump: bool = False
172+
) -> str:
170173
prev_release = list(self.release)
171174
increments = [MAJOR, MINOR, PATCH]
172175
base = dict(zip_longest(increments, prev_release, fillvalue=0))
@@ -175,7 +178,7 @@ def increment_base(self, increment: str | None = None) -> str:
175178
# must remove its prerelease tag,
176179
# so it doesn't matter the increment.
177180
# Example: 1.0.0a0 with PATCH/MINOR -> 1.0.0
178-
if not self.is_prerelease:
181+
if not self.is_prerelease or force_bump:
179182
if increment == MAJOR:
180183
base[MAJOR] += 1
181184
base[MINOR] = 0
@@ -195,6 +198,7 @@ def bump(
195198
prerelease_offset: int = 0,
196199
devrelease: int | None = None,
197200
is_local_version: bool = False,
201+
force_bump: bool = False,
198202
) -> Self:
199203
"""Based on the given increment a proper semver will be generated.
200204
@@ -212,9 +216,21 @@ def bump(
212216
local_version = self.scheme(self.local).bump(increment)
213217
return self.scheme(f"{self.public}+{local_version}") # type: ignore
214218
else:
215-
base = self.increment_base(increment)
219+
base = self.increment_base(increment, force_bump)
216220
dev_version = self.generate_devrelease(devrelease)
217-
pre_version = self.generate_prerelease(prerelease, offset=prerelease_offset)
221+
release = list(self.release)
222+
if len(release) < 3:
223+
release += [0] * (3 - len(release))
224+
current_semver = ".".join(str(part) for part in release)
225+
if base == current_semver:
226+
pre_version = self.generate_prerelease(
227+
prerelease, offset=prerelease_offset
228+
)
229+
else:
230+
base_version = cast(BaseVersion, self.scheme(base))
231+
pre_version = base_version.generate_prerelease(
232+
prerelease, offset=prerelease_offset
233+
)
218234
# TODO: post version
219235
return self.scheme(f"{base}{pre_version}{dev_version}") # type: ignore
220236

tests/commands/test_bump_command.py

+36
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,42 @@ def test_bump_command_prelease(mocker: MockFixture):
227227
assert tag_exists is True
228228

229229

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

0 commit comments

Comments
 (0)