summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2014-09-03 14:11:58 +0000
committerGerrit Code Review <review@openstack.org>2014-09-03 14:11:58 +0000
commit3603c5453d6e29ca48cfcd16816e8f69f4845bfd (patch)
treeabbbeacbf04bdc8d2d63d8f52e26ac4ffbf3684b
parent886a95f8d00e8d3eb344874fbb63905aad985e41 (diff)
parentc1c99a7c317c5dc4bdac9a9f8f10e96e5a658b8f (diff)
downloadpbr-3603c5453d6e29ca48cfcd16816e8f69f4845bfd.tar.gz
Merge "Look for and process sem-ver pseudo headers in git"
-rw-r--r--doc/source/index.rst10
-rw-r--r--pbr/packaging.py39
-rw-r--r--pbr/tests/test_packaging.py59
3 files changed, 104 insertions, 4 deletions
diff --git a/doc/source/index.rst b/doc/source/index.rst
index 897f3e5..fa77de1 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -36,6 +36,16 @@ If a given revision is tagged, that's the version.
If it's not, then we take the last tagged version number and increment it to
get a minimum target version.
+We then walk git history back to the last release. Within each commit we look
+for a sem-ver: pseudo header, and if found parse it looking for keywords.
+Unknown symbols are not an error (so that folk can't wedge pbr or break their
+tree), but we will emit an info level warning message. Known symbols:
+``feature``, ``api-break``, ``deprecation``, ``bugfix``. A missing
+sem-ver line is equivalent to ``sem-ver: bugfix``. The ``bugfix`` symbol causes
+a patch level increment to the version. The ``feature`` and ``deprecation``
+symbols cause a minor version increment. The ``api-break`` symbol causes a
+major version increment.
+
If postversioning is in use, we use the resulting version number as the target
version.
diff --git a/pbr/packaging.py b/pbr/packaging.py
index 3f35593..e2fec70 100644
--- a/pbr/packaging.py
+++ b/pbr/packaging.py
@@ -776,6 +776,43 @@ def have_sphinx():
return _have_sphinx
+def _get_increment_kwargs(git_dir, tag):
+ """Calculate the sort of semver increment needed from git history.
+
+ Every commit from HEAD to tag is consider for sem-ver metadata lines.
+ See the pbr docs for their syntax.
+
+ :return: a dict of kwargs for passing into SemanticVersion.increment.
+ """
+ result = {}
+ if tag:
+ version_spec = tag + "..HEAD"
+ else:
+ version_spec = "HEAD"
+ changelog = _run_git_command(['log', version_spec], git_dir)
+ header_len = len(' sem-ver:')
+ commands = [line[header_len:].strip() for line in changelog.split('\n')
+ if line.startswith(' sem-ver:')]
+ symbols = set()
+ for command in commands:
+ symbols.update([symbol.strip() for symbol in command.split(',')])
+
+ def _handle_symbol(symbol, symbols, impact):
+ if symbol in symbols:
+ result[impact] = True
+ symbols.discard(symbol)
+ _handle_symbol('bugfix', symbols, 'patch')
+ _handle_symbol('feature', symbols, 'minor')
+ _handle_symbol('deprecation', symbols, 'minor')
+ _handle_symbol('api-break', symbols, 'major')
+ for symbol in symbols:
+ log.info('[pbr] Unknown sem-ver symbol %r' % symbol)
+ # We don't want patch in the kwargs since it is not a keyword argument -
+ # its the default minimum increment.
+ result.pop('patch', None)
+ return result
+
+
def _get_revno_and_last_tag(git_dir):
"""Return the commit data about the most recent tag.
@@ -813,7 +850,7 @@ def _get_version_from_git_target(git_dir, target_version):
['log', '-n1', '--pretty=format:%h'], git_dir)
tag, distance = _get_revno_and_last_tag(git_dir)
last_semver = version.SemanticVersion.from_pip_string(tag or '0')
- new_version = last_semver.increment()
+ new_version = last_semver.increment(**_get_increment_kwargs(git_dir, tag))
if target_version is not None and new_version > target_version:
raise ValueError(
"git history requires a target version of %(new)s, but target "
diff --git a/pbr/tests/test_packaging.py b/pbr/tests/test_packaging.py
index 1fe8659..71ce6d3 100644
--- a/pbr/tests/test_packaging.py
+++ b/pbr/tests/test_packaging.py
@@ -73,12 +73,15 @@ class TestRepo(fixtures.Fixture):
'example@example.com'], self._basedir)
base._run_cmd(['git', 'add', '.'], self._basedir)
- def commit(self):
+ def commit(self, message_content='test commit'):
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)
+ base._run_cmd(['git', 'commit', '-m', message_content], self._basedir)
+
+ def uncommit(self):
+ base._run_cmd(['git', 'reset', '--hard', 'HEAD^'], self._basedir)
def tag(self, version):
base._run_cmd(
@@ -237,6 +240,20 @@ class TestVersions(base.BaseTestCase):
version = packaging._get_version_from_git()
self.assertThat(version, matchers.StartsWith('1.2.4.dev1.g'))
+ def test_untagged_version_minor_bump(self):
+ self.repo.commit()
+ self.repo.tag('1.2.3')
+ self.repo.commit('sem-ver: deprecation')
+ version = packaging._get_version_from_git()
+ self.assertThat(version, matchers.StartsWith('1.3.0.dev1.g'))
+
+ def test_untagged_version_major_bump(self):
+ self.repo.commit()
+ self.repo.tag('1.2.3')
+ self.repo.commit('sem-ver: api-break')
+ version = packaging._get_version_from_git()
+ self.assertThat(version, matchers.StartsWith('2.0.0.dev1.g'))
+
def test_untagged_version_has_dev_version_preversion(self):
self.repo.commit()
self.repo.tag('1.2.3')
@@ -244,7 +261,7 @@ class TestVersions(base.BaseTestCase):
version = packaging._get_version_from_git('1.2.5')
self.assertThat(version, matchers.StartsWith('1.2.5.dev1.g'))
- def test_preversion_too_low(self):
+ 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.
self.repo.commit()
@@ -256,6 +273,42 @@ class TestVersions(base.BaseTestCase):
ValueError, packaging._get_version_from_git, '1.2.3')
self.assertThat(err.args[0], matchers.StartsWith('git history'))
+ def test_preversion_too_low_semver_headers(self):
+ # That is, the target version is either already released or not high
+ # enough for the semver requirements given api breaks etc.
+ self.repo.commit()
+ self.repo.tag('1.2.3')
+ self.repo.commit('sem-ver: feature')
+ # Note that we can't target 1.2.4, the feature header means we need
+ # to be working on 1.3.0 or above.
+ err = self.assertRaises(
+ ValueError, packaging._get_version_from_git, '1.2.4')
+ self.assertThat(err.args[0], matchers.StartsWith('git history'))
+
+ def test_get_kwargs_corner_cases(self):
+ # No tags:
+ git_dir = self.repo._basedir + '/.git'
+ get_kwargs = lambda tag: packaging._get_increment_kwargs(git_dir, tag)
+
+ def _check_combinations(tag):
+ self.repo.commit()
+ self.assertEqual(dict(), get_kwargs(tag))
+ self.repo.commit('sem-ver: bugfix')
+ self.assertEqual(dict(), get_kwargs(tag))
+ self.repo.commit('sem-ver: feature')
+ self.assertEqual(dict(minor=True), get_kwargs(tag))
+ self.repo.uncommit()
+ self.repo.commit('sem-ver: deprecation')
+ self.assertEqual(dict(minor=True), get_kwargs(tag))
+ self.repo.uncommit()
+ self.repo.commit('sem-ver: api-break')
+ self.assertEqual(dict(major=True), get_kwargs(tag))
+ self.repo.commit('sem-ver: deprecation')
+ self.assertEqual(dict(major=True, minor=True), get_kwargs(tag))
+ _check_combinations('')
+ self.repo.tag('1.2.3')
+ _check_combinations('1.2.3')
+
def load_tests(loader, in_tests, pattern):
return testscenarios.load_tests_apply_scenarios(loader, in_tests, pattern)