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
This commit is contained in:
Robert Collins 2014-09-29 11:03:53 +13:00
parent 82f3b535c0
commit 2465a4cac7
4 changed files with 200 additions and 200 deletions

View File

@ -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.
with inputs from `PEP 440 <http://www.python.org/dev/peps/pep-0440/>`_
If you'd like to leave feedback, please `open an issue on
GitHub <https://github.com/emonty/semver/issues>`__.
If you'd like to leave feedback, please `open an issue
<https://bugs.launchpad.net/pbr/+filebug>`_.
License
-------

View File

@ -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'

View File

@ -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)
semver = version.SemanticVersion(2, 2, 5)
self.assertEqual(
version.SemanticVersion(1, 2, 6), semver.increment())
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())

View File

@ -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)