Skip to content

Commit 4c69298

Browse files
Merge pull request #240 from RonnyPfannschmidt/add-branch-names
add support for branch name based testing, add simplified semver support
2 parents 85443f8 + 8370c6c commit 4c69298

File tree

9 files changed

+128
-35
lines changed

9 files changed

+128
-35
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ htmlcov/
3737
.coverage
3838
.coverage.*
3939
.cache
40+
.pytest_cache
4041
nosetests.xml
4142
coverage.xml
4243
*,cover

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ def parse(root):
8787
[setuptools_scm.version_scheme]
8888
guess-next-dev = setuptools_scm.version:guess_next_dev_version
8989
post-release = setuptools_scm.version:postrelease_version
90+
python-simplified-semver = setuptools_scm.version:simplified_semver_version
9091
9192
[setuptools_scm.local_scheme]
9293
node-and-date = setuptools_scm.version:get_local_node_and_date

setuptools_scm/git.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,13 @@ def is_dirty(self):
3939
out, _, _ = self.do_ex("git status --porcelain --untracked-files=no")
4040
return bool(out)
4141

42+
def get_branch(self):
43+
branch, err, ret = self.do_ex("git rev-parse --abbrev-ref HEAD")
44+
if ret:
45+
trace("branch err", branch, err, ret)
46+
return
47+
return branch
48+
4249
def is_shallow(self):
4350
return isfile(join(self.path, '.git/shallow'))
4451

@@ -89,7 +96,7 @@ def parse(root, describe_command=DEFAULT_DESCRIBE, pre_parse=warn_on_shallow):
8996
if pre_parse:
9097
pre_parse(wd)
9198

92-
out, err, ret = do_ex(describe_command, root)
99+
out, err, ret = wd.do_ex(describe_command)
93100
if ret:
94101
# If 'git describe' failed, try to get the information otherwise.
95102
rev_node = wd.node()
@@ -103,6 +110,7 @@ def parse(root, describe_command=DEFAULT_DESCRIBE, pre_parse=warn_on_shallow):
103110
distance=wd.count_all_nodes(),
104111
node='g' + rev_node,
105112
dirty=dirty,
113+
branch=wd.get_branch(),
106114
)
107115

108116
# 'out' looks e.g. like 'v1.5.0-0-g4060507' or
@@ -115,10 +123,11 @@ def parse(root, describe_command=DEFAULT_DESCRIBE, pre_parse=warn_on_shallow):
115123

116124
tag, number, node = out.rsplit('-', 2)
117125
number = int(number)
126+
branch = wd.get_branch()
118127
if number:
119-
return meta(tag, distance=number, node=node, dirty=dirty)
128+
return meta(tag, distance=number, node=node, dirty=dirty, branch=branch)
120129
else:
121-
return meta(tag, node=node, dirty=dirty)
130+
return meta(tag, node=node, dirty=dirty, branch=branch)
122131

123132

124133
def list_files_in_archive(path):

setuptools_scm/hg.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
FILES_COMMAND = 'hg locate -I .'
66

77

8-
def _hg_tagdist_normalize_tagcommit(root, tag, dist, node):
8+
def _hg_tagdist_normalize_tagcommit(root, tag, dist, node, branch):
99
dirty = node.endswith('+')
1010
node = 'h' + node.strip('+')
1111

@@ -17,42 +17,44 @@ def _hg_tagdist_normalize_tagcommit(root, tag, dist, node):
1717
" and not tag({tag!r}))" # ignore the tagged commit itself
1818
).format(tag=tag)
1919
if tag != '0.0':
20-
commits = do(['hg', 'log', '-r', revset, '--template', '{node|short}'],
20+
commits = do(['hg', 'log', '-r', revset,
21+
'--template', '{node|short}'],
2122
root)
2223
else:
2324
commits = True
2425
trace('normalize', locals())
2526
if commits or dirty:
26-
return meta(tag, distance=dist, node=node, dirty=dirty)
27+
return meta(tag, distance=dist, node=node, dirty=dirty, branch=branch)
2728
else:
2829
return meta(tag)
2930

3031

3132
def parse(root):
3233
if not has_command('hg'):
3334
return
34-
identity_data = do('hg id -i -t', root).split()
35+
identity_data = do('hg id -i -b -t', root).split()
3536
if not identity_data:
3637
return
3738
node = identity_data.pop(0)
39+
branch = identity_data.pop(0)
3840
tags = tags_to_versions(identity_data)
3941
# filter tip in degraded mode on old setuptools
4042
tags = [x for x in tags if x != 'tip']
4143
dirty = node[-1] == '+'
4244
if tags:
43-
return meta(tags[0], dirty=dirty)
45+
return meta(tags[0], dirty=dirty, branch=branch)
4446

45-
if node.strip('+') == '0'*12:
47+
if node.strip('+') == '0' * 12:
4648
trace('initial node', root)
47-
return meta('0.0', dirty=dirty)
49+
return meta('0.0', dirty=dirty, branch=branch)
4850

4951
try:
5052
tag = get_latest_normalizable_tag(root)
5153
dist = get_graph_distance(root, tag)
5254
if tag == 'null':
5355
tag = '0.0'
5456
dist = int(dist) + 1
55-
return _hg_tagdist_normalize_tagcommit(root, tag, dist, node)
57+
return _hg_tagdist_normalize_tagcommit(root, tag, dist, node, branch)
5658
except ValueError:
5759
pass # unpacking failed, old hg
5860

setuptools_scm/version.py

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,23 @@
22
import datetime
33
import warnings
44
import re
5+
from itertools import chain, repeat, islice
6+
57
from .utils import trace
68

79
from pkg_resources import iter_entry_points
810

911
from pkg_resources import parse_version
1012

13+
SEMVER_MINOR = 2
14+
SEMVER_PATCH = 3
15+
SEMVER_LEN = 3
16+
17+
18+
def _pad(iterable, size, padding=None):
19+
padded = chain(iterable, repeat(padding))
20+
return list(islice(padded, size))
21+
1122

1223
def _get_version_class():
1324
modern_version = parse_version("1.0")
@@ -70,6 +81,7 @@ class ScmVersion(object):
7081
def __init__(self, tag_version,
7182
distance=None, node=None, dirty=False,
7283
preformatted=False,
84+
branch=None,
7385
**kw):
7486
if kw:
7587
trace("unknown args", kw)
@@ -82,6 +94,7 @@ def __init__(self, tag_version,
8294
self.extra = kw
8395
self.dirty = dirty
8496
self.preformatted = preformatted
97+
self.branch = branch
8598

8699
@property
87100
def exact(self):
@@ -90,18 +103,23 @@ def exact(self):
90103
def __repr__(self):
91104
return self.format_with(
92105
'<ScmVersion {tag} d={distance}'
93-
' n={node} d={dirty} x={extra}>')
106+
' n={node} d={dirty} b={branch} x={extra}>')
94107

95108
def format_with(self, fmt, **kw):
96109
return fmt.format(
97110
time=self.time,
98111
tag=self.tag, distance=self.distance,
99-
node=self.node, dirty=self.dirty, extra=self.extra, **kw)
112+
node=self.node, dirty=self.dirty, extra=self.extra,
113+
branch=self.branch, **kw)
100114

101115
def format_choice(self, clean_format, dirty_format, **kw):
102116
return self.format_with(
103117
dirty_format if self.dirty else clean_format, **kw)
104118

119+
def format_next_version(self, guess_next, fmt="{guessed}.dev{distance}", **kw):
120+
guessed = guess_next(self.tag, **kw)
121+
return self.format_with(fmt, guessed=guessed)
122+
105123

106124
def _parse_tag(tag, preformatted):
107125
if preformatted:
@@ -118,11 +136,9 @@ def meta(tag, distance=None, dirty=False, node=None, preformatted=False, **kw):
118136
return ScmVersion(tag, distance, node, dirty, preformatted, **kw)
119137

120138

121-
def guess_next_version(tag_version, distance):
139+
def guess_next_version(tag_version):
122140
version = _strip_local(str(tag_version))
123-
bumped = _bump_dev(version) or _bump_regex(version)
124-
suffix = '.dev%s' % distance
125-
return bumped + suffix
141+
return _bump_dev(version) or _bump_regex(version)
126142

127143

128144
def _strip_local(version_string):
@@ -140,15 +156,37 @@ def _bump_dev(version):
140156

141157

142158
def _bump_regex(version):
143-
prefix, tail = re.match('(.*?)(\d+)$', version).groups()
159+
prefix, tail = re.match(r'(.*?)(\d+)$', version).groups()
144160
return '%s%d' % (prefix, int(tail) + 1)
145161

146162

147163
def guess_next_dev_version(version):
148164
if version.exact:
149165
return version.format_with("{tag}")
150166
else:
151-
return guess_next_version(version.tag, version.distance)
167+
return version.format_next_version(guess_next_version)
168+
169+
170+
def guess_next_simple_semver(version, retain, increment=True):
171+
parts = map(int, str(version).split('.'))
172+
parts = _pad(parts, retain, 0)
173+
if increment:
174+
parts[-1] += 1
175+
parts = _pad(parts, SEMVER_LEN, 0)
176+
return '.'.join(map(str, parts))
177+
178+
179+
def simplified_semver_version(version):
180+
if version.exact:
181+
return guess_next_simple_semver(
182+
version.tag, retain=SEMVER_LEN, increment=False)
183+
else:
184+
if version.branch is not None and 'feature' in version.branch:
185+
return version.format_next_version(
186+
guess_next_simple_semver, retain=SEMVER_MINOR)
187+
else:
188+
return version.format_next_version(
189+
guess_next_simple_semver, retain=SEMVER_PATCH)
152190

153191

154192
def _format_local_with_time(version, time_format):

testing/test_functions.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@ def __format__(self, *k):
1414

1515

1616
@pytest.mark.parametrize('tag, expected', [
17-
('1.1', '1.2.dev0'),
18-
('1.2.dev', '1.2.dev0'),
19-
('1.1a2', '1.1a3.dev0'),
20-
('23.24.post2+deadbeef', '23.24.post3.dev0'),
21-
])
17+
('1.1', '1.2'),
18+
('1.2.dev', '1.2'),
19+
('1.1a2', '1.1a3'),
20+
('23.24.post2+deadbeef', '23.24.post3'),
21+
])
2222
def test_next_tag(tag, expected):
2323
version = pkg_resources.parse_version(tag)
24-
assert guess_next_version(version, 0) == expected
24+
assert guess_next_version(version) == expected
2525

2626

2727
VERSIONS = {

testing/test_git.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ def test_git_shallow_autocorrect(shallow_wd, recwarn):
9999
def test_find_files_stop_at_root_git(wd):
100100
wd.commit_testfile()
101101
wd.cwd.ensure('project/setup.cfg')
102-
assert integration.find_files(str(wd.cwd/'project')) == []
102+
assert integration.find_files(str(wd.cwd / 'project')) == []
103103

104104

105105
@pytest.mark.issue(128)
@@ -133,3 +133,13 @@ def test_git_archive_subdirectory(wd):
133133
wd('git add foobar')
134134
wd.commit()
135135
assert integration.find_files(str(wd.cwd)) == ['foobar/test1.txt']
136+
137+
138+
def test_git_feature_branch_increments_major(wd):
139+
wd.commit_testfile()
140+
wd("git tag 1.0.0")
141+
wd.commit_testfile()
142+
assert wd.get_version(version_scheme="python-simplified-semver").startswith("1.0.1")
143+
wd("git checkout -b feature/fun")
144+
wd.commit_testfile()
145+
assert wd.get_version(version_scheme="python-simplified-semver").startswith("1.1.0")

testing/test_mercurial.py

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ def wd(wd):
1818
'1.1.dev3+h000000000000': {
1919
'latesttag': '1.0',
2020
'latesttagdistance': '3',
21-
'node': '0'*20,
21+
'node': '0' * 20,
2222
},
2323
'0.0': {
24-
'node': '0'*20,
24+
'node': '0' * 20,
2525
},
2626
'1.2.2': {'tag': 'release-1.2.2'},
2727
'1.2.2.dev0': {'tag': 'release-1.2.2.dev'},
@@ -41,7 +41,7 @@ def test_archival_to_version(expected, data):
4141
def test_find_files_stop_at_root_hg(wd):
4242
wd.commit_testfile()
4343
wd.cwd.ensure('project/setup.cfg')
44-
assert integration.find_files(str(wd.cwd/'project')) == []
44+
assert integration.find_files(str(wd.cwd / 'project')) == []
4545

4646

4747
# XXX: better tests for tag prefixes
@@ -114,7 +114,7 @@ def test_parse_no_worktree(tmpdir):
114114
def version_1_0(wd):
115115
wd('hg branch default')
116116
wd.commit_testfile()
117-
wd('hg tag 1.0 -u test -d "0 0"')
117+
wd('hg tag 1.0.0 -u test -d "0 0"')
118118
return wd
119119

120120

@@ -131,14 +131,14 @@ def pre_merge_commit_after_tag(wd, version_1_0):
131131

132132
@pytest.mark.usefixtures("pre_merge_commit_after_tag")
133133
def test_version_bump_before_merge_commit(wd):
134-
assert wd.version.startswith('1.1.dev1+')
134+
assert wd.version.startswith('1.0.1.dev1+')
135135

136136

137137
@pytest.mark.issue(219)
138138
@pytest.mark.usefixtures("pre_merge_commit_after_tag")
139139
def test_version_bump_from_merge_commit(wd):
140140
wd.commit()
141-
assert wd.version.startswith('1.1.dev3+') # issue 219
141+
assert wd.version.startswith('1.0.1.dev3+') # issue 219
142142

143143

144144
@pytest.mark.usefixtures("version_1_0")
@@ -149,9 +149,9 @@ def test_version_bump_from_commit_including_hgtag_mods(wd):
149149
tagfile.write('0 0\n')
150150
wd.write('branchfile', 'branchtext')
151151
wd(wd.add_command)
152-
assert wd.version.startswith('1.1.dev1+') # bump from dirty version
152+
assert wd.version.startswith('1.0.1.dev1+') # bump from dirty version
153153
wd.commit() # commits both the testfile _and_ .hgtags
154-
assert wd.version.startswith('1.1.dev2+')
154+
assert wd.version.startswith('1.0.1.dev2+')
155155

156156

157157
@pytest.mark.issue(229)
@@ -161,4 +161,13 @@ def test_latest_tag_detection(wd):
161161
Note that will be superceded by the fix for pypa/setuptools_scm/issues/235
162162
"""
163163
wd('hg tag some-random-tag')
164-
assert wd.version == '1.0'
164+
assert wd.version == '1.0.0'
165+
166+
167+
@pytest.mark.usefixtures("version_1_0")
168+
def test_feature_branch_increments_major(wd):
169+
170+
wd.commit_testfile()
171+
assert wd.get_version(version_scheme="python-simplified-semver").startswith("1.0.1")
172+
wd("hg branch feature/fun")
173+
assert wd.get_version(version_scheme="python-simplified-semver").startswith("1.1.0")

testing/test_version.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import pytest
2+
from setuptools_scm.version import meta, simplified_semver_version
3+
4+
5+
@pytest.mark.parametrize('version, expected_next', [
6+
pytest.param(meta("1.0.0"), "1.0.0", id='exact'),
7+
8+
pytest.param(meta("1.0"), "1.0.0", id='short_tag'),
9+
pytest.param(meta("1.0.0", distance=2, branch='default'), "1.0.1.dev2",
10+
id='normal_branch'),
11+
12+
pytest.param(meta("1.0", distance=2, branch='default'), "1.0.1.dev2",
13+
id='normal_branch_short_tag'),
14+
pytest.param(meta("1.0.0", distance=2, branch='feature'), "1.1.0.dev2",
15+
id='feature_branch'),
16+
pytest.param(meta("1.0", distance=2, branch='feature'), "1.1.0.dev2",
17+
id='feature_branch_short_tag'),
18+
pytest.param(meta("1.0.0", distance=2, branch='features/test'), "1.1.0.dev2",
19+
id='feature_in_branch'),
20+
])
21+
def test_next_semver(version, expected_next):
22+
computed = simplified_semver_version(version)
23+
assert computed == expected_next

0 commit comments

Comments
 (0)