From da0a2ff26f09fabe815f38b972bb45fc74833c96 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Tue, 12 May 2026 22:22:33 +0200 Subject: [PATCH] fix: properly handle invalid and dev versions in version parser, fixes #9014 - `src/borg/__init__.py`: The `setuptools_scm` fallback version `0.1.dev1` was incorrectly bypassing the assertion check (as `0` and `1` are valid integers), which hid the intended helpful error message when building from source without tags. Added an explicit check for the `0.1.dev` prefix. - `src/borg/version.py`: `parse_version` and `format_version` have been updated to correctly understand `setuptools_scm`'s `.dev` or `dev` prefixes. These dev releases are now properly encoded in the version tuple as `-9` (which logically makes them older than alphas `-4`, betas `-3`, rcs `-2`, and final `-1`), and correctly reformatted to `.dev` strings. (cherry picked from commit 232ccabfa300d4db4015fbb328999a4048ec7c76) --- src/borg/__init__.py | 2 +- src/borg/testsuite/version_test.py | 3 +++ src/borg/version.py | 6 +++--- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/borg/__init__.py b/src/borg/__init__.py index d46c07c50..9b38c42b9 100644 --- a/src/borg/__init__.py +++ b/src/borg/__init__.py @@ -8,7 +8,7 @@ __version_tuple__ = parse_version(__version__).release # assert that all semver components are integers # this is mainly to show errors when people repackage poorly # and setuptools_scm determines a 0.1.dev... version -assert all(isinstance(v, int) for v in __version_tuple__), ( +assert not __version__.startswith("0.1.dev") and all(isinstance(v, int) for v in __version_tuple__), ( """\ Broken BorgBackup version metadata: %r diff --git a/src/borg/testsuite/version_test.py b/src/borg/testsuite/version_test.py index 4b4383332..d33fa0600 100644 --- a/src/borg/testsuite/version_test.py +++ b/src/borg/testsuite/version_test.py @@ -15,6 +15,8 @@ from ..version import parse_version, format_version ("1.0.0a1.dev204+g8866961", (1, 0, 0, -4, 1)), ("1.0.0+d20170606", (1, 0, 0, -1)), # Pre-release versions: + ("1.0.0.dev1", (1, 0, 0, -9, 1)), + ("1.0.0dev2", (1, 0, 0, -9, 2)), ("1.0.0a1", (1, 0, 0, -4, 1)), ("1.0.0a2", (1, 0, 0, -4, 2)), ("1.0.0b3", (1, 0, 0, -3, 3)), @@ -39,6 +41,7 @@ def test_parse_version_invalid(invalid_version): @pytest.mark.parametrize( "version_str, version_tuple", [ + ("1.0.0.dev1", (1, 0, 0, -9, 1)), ("1.0.0a1", (1, 0, 0, -4, 1)), ("1.0.0", (1, 0, 0, -1)), ("1.0.0a2", (1, 0, 0, -4, 2)), diff --git a/src/borg/version.py b/src/borg/version.py index 12eeea168..473fe5f11 100644 --- a/src/borg/version.py +++ b/src/borg/version.py @@ -17,7 +17,7 @@ def parse_version(version): """ version_re = r""" (?P\d+)\.(?P\d+)\.(?P\d+) # version, e.g. 1.2.33 - (?P(?Pa|b|rc)(?P\d+))? # optional prerelease, e.g. a1 or b2 or rc33 + (?P\.?(?Pa|b|rc|dev)(?P\d+))? # optional prerelease, e.g. a1 or b2 or rc33 or .dev1 """ m = re.match(version_re, version, re.VERBOSE) if m is None: @@ -25,7 +25,7 @@ def parse_version(version): gd = m.groupdict() version = [int(gd["major"]), int(gd["minor"]), int(gd["patch"])] if m.lastgroup == "prerelease": - p_type = {"a": -4, "b": -3, "rc": -2}[gd["ptype"]] + p_type = {"a": -4, "b": -3, "rc": -2, "dev": -9}[gd["ptype"]] p_num = int(gd["pnum"]) version += [p_type, p_num] else: @@ -44,6 +44,6 @@ def format_version(version): elif part == -1: break else: - f[-1] = f[-1] + {-2: "rc", -3: "b", -4: "a"}[part] + str(next(it)) + f[-1] = f[-1] + {-2: "rc", -3: "b", -4: "a", -9: ".dev"}[part] + str(next(it)) break return ".".join(f)