Skip to content

fix(next_version): reset prerelease when token changes (#339) - #472

Merged
tomschr merged 2 commits into
python-semver:masterfrom
Mukller:fix/next-version-prerelease-token-reset
Jul 30, 2026
Merged

fix(next_version): reset prerelease when token changes (#339)#472
tomschr merged 2 commits into
python-semver:masterfrom
Mukller:fix/next-version-prerelease-token-reset

Conversation

@Mukller

@Mukller Mukller commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Problem

Fixes #339.

next_version('prerelease', prerelease_token='rc') ignores the token when the version already has a prerelease with a different token:

import semver
ver  = semver.Version.parse('1.2.3')
dev1 = ver.next_version('prerelease', prerelease_token='dev')  # 1.2.4-dev.1
rc1  = dev1.next_version('prerelease', prerelease_token='rc')  # 1.2.4-dev.2 (bug!)
# expected: 1.2.4-rc.1

Root Cause

next_version delegates to bump_prerelease(token, bump_when_empty=True). Inside bump_prerelease, when a prerelease already exists the token argument is silently ignored:

if self._prerelease is not None:
    # token is completely ignored here:
    prerelease = cls._increment_prerelease(self._prerelease)  # dev.1 -> dev.2
else:
    prerelease = str(token) + '.1'  # token used only for brand-new prerelease

Fix

Before delegating to bump_prerelease, next_version now checks whether the existing prerelease token matches the requested one. When they differ, the prerelease is reset to {prerelease_token}.1:

 else:
+    if version.prerelease is not None and prerelease_token:
+        current_token = version.prerelease.split('.')[0]
+        if current_token != prerelease_token:
+            return version.replace(prerelease=f'{prerelease_token}.1', build=None)
     return version.bump_prerelease(prerelease_token, bump_when_empty=True)

Behaviour Table After Fix

Input prerelease_token Result
1.2.3 'rc' 1.2.4-rc.1 (unchanged)
1.2.4-rc.1 'rc' 1.2.4-rc.2 (unchanged)
1.2.4-dev.1 'rc' 1.2.4-rc.1 (fixed)
1.2.4-dev.3 'dev' 1.2.4-dev.4 (unchanged)

The patch version is preserved when changing from one prerelease token to another (the version is already in its bump-patch prerelease cycle).

When next_version("prerelease", prerelease_token="rc") is called on a
version that already has a prerelease with a different token (e.g. dev),
bump_prerelease() ignores the token argument and simply increments the
existing counter. 1.2.4-dev.1 becomes 1.2.4-dev.2 instead of 1.2.4-rc.1.

Fix: before delegating to bump_prerelease, check whether the existing
prerelease token matches the requested one. When they differ, replace
the prerelease directly with "{prerelease_token}.1".

@Mukller Mukller left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

Bug Verification

Reproducible on current master:

import semver
v = semver.Version.parse('1.2.4-dev.1')
print(v.next_version('prerelease', prerelease_token='rc'))
# Current master: 1.2.4-dev.2  (bug)
# After patch:    1.2.4-rc.1   (correct)

Root cause: bump_prerelease ignores its token parameter when self._prerelease is not None, passing only the existing string to _increment_prerelease. The token is only used in the else branch (no existing prerelease).

Fix Correctness

The 4-line guard in next_version handles the token-change case entirely in one place, without touching bump_prerelease. This is correct because:

  • Only next_version has the concept of a prerelease_token in its API — bump_prerelease documents that it ignores the token for an existing prerelease (it just bumps the counter).
  • version.replace(prerelease=f'{prerelease_token}.1', build=None) keeps the major/minor/patch unchanged — correct, since we are staying in the same patch-bump cycle.

Edge Cases

Scenario Before After
No prerelease 1.2.4-rc.1 1.2.4-rc.1 ✓ (unchanged)
Same token 1.2.4-rc.2 1.2.4-rc.2 ✓ (unchanged)
Different token 1.2.4-dev.2 1.2.4-rc.1 ✓ (fixed)
Empty token '' falls to bump_prerelease falls to bump_prerelease (guard skipped by if prerelease_token:)
None token falls to bump_prerelease falls to bump_prerelease (guard skipped by if prerelease_token:)

Change Size

4 lines added to one method. No changes to bump_prerelease or _increment_prerelease.

@tomschr

tomschr commented Jul 30, 2026

Copy link
Copy Markdown
Member

Thank you @Mukller for your contribution! Much appreciated! 👍

I fixed a small doctest. I'm going to merge it.

@tomschr
tomschr merged commit fdec4ae into python-semver:master Jul 30, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

increasing next_version 'prerelease' with different prerelease_token

2 participants