Skip to content

Commit 8e6aa2b

Browse files
Merge pull request #430 from chrisjbillington/release-branch-semver-scheme
Add release-branch-semver scheme
2 parents 6ad91d0 + 26f8ff9 commit 8e6aa2b

File tree

4 files changed

+95
-16
lines changed

4 files changed

+95
-16
lines changed

README.rst

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -477,9 +477,20 @@ Version number construction
477477

478478
Available implementations:
479479

480-
:guess-next-dev: automatically guesses the next development version (default)
481-
:post-release: generates post release versions (adds :code:`postN`)
482-
:python-simplified-semver: basic semantic versioning similar to ``guess-next-dev``
480+
:guess-next-dev: Automatically guesses the next development version (default).
481+
Guesses the upcoming release by incrementing the pre-release segment if present,
482+
otherwise by incrementing the micro segment. Then appends :code:`.devN`.
483+
:post-release: generates post release versions (adds :code:`.postN`)
484+
:python-simplified-semver: Basic semantic versioning. Guesses the upcoming release
485+
by incrementing the minor segment and setting the micro segment to zero if the
486+
current branch contains the string ``'feature'``, otherwise by incrementing the
487+
micro version. Then appends :code:`.devN`. Not compatible with pre-releases.
488+
:release-branch-semver: Semantic versioning for projects with release branches. The
489+
same as ``guess-next-dev`` (incrementing the pre-release or micro segment) if on
490+
a release branch: a branch whose name (ignoring namespace) parses as a version
491+
that matches the most recent tag up to the minor segment. Otherwise if on a
492+
non-release branch, increments the minor segment and sets the micro segment to
493+
zero, then appends :code:`.devN`.
483494

484495
``setuptools_scm.local_scheme``
485496
Configures how the local part of a version is rendered given a

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ def parse(root):
8989
guess-next-dev = setuptools_scm.version:guess_next_dev_version
9090
post-release = setuptools_scm.version:postrelease_version
9191
python-simplified-semver = setuptools_scm.version:simplified_semver_version
92+
release-branch-semver = setuptools_scm.version:release_branch_semver
9293
9394
[setuptools_scm.local_scheme]
9495
node-and-date = setuptools_scm.version:get_local_node_and_date

src/setuptools_scm/version.py

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import datetime
33
import warnings
44
import re
5-
from itertools import chain, repeat, islice
65

76
from .config import Configuration
87
from .utils import trace, string_types, utc
@@ -16,11 +15,6 @@
1615
SEMVER_LEN = 3
1716

1817

19-
def _pad(iterable, size, padding=None):
20-
padded = chain(iterable, repeat(padding))
21-
return list(islice(padded, size))
22-
23-
2418
def _parse_version_tag(tag, config):
2519
tagstring = tag if not isinstance(tag, string_types) else str(tag)
2620
match = config.tag_regex.match(tagstring)
@@ -132,6 +126,7 @@ def __init__(
132126
dirty=False,
133127
preformatted=False,
134128
branch=None,
129+
config=None,
135130
**kw
136131
):
137132
if kw:
@@ -146,6 +141,7 @@ def __init__(
146141
self.dirty = dirty
147142
self.preformatted = preformatted
148143
self.branch = branch
144+
self.config = config
149145

150146
@property
151147
def extra(self):
@@ -193,7 +189,14 @@ def _parse_tag(tag, preformatted, config):
193189

194190

195191
def meta(
196-
tag, distance=None, dirty=False, node=None, preformatted=False, config=None, **kw
192+
tag,
193+
distance=None,
194+
dirty=False,
195+
node=None,
196+
preformatted=False,
197+
branch=None,
198+
config=None,
199+
**kw
197200
):
198201
if not config:
199202
warnings.warn(
@@ -203,7 +206,9 @@ def meta(
203206
parsed_version = _parse_tag(tag, preformatted, config)
204207
trace("version", tag, "->", parsed_version)
205208
assert parsed_version is not None, "cant parse version %s" % tag
206-
return ScmVersion(parsed_version, distance, node, dirty, preformatted, **kw)
209+
return ScmVersion(
210+
parsed_version, distance, node, dirty, preformatted, branch, config, **kw
211+
)
207212

208213

209214
def guess_next_version(tag_version):
@@ -238,12 +243,14 @@ def guess_next_dev_version(version):
238243

239244

240245
def guess_next_simple_semver(version, retain, increment=True):
241-
parts = map(int, str(version).split("."))
242-
parts = _pad(parts, retain, 0)
246+
parts = [int(i) for i in str(version).split(".")[:retain]]
247+
while len(parts) < retain:
248+
parts.append(0)
243249
if increment:
244250
parts[-1] += 1
245-
parts = _pad(parts, SEMVER_LEN, 0)
246-
return ".".join(map(str, parts))
251+
while len(parts) < SEMVER_LEN:
252+
parts.append(0)
253+
return ".".join(str(i) for i in parts)
247254

248255

249256
def simplified_semver_version(version):
@@ -260,6 +267,25 @@ def simplified_semver_version(version):
260267
)
261268

262269

270+
def release_branch_semver(version):
271+
if version.exact:
272+
return version.format_with("{tag}")
273+
if version.branch is not None:
274+
# Does the branch name (stripped of namespace) parse as a version?
275+
branch_ver = _parse_version_tag(version.branch.split("/")[-1], version.config)
276+
if branch_ver is not None:
277+
# Does the branch version up to the minor part match the tag? If not it
278+
# might be like, an issue number or something and not a version number, so
279+
# we only want to use it if it matches.
280+
tag_ver_up_to_minor = str(version.tag).split(".")[:SEMVER_MINOR]
281+
branch_ver_up_to_minor = branch_ver["version"].split(".")[:SEMVER_MINOR]
282+
if branch_ver_up_to_minor == tag_ver_up_to_minor:
283+
# We're in a release/maintenance branch, next is a patch/rc/beta bump:
284+
return version.format_next_version(guess_next_version)
285+
# We're in a development branch, next is a minor bump:
286+
return version.format_next_version(guess_next_simple_semver, retain=SEMVER_MINOR)
287+
288+
263289
def _format_local_with_time(version, time_format):
264290

265291
if version.exact or version.node is None:

testing/test_version.py

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import pytest
22
from setuptools_scm.config import Configuration
3-
from setuptools_scm.version import meta, simplified_semver_version, tags_to_versions
3+
from setuptools_scm.version import (
4+
meta,
5+
simplified_semver_version,
6+
release_branch_semver,
7+
tags_to_versions,
8+
)
49

510

611
c = Configuration()
@@ -43,6 +48,42 @@ def test_next_semver(version, expected_next):
4348
assert computed == expected_next
4449

4550

51+
@pytest.mark.parametrize(
52+
"version, expected_next",
53+
[
54+
pytest.param(meta("1.0.0", config=c), "1.0.0", id="exact"),
55+
pytest.param(
56+
meta("1.0.0", distance=2, branch="master", config=c),
57+
"1.1.0.dev2",
58+
id="development_branch",
59+
),
60+
pytest.param(
61+
meta("1.0.0rc1", distance=2, branch="master", config=c),
62+
"1.1.0.dev2",
63+
id="development_branch_release_candidate",
64+
),
65+
pytest.param(
66+
meta("1.0.0", distance=2, branch="maintenance/1.0.x", config=c),
67+
"1.0.1.dev2",
68+
id="release_branch_legacy_version",
69+
),
70+
pytest.param(
71+
meta("1.0.0", distance=2, branch="release-1.0", config=c),
72+
"1.0.1.dev2",
73+
id="release_branch_with_prefix",
74+
),
75+
pytest.param(
76+
meta("1.0.0", distance=2, branch="bugfix/3434", config=c),
77+
"1.1.0.dev2",
78+
id="false_positive_release_branch",
79+
),
80+
],
81+
)
82+
def test_next_release_branch_semver(version, expected_next):
83+
computed = release_branch_semver(version)
84+
assert computed == expected_next
85+
86+
4687
@pytest.mark.parametrize(
4788
"tag, expected",
4889
[

0 commit comments

Comments
 (0)