summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobert Collins <rbtcollins@hp.com>2014-09-29 11:03:53 +1300
committerRobert Collins <rbtcollins@hp.com>2015-04-15 19:06:05 +1200
commit2465a4cac7570cbf8e61456faaf44ce67a8bbc0b (patch)
treeb476bb0c826eb6edf1db0927e1b9dcb39b12a220
parent82f3b535c058984fcc62426de8b955ffcef7ef33 (diff)
downloadpbr-2465a4cac7570cbf8e61456faaf44ce67a8bbc0b.tar.gz
Fixup semver
The semver doc that Monty wrote was great, but doesn't work in the real world. Specifically because 1.dev1 < 1.a1, we're unable to depend on alphas without our testing getting locked down to the alpha on PyPI rather than the revision we're trying to test. This update fixes that by allowing 1.a1.dev1, audits for consistency with PEP-440, fixes up the contact details now that this is team maintained and notes that its as much Python specific as Linux specific. We also remove the never-in-a-release SemanticVersion.to_release method, as YAGNI. Change-Id: I005a2386842633e9fcda76adfc523196c8c2c95d Sem-Ver: feature
-rw-r--r--doc/source/semver.rst101
-rw-r--r--pbr/tests/test_packaging.py16
-rw-r--r--pbr/tests/test_version.py163
-rw-r--r--pbr/version.py122
4 files changed, 201 insertions, 201 deletions
diff --git a/doc/source/semver.rst b/doc/source/semver.rst
index 8b10112..c5cf5bf 100644
--- a/doc/source/semver.rst
+++ b/doc/source/semver.rst
@@ -1,25 +1,34 @@
-Linux Compatible Semantic Versioning 3.0.0
-==========================================
+Linux/Python Compatible Semantic Versioning 3.0.0
+=================================================
This is a fork of Semantic Versioning 2.0. The specific changes have to do
with the format of pre-release and build labels, specifically to make them
-not confusing when co-existing with Linux Distribution packaging.
-Inspiration for the format of the pre-release and build labels came from
-Python's PEP440.
+not confusing when co-existing with Linux distribution packaging and Python
+packaging. Inspiration for the format of the pre-release and build labels
+came from Python's PEP440.
+
+Changes vs SemVer 2.0
+---------------------
+
+#. dev versions are defined. These are extremely useful when
+ dealing with CI and CD systems when 'every commit is a release' is not
+ feasible.
+
+#. All versions have been made PEP-440 compatible, because of our deep
+ roots in Python. Pre-release versions are now separated by . not -, and
+ use a/b/c rather than alpha/beta etc.
Summary
-------
-Given a version number MAJOR.MINOR.PATCH, increment the:
+Given a version number MAJOR.MINOR.PATCH,
+increment the:
#. MAJOR version when you make incompatible API changes,
#. MINOR version when you add functionality in a backwards-compatible
manner, and
#. PATCH version when you make backwards-compatible bug fixes.
-Additional labels for pre-release and build metadata are available as
-extensions to the MAJOR.MINOR.PATCH format.
-
Introduction
------------
@@ -56,12 +65,6 @@ I call this system "Semantic Versioning." Under this scheme, version
numbers and the way they change convey meaning about the underlying code
and what has been modified from one version to the next.
-Linux Compatible Semantic Versioning is different from Semantic
-Versioning in that it does not employ the use of the hyphen in ways that
-are ambiguous when used with or adjacent to software packaged with dpkg or
-rpm. Instead, it draws from PEP440's approach of indicating pre-releases
-with leading characters in the version segment.
-
Semantic Versioning Specification (SemVer)
------------------------------------------
@@ -111,24 +114,27 @@ document are to be interpreted as described in `RFC
#. A pre-release version MAY be denoted by appending a dot
separated identifier immediately following the patch version.
- The identifier MUST comprise only a, b, rc followed by non-negative
+ The identifier MUST comprise only a, b, c followed by non-negative
integer value. The identifier MUST NOT be empty.
Pre-release versions have a lower precedence than the associated normal
version. A pre-release version indicates that
the version is unstable and might not satisfy the intended
compatibility requirements as denoted by its associated normal
- version. Examples: 1.0.0.a1, 1.0.0.b99, 1.0.0.rc1000.
+ version. Examples: 1.0.0.a1, 1.0.0.b99, 1.0.0.c1000.
#. A development version MAY be denoted by appending a dot separated
identifier immediately following the patch version.
The identifier MUST comprise the string dev followed by non-negative
integer value. The identifier MUST NOT be empty. Development versions
- have a lower precedence than the associated normal version. A development
- version is a completely unsupported and conveys no API promises when
- related to other versions. They are more useful as communication
- vehicles between developers of a community, whereas pre-releases, while
- potentially prone to break still, are intended for externally facing
- communication of not-yet-released ideas. Example: 1.0.0.dev1.
+ have a lower precedence than the associated normal version or pre-release
+ version. A development version is a completely unsupported and conveys no
+ API promises when related to other versions. They are more useful as
+ communication vehicles between developers of a community, whereas
+ pre-releases, while potentially prone to break still, are intended for
+ externally facing communication of not-yet-released ideas. Dev versions
+ are not public artifacts and should never be placed in public
+ repositories: they are intended as developer-local resources. Examples:
+ 1.0.0.dev1, 1.0.0.a1.dev1
#. git version metadata MAY be denoted by appending a dot separated
identifier immediately following a development or pre-release version.
@@ -149,23 +155,26 @@ document are to be interpreted as described in `RFC
#. Precedence refers to how versions are compared to each other when
ordered. Precedence MUST be calculated by separating the version
- into major, minor, patch and pre-release identifiers in that order
- (Build metadata does not figure into precedence). Precedence is
- determined by the first difference when comparing each of these
+ into major, minor, patch, pre-release, and development identifiers in
+ that order (Build metadata does not figure into precedence). Precedence
+ is determined by the first difference when comparing each of these
identifiers from left to right as follows: Major, minor, and patch
versions are always compared numerically. Example: 1.0.0 < 2.0.0 <
2.1.0 < 2.1.1. When major, minor, and patch are equal, a pre-release
version has lower precedence than a normal version. Example:
- 1.0.0.a1 < 1.0.0. When major, minor, and patch are equal, a development
- version as a lower precedence than a normal version and of a pre-release
- version. Example: 1.0.0.dev1 < 1.0.0 and 1.0.0dev9 < 1.0.0a1.
- Precedence for two pre-release or development versions with
- the same major, minor, and patch version MUST be determined by
- comparing the identifier to the right of the patch version as follows:
+ 1.0.0.a1 < 1.0.0. When major, minor, patch and pre-release are equal, a
+ development version has a lower precedence than a normal version and of a
+ pre-release version. Example: 1.0.0.dev1 < 1.0.0 and 1.0.0.dev9 <
+ 1.0.0.a1 and 1.0.0.a1 < 1.0.0.a2.dev4. Precedence for two pre-release
+ versions with the same major, minor, and patch version MUST be determined
+ by comparing the identifier to the right of the patch version as follows:
if the alpha portion matches, the numeric portion is compared in
- numerical sort order. If the alpha portion does not match, the sort
- order is dev < a < b < rc. Example: 1.0.0.dev8 < 1.0.0.dev9
- 1.0.0.a1 < 1.0.0.b2 < 1.0.0.rc1 < 1.0.0.
+ numerical sort order. If the alpha portion does not match, the sort order
+ is dev < a < b < c. Example: 1.0.0.dev8 < 1.0.0.dev9 < 1.0.0.a1.dev3 <
+ 1.0.0.a1 < 1.0.0.b2 < 1.0.0.c1 < 1.0.0. Precedence for dev versions if
+ all other components are equal is done by comparing their numeric
+ component. If all other components are not equal, predence is determined
+ by comparing the other components.
Why Use Semantic Versioning?
----------------------------
@@ -302,23 +311,15 @@ limits on the size of the string.
About
-----
-The Linux Compatible Semantic Versioning specification was modified by
-`Monty Taylor <http://inaugust.com>`__, member of `The Satori
-Group <http://satori-group.com>`__, co-founder of OpenStack and Free
-Software Hacker.
+The Linux/Python Compatible Semantic Versioning specification is maintained
+by the `OpenStack <http://openstack.org>`_ project.
-It was based on The Semantic Versioning specification, which was
+It is based on The Semantic Versioning specification, which was
authored by `Tom Preston-Werner <http://tom.preston-werner.com>`__,
-inventor of Gravatars and cofounder of GitHub, with inputs from `PEP
-440 <http://www.python.org/dev/peps/pep-0440/>`__ which was authored by
-`Nick Coughlan <http://www.boredomandlaziness.org>`__ who is a core
-Python developer and generally a great guy. I don't really know which
-things Nick invented or co-founded, and I'm not really sure why we'd
-need to list those here, but Tom did, so I figured coding style is
-usually about sticking to the style that was there before you showed up.
-
-If you'd like to leave feedback, please `open an issue on
-GitHub <https://github.com/emonty/semver/issues>`__.
+with inputs from `PEP 440 <http://www.python.org/dev/peps/pep-0440/>`_
+
+If you'd like to leave feedback, please `open an issue
+<https://bugs.launchpad.net/pbr/+filebug>`_.
License
-------
diff --git a/pbr/tests/test_packaging.py b/pbr/tests/test_packaging.py
index d048a3c..5615d28 100644
--- a/pbr/tests/test_packaging.py
+++ b/pbr/tests/test_packaging.py
@@ -300,6 +300,13 @@ class TestVersions(base.BaseTestCase):
version = packaging._get_version_from_git()
self.assertThat(version, matchers.StartsWith('1.2.4.dev1'))
+ def test_untagged_pre_release_has_pre_dev_version_postversion(self):
+ self.repo.commit()
+ self.repo.tag('1.2.3.0a1')
+ self.repo.commit()
+ version = packaging._get_version_from_git()
+ self.assertThat(version, matchers.StartsWith('1.2.3.0a2.dev1'))
+
def test_untagged_version_minor_bump(self):
self.repo.commit()
self.repo.tag('1.2.3')
@@ -321,6 +328,13 @@ class TestVersions(base.BaseTestCase):
version = packaging._get_version_from_git('1.2.5')
self.assertThat(version, matchers.StartsWith('1.2.5.dev1'))
+ def test_untagged_version_after_pre_has_dev_version_preversion(self):
+ self.repo.commit()
+ self.repo.tag('1.2.3.0a1')
+ self.repo.commit()
+ version = packaging._get_version_from_git('1.2.5')
+ self.assertThat(version, matchers.StartsWith('1.2.5.dev1'))
+
def test_preversion_too_low_simple(self):
# That is, the target version is either already released or not high
# enough for the semver requirements given api breaks etc.
@@ -403,7 +417,7 @@ class TestVersions(base.BaseTestCase):
self.repo.commit()
self.repo.tag('badver4')
version = packaging._get_version_from_git()
- self.assertThat(version, matchers.StartsWith('1.2.4.dev1'))
+ self.assertThat(version, matchers.StartsWith('1.2.4.0a2.dev1'))
def test_valid_tag_honoured(self):
# Fix for bug 1370608 - we converted any target into a 'dev version'
diff --git a/pbr/tests/test_version.py b/pbr/tests/test_version.py
index 73f384b..e2824bb 100644
--- a/pbr/tests/test_version.py
+++ b/pbr/tests/test_version.py
@@ -13,7 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-import operator
+import itertools
from testtools import matchers
@@ -26,72 +26,41 @@ from_pip_string = version.SemanticVersion.from_pip_string
class TestSemanticVersion(base.BaseTestCase):
- def test_equality(self):
- base = version.SemanticVersion(1, 2, 3)
- base2 = version.SemanticVersion(1, 2, 3)
- major = version.SemanticVersion(2, 2, 3)
- minor = version.SemanticVersion(1, 3, 3)
- patch = version.SemanticVersion(1, 2, 4)
- pre_base = version.SemanticVersion(1, 2, 3, 'a', 4)
- pre_base2 = version.SemanticVersion(1, 2, 3, 'a', 4)
- pre_type = version.SemanticVersion(1, 2, 3, 'b', 4)
- pre_serial = version.SemanticVersion(1, 2, 3, 'a', 5)
- dev_base = version.SemanticVersion(1, 2, 3, dev_count=6)
- dev_base2 = version.SemanticVersion(1, 2, 3, dev_count=6)
- dev_count = version.SemanticVersion(1, 2, 3, dev_count=7)
- self.assertEqual(base, base2)
- self.assertNotEqual(base, major)
- self.assertNotEqual(base, minor)
- self.assertNotEqual(base, patch)
- self.assertNotEqual(base, pre_type)
- self.assertNotEqual(base, pre_serial)
- self.assertNotEqual(base, dev_count)
- self.assertEqual(pre_base, pre_base2)
- self.assertNotEqual(pre_base, pre_type)
- self.assertNotEqual(pre_base, pre_serial)
- self.assertNotEqual(pre_base, dev_count)
- self.assertEqual(dev_base, dev_base2)
- self.assertNotEqual(dev_base, dev_count)
- simple = version.SemanticVersion(1)
- explicit_minor = version.SemanticVersion(1, 0)
- explicit_patch = version.SemanticVersion(1, 0, 0)
- self.assertEqual(simple, explicit_minor)
- self.assertEqual(simple, explicit_patch)
- self.assertEqual(explicit_minor, explicit_patch)
-
def test_ordering(self):
- base = version.SemanticVersion(1, 2, 3)
- major = version.SemanticVersion(2, 2, 3)
- minor = version.SemanticVersion(1, 3, 3)
- patch = version.SemanticVersion(1, 2, 4)
- pre_alpha = version.SemanticVersion(1, 2, 3, 'a', 4)
- pre_beta = version.SemanticVersion(1, 2, 3, 'b', 3)
- pre_rc = version.SemanticVersion(1, 2, 3, 'rc', 2)
- pre_serial = version.SemanticVersion(1, 2, 3, 'a', 5)
- dev_base = version.SemanticVersion(1, 2, 3, dev_count=6)
- dev_count = version.SemanticVersion(1, 2, 3, dev_count=7)
- self.assertThat(base, matchers.LessThan(major))
- self.assertThat(major, matchers.GreaterThan(base))
- self.assertThat(base, matchers.LessThan(minor))
- self.assertThat(minor, matchers.GreaterThan(base))
- self.assertThat(base, matchers.LessThan(patch))
- self.assertThat(patch, matchers.GreaterThan(base))
- self.assertThat(pre_alpha, matchers.LessThan(base))
- self.assertThat(base, matchers.GreaterThan(pre_alpha))
- self.assertThat(pre_alpha, matchers.LessThan(pre_beta))
- self.assertThat(pre_beta, matchers.GreaterThan(pre_alpha))
- self.assertThat(pre_beta, matchers.LessThan(pre_rc))
- self.assertThat(pre_rc, matchers.GreaterThan(pre_beta))
- self.assertThat(pre_alpha, matchers.LessThan(pre_serial))
- self.assertThat(pre_serial, matchers.GreaterThan(pre_alpha))
- self.assertThat(pre_serial, matchers.LessThan(pre_beta))
- self.assertThat(pre_beta, matchers.GreaterThan(pre_serial))
- self.assertThat(dev_base, matchers.LessThan(base))
- self.assertThat(base, matchers.GreaterThan(dev_base))
- self.assertRaises(TypeError, operator.lt, pre_alpha, dev_base)
- self.assertRaises(TypeError, operator.lt, dev_base, pre_alpha)
- self.assertThat(dev_base, matchers.LessThan(dev_count))
- self.assertThat(dev_count, matchers.GreaterThan(dev_base))
+ ordered_versions = [
+ "1.2.3.dev6",
+ "1.2.3.dev7",
+ "1.2.3.a4.dev12",
+ "1.2.3.a4.dev13",
+ "1.2.3.a4",
+ "1.2.3.a5.dev1",
+ "1.2.3.a5",
+ "1.2.3.b3.dev1",
+ "1.2.3.b3",
+ "1.2.3.rc2.dev1",
+ "1.2.3.rc2",
+ "1.2.3.rc3.dev1",
+ "1.2.3",
+ "1.2.4",
+ "1.3.3",
+ "2.2.3",
+ ]
+ for v in ordered_versions:
+ sv = version.SemanticVersion.from_pip_string(v)
+ self.expectThat(sv, matchers.Equals(sv))
+ for left, right in itertools.combinations(ordered_versions, 2):
+ l_pos = ordered_versions.index(left)
+ r_pos = ordered_versions.index(right)
+ if l_pos < r_pos:
+ m1 = matchers.LessThan
+ m2 = matchers.GreaterThan
+ else:
+ m1 = matchers.GreaterThan
+ m2 = matchers.LessThan
+ left_sv = version.SemanticVersion.from_pip_string(left)
+ right_sv = version.SemanticVersion.from_pip_string(right)
+ self.expectThat(left_sv, m1(right_sv))
+ self.expectThat(right_sv, m2(left_sv))
def test_from_pip_string_legacy_alpha(self):
expected = version.SemanticVersion(
@@ -99,6 +68,22 @@ class TestSemanticVersion(base.BaseTestCase):
parsed = from_pip_string('1.2.0rc1')
self.assertEqual(expected, parsed)
+ def test_from_pip_string_legacy_postN(self):
+ # When pbr trunk was incompatible with PEP-440, a stable release was
+ # made that used postN versions to represent developer builds. As
+ # we expect only to be parsing versions of our own, we map those
+ # into dev builds of the next version.
+ expected = version.SemanticVersion(1, 2, 4, dev_count=5)
+ parsed = from_pip_string('1.2.3.post5')
+ self.expectThat(expected, matchers.Equals(parsed))
+ expected = version.SemanticVersion(1, 2, 3, 'a', 5, dev_count=6)
+ parsed = from_pip_string('1.2.3.0a4.post6')
+ self.expectThat(expected, matchers.Equals(parsed))
+ # We can't define a mapping for .postN.devM, so it should raise.
+ self.expectThat(
+ lambda: from_pip_string('1.2.3.post5.dev6'),
+ matchers.raises(ValueError))
+
def test_from_pip_string_legacy_nonzero_lead_in(self):
# reported in bug 1361251
expected = version.SemanticVersion(
@@ -173,8 +158,13 @@ class TestSemanticVersion(base.BaseTestCase):
self.assertEqual(semver, from_pip_string("1.2.0.dev5"))
def test_alpha_dev_version(self):
- self.assertRaises(
- ValueError, version.SemanticVersion, 1, 2, 4, 'a', 1, '12')
+ semver = version.SemanticVersion(1, 2, 4, 'a', 1, 12)
+ self.assertEqual((1, 2, 4, 'alphadev', 12), semver.version_tuple())
+ self.assertEqual("1.2.4", semver.brief_string())
+ self.assertEqual("1.2.4~a1.dev12", semver.debian_string())
+ self.assertEqual("1.2.4.0a1.dev12", semver.release_string())
+ self.assertEqual("1.2.3.a1.dev12", semver.rpm_string())
+ self.assertEqual(semver, from_pip_string("1.2.4.0a1.dev12"))
def test_alpha_version(self):
semver = version.SemanticVersion(1, 2, 4, 'a', 1)
@@ -213,8 +203,13 @@ class TestSemanticVersion(base.BaseTestCase):
self.assertEqual(semver, from_pip_string("1.2.4.0a0"))
def test_beta_dev_version(self):
- self.assertRaises(
- ValueError, version.SemanticVersion, 1, 2, 4, 'b', 5, '12')
+ semver = version.SemanticVersion(1, 2, 4, 'b', 1, 12)
+ self.assertEqual((1, 2, 4, 'betadev', 12), semver.version_tuple())
+ self.assertEqual("1.2.4", semver.brief_string())
+ self.assertEqual("1.2.4~b1.dev12", semver.debian_string())
+ self.assertEqual("1.2.4.0b1.dev12", semver.release_string())
+ self.assertEqual("1.2.3.b1.dev12", semver.rpm_string())
+ self.assertEqual(semver, from_pip_string("1.2.4.0b1.dev12"))
def test_beta_version(self):
semver = version.SemanticVersion(1, 2, 4, 'b', 1)
@@ -241,13 +236,9 @@ class TestSemanticVersion(base.BaseTestCase):
def test_decrement_release(self):
# The next patch version of a release version requires a change to the
# patch level.
- semver = version.SemanticVersion(1, 2, 5)
- self.assertEqual(
- version.SemanticVersion(1, 2, 6), semver.increment())
+ semver = version.SemanticVersion(2, 2, 5)
self.assertEqual(
- version.SemanticVersion(1, 3, 0), semver.increment(minor=True))
- self.assertEqual(
- version.SemanticVersion(2, 0, 0), semver.increment(major=True))
+ version.SemanticVersion(2, 2, 4), semver.decrement())
def test_increment_nonrelease(self):
# The next patch version of a non-release version is another
@@ -274,8 +265,13 @@ class TestSemanticVersion(base.BaseTestCase):
version.SemanticVersion(2, 0, 0), semver.increment(major=True))
def test_rc_dev_version(self):
- self.assertRaises(
- ValueError, version.SemanticVersion, 1, 2, 4, 'rc', 1, '12')
+ semver = version.SemanticVersion(1, 2, 4, 'rc', 1, 12)
+ self.assertEqual((1, 2, 4, 'candidatedev', 12), semver.version_tuple())
+ self.assertEqual("1.2.4", semver.brief_string())
+ self.assertEqual("1.2.4~rc1.dev12", semver.debian_string())
+ self.assertEqual("1.2.4.0rc1.dev12", semver.release_string())
+ self.assertEqual("1.2.3.rc1.dev12", semver.rpm_string())
+ self.assertEqual(semver, from_pip_string("1.2.4.0rc1.dev12"))
def test_rc_version(self):
semver = version.SemanticVersion(1, 2, 4, 'rc', 1)
@@ -291,14 +287,5 @@ class TestSemanticVersion(base.BaseTestCase):
version.SemanticVersion(1, 2, 3, dev_count=1),
version.SemanticVersion(1, 2, 3).to_dev(1))
self.assertEqual(
- version.SemanticVersion(1, 2, 3, dev_count=1),
+ version.SemanticVersion(1, 2, 3, 'rc', 1, dev_count=1),
version.SemanticVersion(1, 2, 3, 'rc', 1).to_dev(1))
-
- def test_to_release(self):
- self.assertEqual(
- version.SemanticVersion(1, 2, 3),
- version.SemanticVersion(
- 1, 2, 3, dev_count=1).to_release())
- self.assertEqual(
- version.SemanticVersion(1, 2, 3),
- version.SemanticVersion(1, 2, 3, 'rc', 1).to_release())
diff --git a/pbr/version.py b/pbr/version.py
index dac0966..e297da7 100644
--- a/pbr/version.py
+++ b/pbr/version.py
@@ -20,6 +20,7 @@ Utilities for consuming the version from pkg_resources.
import itertools
import operator
+import sys
import pkg_resources
@@ -51,11 +52,6 @@ class SemanticVersion(object):
:param prerelease: For prerelease versions, what number prerelease.
Defaults to 0.
:param dev_count: How many commits since the last release.
-
- :raises: ValueError if both a prerelease version and dev_count is
- supplied. This is because semver (see the pbr semver documentation)
- does not permit both a prerelease version and a dev marker at the same
- time.
"""
self._major = major
self._minor = minor
@@ -64,11 +60,7 @@ class SemanticVersion(object):
self._prerelease = prerelease
if self._prerelease_type and not self._prerelease:
self._prerelease = 0
- self._dev_count = dev_count
- if prerelease_type is not None and dev_count is not None:
- raise ValueError(
- "invalid version: cannot have prerelease and dev strings %s %s"
- % (prerelease_type, dev_count))
+ self._dev_count = dev_count or 0 # Normalise 0 to None.
def __eq__(self, other):
if not isinstance(other, SemanticVersion):
@@ -78,6 +70,29 @@ class SemanticVersion(object):
def __hash__(self):
return sum(map(hash, self.__dict__.values()))
+ def _sort_key(self):
+ """Return a key for sorting SemanticVersion's on."""
+ # key things:
+ # - final is after rc's, so we make that a/b/rc/z
+ # - dev==None is after all other devs, so we use sys.maxsize there.
+ # - unqualified dev releases come before any pre-releases.
+ # So we do:
+ # (major, minor, patch) - gets the major grouping.
+ # (0|1) unqualified dev flag
+ # (a/b/rc/z) - release segment grouping
+ # pre-release level
+ # dev count, maxsize for releases.
+ rc_lookup = {'a': 'a', 'b': 'b', 'rc': 'rc', None: 'z'}
+ if self._dev_count and not self._prerelease_type:
+ uq_dev = 0
+ else:
+ uq_dev = 1
+ return (
+ self._major, self._minor, self._patch,
+ uq_dev,
+ rc_lookup[self._prerelease_type], self._prerelease,
+ self._dev_count or sys.maxsize)
+
def __lt__(self, other):
"""Compare self and other, another Semantic Version."""
# NB(lifeless) this could perhaps be rewritten as
@@ -86,39 +101,7 @@ class SemanticVersion(object):
# if this ever becomes performance sensitive.
if not isinstance(other, SemanticVersion):
raise TypeError("ordering to non-SemanticVersion is undefined")
- this_tuple = (self._major, self._minor, self._patch)
- other_tuple = (other._major, other._minor, other._patch)
- if this_tuple < other_tuple:
- return True
- elif this_tuple > other_tuple:
- return False
- if self._prerelease_type:
- if other._prerelease_type:
- # Use the a < b < rc cheat
- this_tuple = (self._prerelease_type, self._prerelease)
- other_tuple = (other._prerelease_type, other._prerelease)
- return this_tuple < other_tuple
- elif other._dev_count:
- raise TypeError(
- "ordering pre-release with dev builds is undefined")
- else:
- return True
- elif self._dev_count:
- if other._dev_count:
- if self._dev_count < other._dev_count:
- return True
- else:
- return False
- elif other._prerelease_type:
- raise TypeError(
- "ordering pre-release with dev builds is undefined")
- else:
- return True
- else:
- # This is not pre-release.
- # If the other is pre-release or dev, we are greater, which is ! <
- # If the other is not pre-release, we are equal, which is ! <
- return False
+ return self._sort_key() < other._sort_key()
def __le__(self, other):
return self == other or self < other
@@ -181,6 +164,7 @@ class SemanticVersion(object):
major = int(components[0])
minor = int(components[1])
dev_count = None
+ post_count = None
prerelease_type = None
prerelease = None
@@ -215,17 +199,28 @@ class SemanticVersion(object):
# Current RC/beta layout
prerelease_type, prerelease = _parse_type(remainder[0])
remainder = remainder[1:]
- if remainder:
+ while remainder:
component = remainder[0]
if component.startswith('dev'):
dev_count = int(component[3:])
+ elif component.startswith('post'):
+ dev_count = None
+ post_count = int(component[4:])
else:
raise ValueError(
'Unknown remainder %r in %r'
% (remainder, version_string))
- return SemanticVersion(
+ remainder = remainder[1:]
+ result = SemanticVersion(
major, minor, patch, prerelease_type=prerelease_type,
prerelease=prerelease, dev_count=dev_count)
+ if post_count:
+ if dev_count:
+ raise ValueError(
+ 'Cannot combine postN and devN - no mapping in %r'
+ % (version_string,))
+ result = result.increment().to_dev(post_count)
+ return result
def brief_string(self):
"""Return the short version minus any alpha/beta tags."""
@@ -239,7 +234,7 @@ class SemanticVersion(object):
"""
return self._long_version("~")
- def decrement(self, minor=False, major=False):
+ def decrement(self):
"""Return a decremented SemanticVersion.
Decrementing versions doesn't make a lot of sense - this method only
@@ -329,7 +324,10 @@ class SemanticVersion(object):
"%s%s%s%s" % (pre_separator, rc_marker, self._prerelease_type,
self._prerelease))
if self._dev_count:
- segments.append(pre_separator)
+ if not self._prerelease_type:
+ segments.append(pre_separator)
+ else:
+ segments.append('.')
segments.append('dev')
segments.append(self._dev_count)
return "".join(str(s) for s in segments)
@@ -357,15 +355,8 @@ class SemanticVersion(object):
:param dev_count: The number of commits since the last release.
"""
return SemanticVersion(
- self._major, self._minor, self._patch, dev_count=dev_count)
-
- def to_release(self):
- """Discard any pre-release or dev metadata.
-
- :return: A new SemanticVersion with major/minor/patch the same as this
- one.
- """
- return SemanticVersion(self._major, self._minor, self._patch)
+ self._major, self._minor, self._patch, self._prerelease_type,
+ self._prerelease, dev_count=dev_count)
def version_tuple(self):
"""Present the version as a version_info tuple.
@@ -374,7 +365,10 @@ class SemanticVersion(object):
documentation for sys.version_info.
Since semver and PEP-440 represent overlapping but not subsets of
- versions, we have to have some heuristic / mapping rules:
+ versions, we have to have some heuristic / mapping rules, and have
+ extended the releaselevel field to have alphadev, betadev and
+ candidatedev values. When they are present the dev count is used
+ to provide the serial.
- a/b/rc take precedence.
- if there is no pre-release version the dev version is used.
- serial is taken from the dev/a/b/c component.
@@ -382,12 +376,16 @@ class SemanticVersion(object):
"""
segments = [self._major, self._minor, self._patch]
if self._prerelease_type:
- type_map = {'a': 'alpha',
- 'b': 'beta',
- 'rc': 'candidate',
+ type_map = {('a', False): 'alpha',
+ ('b', False): 'beta',
+ ('rc', False): 'candidate',
+ ('a', True): 'alphadev',
+ ('b', True): 'betadev',
+ ('rc', True): 'candidatedev',
}
- segments.append(type_map[self._prerelease_type])
- segments.append(self._prerelease)
+ segments.append(
+ type_map[(self._prerelease_type, bool(self._dev_count))])
+ segments.append(self._dev_count or self._prerelease)
elif self._dev_count:
segments.append('dev')
segments.append(self._dev_count - 1)