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:
parent
82f3b535c0
commit
2465a4cac7
@ -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
|
||||
-------
|
||||
|
@ -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'
|
||||
|
@ -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())
|
||||
|
122
pbr/version.py
122
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)
|
||||
|
Loading…
Reference in New Issue
Block a user