浏览代码

Merge pull request #2617 from ThomasWaldmann/fix-parse-version

fix parse_version, add tests, fixes #2556
TW 8 年之前
父节点
当前提交
af979c2b2a
共有 2 个文件被更改,包括 58 次插入20 次删除
  1. 38 0
      src/borg/testsuite/version.py
  2. 20 20
      src/borg/version.py

+ 38 - 0
src/borg/testsuite/version.py

@@ -0,0 +1,38 @@
+import pytest
+
+from ..version import parse_version
+
+
+@pytest.mark.parametrize("version_str, version_tuple", [
+    # setuptools < 8.0 uses "-"
+    ('1.0.0a1.dev204-g8866961.d20170606', (1, 0, 0, -4, 1)),
+    ('1.0.0a1.dev204-g8866961', (1, 0, 0, -4, 1)),
+    ('1.0.0-d20170606', (1, 0, 0, -1)),
+    # setuptools >= 8.0 uses "+"
+    ('1.0.0a1.dev204+g8866961.d20170606', (1, 0, 0, -4, 1)),
+    ('1.0.0a1.dev204+g8866961', (1, 0, 0, -4, 1)),
+    ('1.0.0+d20170606', (1, 0, 0, -1)),
+    # pre-release versions:
+    ('1.0.0a1', (1, 0, 0, -4, 1)),
+    ('1.0.0a2', (1, 0, 0, -4, 2)),
+    ('1.0.0b3', (1, 0, 0, -3, 3)),
+    ('1.0.0rc4', (1, 0, 0, -2, 4)),
+    # release versions:
+    ('0.0.0', (0, 0, 0, -1)),
+    ('0.0.11', (0, 0, 11, -1)),
+    ('0.11.0', (0, 11, 0, -1)),
+    ('11.0.0', (11, 0, 0, -1)),
+])
+def test_parse_version(version_str, version_tuple):
+    assert parse_version(version_str) == version_tuple
+
+
+def test_parse_version_invalid():
+    with pytest.raises(ValueError):
+        assert parse_version('')  # we require x.y.z versions
+    with pytest.raises(ValueError):
+        assert parse_version('1')  # we require x.y.z versions
+    with pytest.raises(ValueError):
+        assert parse_version('1.2')  # we require x.y.z versions
+    with pytest.raises(ValueError):
+        assert parse_version('crap')

+ 20 - 20
src/borg/version.py

@@ -3,33 +3,33 @@ import re
 
 def parse_version(version):
     """
-    simplistic parser for setuptools_scm versions
+    Simplistic parser for setuptools_scm versions.
 
-    supports final versions and alpha ('a'), beta ('b') and rc versions. It just discards commits since last tag
-    and git revision hash.
+    Supports final versions and alpha ('a'), beta ('b') and release candidate ('rc') versions.
+    It does not try to parse anything else than that, even if there is more in the version string.
 
     Output is a version tuple containing integers. It ends with one or two elements that ensure that relational
-    operators yield correct relations for alpha, beta and rc versions too. For final versions the last element
-    is a -1, for prerelease versions the last two elements are a smaller negative number and the number of e.g.
-    the beta.
-
-    Note, this sorts version 1.0 before 1.0.0.
+    operators yield correct relations for alpha, beta and rc versions, too.
+    For final versions the last element is a -1.
+    For prerelease versions the last two elements are a smaller negative number and the number of e.g. the beta.
 
     This version format is part of the remote protocol, don‘t change in breaking ways.
     """
-
-    parts = version.split('+')[0].split('.')
-    if parts[-1].startswith('dev'):
-        del parts[-1]
-    version = [int(segment) for segment in parts[:-1]]
-
-    prerelease = re.fullmatch('([0-9]+)(a|b|rc)([0-9]+)', parts[-1])
-    if prerelease:
-        version_type = {'a': -4, 'b': -3, 'rc': -2}[prerelease.group(2)]
-        version += [int(prerelease.group(1)), version_type, int(prerelease.group(3))]
+    version_re = r"""
+        (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)   # version, e.g. 1.2.33
+        (?P<prerelease>(?P<ptype>a|b|rc)(?P<pnum>\d+))?  # optional prerelease, e.g. a1 or b2 or rc33
+    """
+    m = re.match(version_re, version, re.VERBOSE)
+    if m is None:
+        raise ValueError('Invalid version string %s' % 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_num = int(gd['pnum'])
+        version += [p_type, p_num]
     else:
-        version += [int(parts[-1]), -1]
-
+        version += [-1]
     return tuple(version)