From b05fbe1a0273b99469c190ca3820ed6192f996f8 Mon Sep 17 00:00:00 2001 From: Cristian Ramirez <62491943+Str0k@users.noreply.github.com> Date: Sat, 12 Sep 2026 11:32:30 +0000 Subject: [PATCH 1/2] Fix bump_build() silently returning an unchanged version for digitless build metadata --- docs/usage/raise-parts-of-a-version.rst | 11 ++++ src/semver/version.py | 7 +++ tests/test_bump_build_digitless.py | 78 +++++++++++++++++++++++++ 3 files changed, 96 insertions(+) create mode 100644 tests/test_bump_build_digitless.py diff --git a/docs/usage/raise-parts-of-a-version.rst b/docs/usage/raise-parts-of-a-version.rst index 101a8c35..719d3dc4 100644 --- a/docs/usage/raise-parts-of-a-version.rst +++ b/docs/usage/raise-parts-of-a-version.rst @@ -79,3 +79,14 @@ lower than `rc10` (non-numeric identifiers are compared alphabetically): >>> str(Version.parse("3.4.5-rc.9").bump_prerelease()) '3.4.5-rc.10' +The same applies to :meth:`~semver.version.Version.bump_build`: if the +last part of the dot-separated build metadata is not numeric, ``.0`` is +appended to ensure a raised build: + +.. code-block:: python + + >>> str(Version.parse("3.4.5+build.4").bump_build()) + '3.4.5+build.5' + >>> str(Version.parse("3.4.5+alpha").bump_build()) + '3.4.5+alpha.0' + diff --git a/src/semver/version.py b/src/semver/version.py index 0cfd2131..f0a24270 100644 --- a/src/semver/version.py +++ b/src/semver/version.py @@ -389,6 +389,8 @@ def bump_build(self, token: Optional[str] = "build") -> "Version": >>> ver.bump_build() Version(major=3, minor=4, patch=5, prerelease='rc.1', \ build='build.10') + >>> str(semver.Version.parse("3.4.5-rc.1+alpha").bump_build()) + '3.4.5-rc.1+alpha.0' """ cls = type(self) if self._build is not None: @@ -401,6 +403,11 @@ def bump_build(self, token: Optional[str] = "build") -> "Version": build = str(token) + ".0" build = cls._increment_string(build) + if build == self._build: + # _increment_string leaves a build without any digits + # unchanged. Raise it nevertheless by appending ".0", + # analogous to _increment_prerelease (see :gh:`460`). + build += ".0" return cls(self._major, self._minor, self._patch, self._prerelease, build) def compare(self, other: Comparable) -> int: diff --git a/tests/test_bump_build_digitless.py b/tests/test_bump_build_digitless.py new file mode 100644 index 00000000..3abd0cf0 --- /dev/null +++ b/tests/test_bump_build_digitless.py @@ -0,0 +1,78 @@ +"""Regression tests for bump_build() with digitless build metadata. + +Related to :gh:`466`. While investigating how the ``token`` parameter of +:meth:`semver.version.Version.bump_build` is used, it turned out that +``bump_build()`` silently returns an *unchanged* version when the build +metadata contains no digits at all (for example ``1.2.3+alpha``). + +That contradicts the documented contract that bumping raises the +respective part of the version: the returned object is identical to the +original one, so callers cannot tell that anything was bumped. + +The analogous problem for the prerelease part was fixed in :gh:`460` +by appending ``.0`` when the last identifier is not numeric. These +tests require the same behavior for the build part. + +Note: build metadata does not take part in SemVer precedence, so the +assertions compare the resulting build strings (and string +representations) instead of using :meth:`Version.compare`. +""" + +from semver import Version + + +def test_bump_build_digitless_metadata_should_append_0(): + """bump_build() must raise a digitless build, not return it unchanged.""" + v = Version.parse("1.2.3+alpha") + bumped = v.bump_build() + assert bumped.build == "alpha.0", ( + f"Expected build 'alpha.0', but got {bumped.build!r}" + ) + assert str(bumped) == "1.2.3+alpha.0" + + +def test_bump_build_digitless_metadata_should_return_new_version(): + """bump_build() must not silently return an equal version.""" + v = Version.parse("1.2.3+alpha") + bumped = v.bump_build() + assert bumped is not v + assert bumped.build != v.build + assert bumped.build == "alpha.0" + + +def test_bump_build_digitless_multi_identifier_should_append_0(): + """The last identifier is raised, also for dotted digitless builds.""" + v = Version.parse("1.2.3+exp.sha") + assert str(v.bump_build()) == "1.2.3+exp.sha.0" + + +def test_bump_build_numeric_ending_unchanged(): + """Builds with a numeric last identifier keep the incrementing behavior.""" + v = Version.parse("1.2.3+build.9") + assert str(v.bump_build()) == "1.2.3+build.10" + v2 = Version.parse("1.2.3+alpha.1") + assert str(v2.bump_build()) == "1.2.3+alpha.2" + + +def test_bump_build_token_ignored_for_existing_digitless_build(): + """The token only constructs a build when none exists. + + An existing (digitless) build must still be raised, not replaced + by the token or returned unchanged. + """ + v = Version.parse("1.2.3+alpha") + bumped = v.bump_build("b") + assert str(bumped) == "1.2.3+alpha.0" + + +def test_bump_build_digitless_bump_is_repeatable(): + """Repeated bumping of a digitless build keeps raising it.""" + v = Version.parse("1.2.3+alpha") + assert str(v.bump_build().bump_build()) == "1.2.3+alpha.1" + + +def test_bump_build_no_build_uses_token_unchanged(): + """Without any build, the token logic is unchanged (sanity check).""" + assert str(Version.parse("1.2.3").bump_build()) == "1.2.3+build.1" + assert str(Version.parse("1.2.3").bump_build("b")) == "1.2.3+b.1" + assert str(Version.parse("1.2.3").bump_build("")) == "1.2.3+1" From ce2c8bb3d9fc7fcf10ac21ae1ccea7acc912d4cb Mon Sep 17 00:00:00 2001 From: Cristian Ramirez <62491943+Str0k@users.noreply.github.com> Date: Sat, 12 Sep 2026 11:32:34 +0000 Subject: [PATCH 2/2] Add changelog fragment for PR #480 --- changelog.d/pr480.bugfix.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/pr480.bugfix.rst diff --git a/changelog.d/pr480.bugfix.rst b/changelog.d/pr480.bugfix.rst new file mode 100644 index 00000000..df02553b --- /dev/null +++ b/changelog.d/pr480.bugfix.rst @@ -0,0 +1 @@ +(:meth:`~semver.version.Version.bump_build`) will now add ``.0`` to an existing build when the last segment of the current build metadata, split by dots (``.``), is not numeric. This is to ensure the bumped version contains a raised build instead of silently returning an unchanged version. For example, ``1.2.3+alpha`` is bumped to ``1.2.3+alpha.0``. This mirrors the corresponding fix for the prerelease part (:gh:`460`). Related to :gh:`466`.