Skip to content

Commit 67eea3d

Browse files
committed
Fixed tag version extraction.
The output of `git describe` uses `-` as a delimiter. Parsing tags caused splits in the parsing of version numbers. This joins all the remaining parts of the `git describe` with a `-`.
1 parent 0386073 commit 67eea3d

File tree

4 files changed

+134
-4
lines changed

4 files changed

+134
-4
lines changed

bumpversion/scm.py

+6-4
Original file line numberDiff line numberDiff line change
@@ -132,10 +132,12 @@ def get_version_from_tag(cls, tag: str, tag_name: str, parse_pattern: str) -> Op
132132
"""Return the version from a tag."""
133133
version_pattern = parse_pattern.replace("\\\\", "\\")
134134
version_pattern, regex_flags = extract_regex_flags(version_pattern)
135-
rep = tag_name.replace("{new_version}", f"(?P<current_version>{version_pattern})")
136-
rep = f"{regex_flags}{rep}"
135+
parts = tag_name.split("{new_version}", maxsplit=1)
136+
prefix = parts[0]
137+
suffix = parts[1]
138+
rep = f"{regex_flags}{re.escape(prefix)}(?P<current_version>{version_pattern}){re.escape(suffix)}"
137139
tag_regex = re.compile(rep)
138-
return match["current_version"] if (match := tag_regex.match(tag)) else None
140+
return match["current_version"] if (match := tag_regex.search(tag)) else None
139141

140142
@classmethod
141143
def commit_to_scm(
@@ -286,7 +288,7 @@ def latest_tag_info(cls, tag_name: str, parse_pattern: str) -> SCMInfo:
286288

287289
info.commit_sha = describe_out.pop().lstrip("g")
288290
info.distance_to_latest_tag = int(describe_out.pop())
289-
version = cls.get_version_from_tag(describe_out[-1], tag_name, parse_pattern)
291+
version = cls.get_version_from_tag("-".join(describe_out), tag_name, parse_pattern)
290292
info.current_version = version or "-".join(describe_out).lstrip("v")
291293

292294
return info

tests/test_cli/test_bump.py

+57
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import traceback
66
from datetime import datetime
77
from pathlib import Path
8+
from textwrap import dedent
89

910
import pytest
1011
from pytest import param
@@ -255,3 +256,59 @@ def test_ignores_missing_files_with_option(tmp_path, fixtures_path, runner):
255256
print(traceback.print_exception(result.exc_info[1]))
256257

257258
assert result.exit_code == 0
259+
260+
261+
def test_dash_in_tag_matches_current_version(git_repo, runner, caplog):
262+
import logging
263+
264+
caplog.set_level(logging.DEBUG, logger="bumpversion")
265+
266+
repo_path: Path = git_repo
267+
with inside_dir(repo_path):
268+
# Arrange
269+
config_toml = dedent(
270+
"""
271+
[tool.bumpversion]
272+
current_version = "4.0.3-beta+build.1047"
273+
parse = "(?P<major>\\\\d+)\\\\.(?P<minor>\\\\d+)\\\\.(?P<patch>\\\\d+)(-(?P<release>[0-9A-Za-z]+))?(\\\\+build\\\\.(?P<build>.[0-9A-Za-z]+))?"
274+
serialize = [
275+
"{major}.{minor}.{patch}-{release}+build.{build}",
276+
"{major}.{minor}.{patch}+build.{build}"
277+
]
278+
commit = true
279+
message = "Bump version: {current_version} -> {new_version}"
280+
tag = false
281+
tag_name = "app/{new_version}"
282+
tag_message = "Version app/{new_version}"
283+
allow_dirty = false
284+
285+
[tool.bumpversion.parts.release]
286+
values = [
287+
"beta",
288+
"rc",
289+
"final"
290+
]
291+
optional_value = "final"
292+
293+
[tool.bumpversion.parts.build]
294+
first_value = 1000
295+
independent = true
296+
always_increment = true
297+
"""
298+
)
299+
repo_path.joinpath(".bumpversion.toml").write_text(config_toml, encoding="utf-8")
300+
subprocess.run(["git", "add", ".bumpversion.toml"], check=True)
301+
subprocess.run(["git", "commit", "-m", "added file"], check=True)
302+
subprocess.run(["git", "tag", "-a", "app/4.0.3-beta+build.1047", "-m", "added tag"], check=True)
303+
304+
# Act
305+
result: Result = runner.invoke(cli.cli, ["bump", "-vv", "--tag", "--commit", "patch"])
306+
307+
# Assert
308+
if result.exit_code != 0:
309+
import traceback
310+
311+
print(caplog.text)
312+
print(traceback.print_exception(result.exc_info[1]))
313+
314+
assert result.exit_code == 0

tests/test_config/test_init.py

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
"""Tests for the config.__init__ module."""
2+
3+
from dataclasses import dataclass
4+
from typing import Optional
5+
6+
from bumpversion.scm import SCMInfo
7+
from bumpversion.config import check_current_version
8+
9+
10+
@dataclass
11+
class MockConfig:
12+
"""Just includes the current_version and scm_info attributes."""
13+
14+
current_version: Optional[str]
15+
scm_info: SCMInfo
16+
17+
18+
class TestCheckCurrentVersion:
19+
"""
20+
Tests for the check_current_version function.
21+
22+
- tag may not match serialization
23+
- tag may not match current version in config
24+
"""
25+
26+
def test_uses_tag_when_missing_current_version(self):
27+
"""When the config does not have a current_version, the last tag is used."""
28+
# Assemble
29+
scm_info = SCMInfo()
30+
scm_info.current_version = "1.2.3"
31+
config = MockConfig(current_version=None, scm_info=scm_info)
32+
33+
# Act
34+
result = check_current_version(config)
35+
36+
# Assert
37+
assert result == "1.2.3"

tests/test_scm.py

+34
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,40 @@
1212
from tests.conftest import get_config_data, inside_dir
1313

1414

15+
class TestpSourceCodeManager:
16+
"""Tests related to SourceCodeManager."""
17+
18+
class TestGetVersionFromTag:
19+
"""Tests for the get_version_from_tag classmethod."""
20+
21+
simple_pattern = r"(?P<major>\\d+)\\.(?P<minor>\\d+)\.(?P<patch>\\d+)"
22+
complex_pattern = r"(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)(-(?P<release>[0-9A-Za-z]+))?(\\+build\\.(?P<build>.[0-9A-Za-z]+))?"
23+
24+
@pytest.mark.parametrize(
25+
["tag", "tag_name", "parse_pattern", "expected"],
26+
[
27+
param("1.2.3", "{new_version}", simple_pattern, "1.2.3", id="no-prefix, no-suffix"),
28+
param("v/1.2.3", "v/{new_version}", simple_pattern, "1.2.3", id="prefix, no-suffix"),
29+
param("v/1.2.3/12345", "v/{new_version}/{something}", simple_pattern, "1.2.3", id="prefix, suffix"),
30+
param("1.2.3/2024-01-01", "{new_version}/{today}", simple_pattern, "1.2.3", id="no-prefix, suffix"),
31+
param(
32+
"app/4.0.3-beta+build.1047",
33+
"app/{new_version}",
34+
complex_pattern,
35+
"4.0.3-beta+build.1047",
36+
id="complex",
37+
),
38+
],
39+
)
40+
def returns_version_from_pattern(self, tag: str, tag_name: str, parse_pattern: str, expected: str) -> None:
41+
"""It properly returns the version from the tag."""
42+
# Act
43+
version = scm.SourceCodeManager.get_version_from_tag(tag, tag_name, parse_pattern)
44+
45+
# Assert
46+
assert version == expected
47+
48+
1549
def test_format_and_raise_error(git_repo: Path) -> None:
1650
"""The output formatting from called process error string works as expected."""
1751
with inside_dir(git_repo):

0 commit comments

Comments
 (0)