diff --git a/bumpversion/scm.py b/bumpversion/scm.py index 7e3ff964..5e4d65e1 100644 --- a/bumpversion/scm.py +++ b/bumpversion/scm.py @@ -132,10 +132,12 @@ def get_version_from_tag(cls, tag: str, tag_name: str, parse_pattern: str) -> Op """Return the version from a tag.""" version_pattern = parse_pattern.replace("\\\\", "\\") version_pattern, regex_flags = extract_regex_flags(version_pattern) - rep = tag_name.replace("{new_version}", f"(?P{version_pattern})") - rep = f"{regex_flags}{rep}" + parts = tag_name.split("{new_version}", maxsplit=1) + prefix = parts[0] + suffix = parts[1] + rep = f"{regex_flags}{re.escape(prefix)}(?P{version_pattern}){re.escape(suffix)}" tag_regex = re.compile(rep) - return match["current_version"] if (match := tag_regex.match(tag)) else None + return match["current_version"] if (match := tag_regex.search(tag)) else None @classmethod def commit_to_scm( @@ -286,7 +288,7 @@ def latest_tag_info(cls, tag_name: str, parse_pattern: str) -> SCMInfo: info.commit_sha = describe_out.pop().lstrip("g") info.distance_to_latest_tag = int(describe_out.pop()) - version = cls.get_version_from_tag(describe_out[-1], tag_name, parse_pattern) + version = cls.get_version_from_tag("-".join(describe_out), tag_name, parse_pattern) info.current_version = version or "-".join(describe_out).lstrip("v") return info diff --git a/tests/test_cli/test_bump.py b/tests/test_cli/test_bump.py index cd99d2df..9406a39e 100644 --- a/tests/test_cli/test_bump.py +++ b/tests/test_cli/test_bump.py @@ -5,6 +5,7 @@ import traceback from datetime import datetime from pathlib import Path +from textwrap import dedent import pytest from pytest import param @@ -255,3 +256,59 @@ def test_ignores_missing_files_with_option(tmp_path, fixtures_path, runner): print(traceback.print_exception(result.exc_info[1])) assert result.exit_code == 0 + + +def test_dash_in_tag_matches_current_version(git_repo, runner, caplog): + import logging + + caplog.set_level(logging.DEBUG, logger="bumpversion") + + repo_path: Path = git_repo + with inside_dir(repo_path): + # Arrange + config_toml = dedent( + """ + [tool.bumpversion] + current_version = "4.0.3-beta+build.1047" + parse = "(?P\\\\d+)\\\\.(?P\\\\d+)\\\\.(?P\\\\d+)(-(?P[0-9A-Za-z]+))?(\\\\+build\\\\.(?P.[0-9A-Za-z]+))?" + serialize = [ + "{major}.{minor}.{patch}-{release}+build.{build}", + "{major}.{minor}.{patch}+build.{build}" + ] + commit = true + message = "Bump version: {current_version} -> {new_version}" + tag = false + tag_name = "app/{new_version}" + tag_message = "Version app/{new_version}" + allow_dirty = false + + [tool.bumpversion.parts.release] + values = [ + "beta", + "rc", + "final" + ] + optional_value = "final" + + [tool.bumpversion.parts.build] + first_value = 1000 + independent = true + always_increment = true + """ + ) + repo_path.joinpath(".bumpversion.toml").write_text(config_toml, encoding="utf-8") + subprocess.run(["git", "add", ".bumpversion.toml"], check=True) + subprocess.run(["git", "commit", "-m", "added file"], check=True) + subprocess.run(["git", "tag", "-a", "app/4.0.3-beta+build.1047", "-m", "added tag"], check=True) + + # Act + result: Result = runner.invoke(cli.cli, ["bump", "-vv", "--tag", "--commit", "patch"]) + + # Assert + if result.exit_code != 0: + import traceback + + print(caplog.text) + print(traceback.print_exception(result.exc_info[1])) + + assert result.exit_code == 0 diff --git a/tests/test_config/test_init.py b/tests/test_config/test_init.py index e69de29b..a723a6b5 100644 --- a/tests/test_config/test_init.py +++ b/tests/test_config/test_init.py @@ -0,0 +1,37 @@ +"""Tests for the config.__init__ module.""" + +from dataclasses import dataclass +from typing import Optional + +from bumpversion.scm import SCMInfo +from bumpversion.config import check_current_version + + +@dataclass +class MockConfig: + """Just includes the current_version and scm_info attributes.""" + + current_version: Optional[str] + scm_info: SCMInfo + + +class TestCheckCurrentVersion: + """ + Tests for the check_current_version function. + + - tag may not match serialization + - tag may not match current version in config + """ + + def test_uses_tag_when_missing_current_version(self): + """When the config does not have a current_version, the last tag is used.""" + # Assemble + scm_info = SCMInfo() + scm_info.current_version = "1.2.3" + config = MockConfig(current_version=None, scm_info=scm_info) + + # Act + result = check_current_version(config) + + # Assert + assert result == "1.2.3" diff --git a/tests/test_scm.py b/tests/test_scm.py index f4451b03..2ffdd80a 100644 --- a/tests/test_scm.py +++ b/tests/test_scm.py @@ -12,6 +12,40 @@ from tests.conftest import get_config_data, inside_dir +class TestpSourceCodeManager: + """Tests related to SourceCodeManager.""" + + class TestGetVersionFromTag: + """Tests for the get_version_from_tag classmethod.""" + + simple_pattern = r"(?P\\d+)\\.(?P\\d+)\.(?P\\d+)" + complex_pattern = r"(?P\\d+)\\.(?P\\d+)\\.(?P\\d+)(-(?P[0-9A-Za-z]+))?(\\+build\\.(?P.[0-9A-Za-z]+))?" + + @pytest.mark.parametrize( + ["tag", "tag_name", "parse_pattern", "expected"], + [ + param("1.2.3", "{new_version}", simple_pattern, "1.2.3", id="no-prefix, no-suffix"), + param("v/1.2.3", "v/{new_version}", simple_pattern, "1.2.3", id="prefix, no-suffix"), + param("v/1.2.3/12345", "v/{new_version}/{something}", simple_pattern, "1.2.3", id="prefix, suffix"), + param("1.2.3/2024-01-01", "{new_version}/{today}", simple_pattern, "1.2.3", id="no-prefix, suffix"), + param( + "app/4.0.3-beta+build.1047", + "app/{new_version}", + complex_pattern, + "4.0.3-beta+build.1047", + id="complex", + ), + ], + ) + def returns_version_from_pattern(self, tag: str, tag_name: str, parse_pattern: str, expected: str) -> None: + """It properly returns the version from the tag.""" + # Act + version = scm.SourceCodeManager.get_version_from_tag(tag, tag_name, parse_pattern) + + # Assert + assert version == expected + + def test_format_and_raise_error(git_repo: Path) -> None: """The output formatting from called process error string works as expected.""" with inside_dir(git_repo):