Teach pbr about post versioned dev versions.

Untagged versions should not be presented as final versions, which is
what was happening. The rules for dev versions are that they lead up
to the next release, so to emit a dev version we have to increment
the current version, then we can emit a dev version number.

implements: blueprint pbr-semver
sem-ver: feature
Change-Id: Icf2f1999613e0d26424798697de34811b9cfc4ab
This commit is contained in:
Robert Collins 2014-03-14 15:05:18 +13:00
parent 1758998881
commit 81c2000881
4 changed files with 131 additions and 21 deletions

View File

@ -26,11 +26,17 @@ PBR can and does do a bunch of things for you:
Version
-------
Version strings will be inferred from git. If a given revision is tagged,
that's the version. If it's not, and you don't provide a version, the version
will be very similar to git describe. If you do, then we'll assume that's the
version you are working towards, and will generate alpha version strings
based on commits since last tag and the current git sha.
Versions can be managed two ways - postversioning and preversioning.
Postversioning is the default, and preversioning is enabeld by setting
``version`` in the setup.cfg ``metadata`` section. In both cases
version strings are inferred from git.
If a given revision is tagged, that's the version. If it's not, then either
the current version is incremented to get a target version (postversioning) or
the version set in setup.cfg metadata (preversioning) is used as the target
version. We then generate dev version strings based on the commits since the
last release and include the current git sha to disambiguate multiple dev
versions with the same number of commits since the release.
.. note::

View File

@ -44,6 +44,7 @@ from setuptools.command import install_scripts
from setuptools.command import sdist
from pbr import extra_files
from pbr import version
TRUE_VALUES = ('true', '1', 'yes')
REQUIREMENTS_FILES = ('requirements.txt', 'tools/pip-requires')
@ -784,7 +785,7 @@ def _get_revno(git_dir):
"""
describe = _run_git_command(['describe', '--always'], git_dir)
if "-" in describe:
return describe.rsplit("-", 2)[-2]
return int(describe.rsplit("-", 2)[-2])
# no tags found
revlist = _run_git_command(
@ -792,12 +793,31 @@ def _get_revno(git_dir):
return len(revlist.splitlines())
def _get_version_from_git(pre_version):
"""Return a version which is equal to the tag that's on the current
revision if there is one, or tag plus number of additional revisions
if the current revision has no tag.
"""
def _get_version_from_git_target(semver, git_dir):
"""Calculate a version from a target version in git_dir.
:param semver: The version we will release next.
:param git_dir: The git directory we're working from.
:return: A version string like 1.2.3.dev1.g123124
"""
# Drop any RC etc versions.
sha = _run_git_command(
['log', '-n1', '--pretty=format:%h'], git_dir)
return semver.to_dev(_get_revno(git_dir), sha)
def _get_version_from_git(pre_version=None):
"""Calculate a version string from git.
If the revision is tagged, return that. Otherwise calculate a semantic
version description of the tree.
The number of revisions since the last tag is included in the dev counter
in the version for untagged versions.
:param pre_version: If supplied use this as the target version rather than
inferring one from the last tag + commit messages.
"""
git_dir = _get_git_directory()
if git_dir and _git_is_installed():
if pre_version:
@ -806,16 +826,24 @@ def _get_version_from_git(pre_version):
['describe', '--exact-match'], git_dir,
throw_on_error=True).replace('-', '.')
except Exception:
sha = _run_git_command(
['log', '-n1', '--pretty=format:%h'], git_dir)
return "%s.dev%s.g%s" % (pre_version, _get_revno(git_dir), sha)
# not released yet - use pre_version as the target
semver = version.SemanticVersion.from_pip_string(pre_version)
return _get_version_from_git_target(
semver, git_dir).release_string()
else:
description = _run_git_command(
['describe', '--always'], git_dir).replace('-', '.')
if '.' not in description:
# Untagged tree.
description = '0.g%s' % description
return description
try:
return _run_git_command(
['describe', '--exact-match'], git_dir,
throw_on_error=True).replace('-', '.')
except Exception:
last_version = _run_git_command(
['describe', '--abbrev=0'], git_dir)
if not last_version:
# Untagged tree.
last_version = '0'
semver = version.SemanticVersion.from_pip_string(last_version)
return _get_version_from_git_target(
semver.increment(), git_dir).release_string()
# If we don't know the version, return an empty string so at least
# the downstream users of the value always have the same type of
# object to work with.
@ -852,6 +880,9 @@ def get_version(package_name, pre_version=None):
that a source tarball be made from our git repo - or that if someone wants
to make a source tarball from a fork of our repo with additional tags in it
that they understand and desire the results of doing that.
:param pre_version: The version field from setup.cfg - if set then this
version will be the next release.
"""
version = os.environ.get(
"PBR_VERSION",

View File

@ -147,7 +147,8 @@ def _run_cmd(args, cwd):
:return: ((stdout, stderr), returncode)
"""
p = subprocess.Popen(
args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd)
args, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, cwd=cwd)
streams = tuple(s.decode('latin1').strip() for s in p.communicate())
for content in streams:
print(content)

View File

@ -44,6 +44,7 @@ import tempfile
import fixtures
import mock
import testscenarios
from testtools import matchers
from pbr import packaging
from pbr.tests import base
@ -67,11 +68,53 @@ class TestRepo(fixtures.Fixture):
base._run_cmd(
['git', 'config', '--global', 'user.email', 'example@example.com'],
self._basedir)
base._run_cmd(
['git', 'config', '--global', 'user.signingkey',
'example@example.com'], self._basedir)
base._run_cmd(['git', 'add', '.'], self._basedir)
def commit(self):
files = len(os.listdir(self._basedir))
path = self._basedir + '/%d' % files
open(path, 'wt').close()
base._run_cmd(['git', 'add', path], self._basedir)
base._run_cmd(['git', 'commit', '-m', 'test commit'], self._basedir)
def tag(self, version):
base._run_cmd(
['git', 'tag', '-sm', 'test tag', version], self._basedir)
class GPGKeyFixture(fixtures.Fixture):
"""Creates a GPG key for testing.
It's recommended that this be used in concert with a unique home
directory.
"""
def setUp(self):
super(GPGKeyFixture, self).setUp()
tempdir = self.useFixture(fixtures.TempDir())
config_file = tempdir.path + '/key-config'
f = open(config_file, 'wt')
try:
f.write("""
#%no-protection -- these would be ideal but they are documented
#%transient-key -- but not implemented in gnupg!
%no-ask-passphrase
Key-Type: RSA
Name-Real: Example Key
Name-Comment: N/A
Name-Email: example@example.com
Expire-Date: 2d
Preferences: (setpref)
%commit
""")
finally:
f.close()
base._run_cmd(
['gpg', '--gen-key', '--batch', config_file], tempdir.path)
class TestPackagingInGitRepoWithCommit(base.BaseTestCase):
@ -173,5 +216,34 @@ class TestNestedRequirements(base.BaseTestCase):
self.assertEqual(result, ['pbr'])
class TestVersions(base.BaseTestCase):
def setUp(self):
super(TestVersions, self).setUp()
self.repo = self.useFixture(TestRepo(self.package_dir))
self.useFixture(GPGKeyFixture())
self.useFixture(base.DiveDir(self.package_dir))
def test_tagged_version_has_tag_version(self):
self.repo.commit()
self.repo.tag('1.2.3')
version = packaging._get_version_from_git('1.2.3')
self.assertEqual('1.2.3', version)
def test_untagged_version_has_dev_version_postversion(self):
self.repo.commit()
self.repo.tag('1.2.3')
self.repo.commit()
version = packaging._get_version_from_git()
self.assertThat(version, matchers.StartsWith('1.2.4.dev1.g'))
def test_untagged_version_has_dev_version_preversion(self):
self.repo.commit()
self.repo.tag('1.2.3')
self.repo.commit()
version = packaging._get_version_from_git('1.2.5')
self.assertThat(version, matchers.StartsWith('1.2.5.dev1.g'))
def load_tests(loader, in_tests, pattern):
return testscenarios.load_tests_apply_scenarios(loader, in_tests, pattern)