From 1ebfc11fedc851775aaf35e1412961f572e2caff Mon Sep 17 00:00:00 2001 From: Mario Jaeckle Date: Thu, 7 Apr 2022 14:46:41 +0300 Subject: [PATCH 1/6] feat: improve prerelease determination and version regex --- semantic_release/cli.py | 9 ++-- semantic_release/history/__init__.py | 78 ++++++++++++---------------- semantic_release/vcs_helpers.py | 28 +++------- tests/history/test_version.py | 32 +++++------- tests/test_cli.py | 31 ++++++----- tests/test_vcs_helpers.py | 62 ++++++---------------- 6 files changed, 90 insertions(+), 150 deletions(-) diff --git a/semantic_release/cli.py b/semantic_release/cli.py index bed36b18a..087e947b1 100644 --- a/semantic_release/cli.py +++ b/semantic_release/cli.py @@ -98,7 +98,7 @@ def print_version(*, current=False, force_level=None, prerelease=False, **kwargs Print the current or new version to standard output. """ try: - current_version = get_current_version(prerelease) + current_version = get_current_version() except GitError as e: print(str(e), file=sys.stderr) return False @@ -130,12 +130,13 @@ def version(*, retry=False, noop=False, force_level=None, prerelease=False, **kw # Get the current version number try: - current_version = get_current_version(prerelease) + current_version = get_current_version() logger.info(f"Current version: {current_version}") except GitError as e: logger.error(str(e)) return False # Find what the new version number should be + # TODO: depending on prerelease or not we have to do this differently level_bump = evaluate_version_bump(current_version, force_level) new_version = get_new_version(current_version, level_bump, prerelease) @@ -207,7 +208,7 @@ def changelog(*, unreleased=False, noop=False, post=False, prerelease=False, **k :raises ImproperConfigurationError: if there is no current version """ - current_version = get_current_version(prerelease) + current_version = get_current_version() if current_version is None: raise ImproperConfigurationError( "Unable to get the current version. " @@ -245,7 +246,7 @@ def publish( retry: bool = False, noop: bool = False, prerelease: bool = False, **kwargs ): """Run the version task, then push to git and upload to an artifact repository / GitHub Releases.""" - current_version = get_current_version(prerelease) + current_version = get_current_version() verbose = logger.isEnabledFor(logging.DEBUG) if retry: diff --git a/semantic_release/history/__init__.py b/semantic_release/history/__init__.py index fa9e6f67c..3357cd757 100644 --- a/semantic_release/history/__init__.py +++ b/semantic_release/history/__init__.py @@ -25,6 +25,10 @@ logger = logging.getLogger(__name__) +version_pattern = f"(\d+.\d+.\d+(-{config.get('prerelease_tag')}.\d+)?)" +version_regex = rf"{version_pattern}" + + class VersionDeclaration(ABC): def __init__(self, path: Union[str, Path]): self.path = Path(path) @@ -46,7 +50,7 @@ def from_variable(config_str: str): """ path, variable = config_str.split(":", 1) pattern = ( - rf'{variable} *[:=] *["\']{PatternVersionDeclaration.version_regex}["\']' + rf'{variable} *[:=] *["\']{version_regex}["\']' ) return PatternVersionDeclaration(path, pattern) @@ -57,7 +61,7 @@ def from_pattern(config_str: str): regular expression matching the version number. """ path, pattern = config_str.split(":", 1) - pattern = pattern.format(version=PatternVersionDeclaration.version_regex) + pattern = pattern.format(version=version_regex) return PatternVersionDeclaration(path, pattern) @abstractmethod @@ -116,8 +120,6 @@ class PatternVersionDeclaration(VersionDeclaration): function to create the version patterns specified in the config files. """ - version_regex = r"(\d+\.\d+(?:\.\d+)?)" - # The pattern should be a regular expression with a single group, # containing the version to replace. def __init__(self, path: str, pattern: str): @@ -176,18 +178,14 @@ def swap_version(m): self.path.write_text(new_content) -def get_prerelease_pattern() -> str: - return "-" + config.get("prerelease_tag") + "." - - @LoggedFunction(logger) -def get_current_version_by_tag(omit_pattern=None) -> str: +def get_current_version_by_tag() -> str: """ Find the current version of the package in the current working directory using git tags. :return: A string with the version number or 0.0.0 on failure. """ - version = get_last_version(omit_pattern=omit_pattern) + version = get_last_version(pattern=version_pattern) if version: return version @@ -196,7 +194,7 @@ def get_current_version_by_tag(omit_pattern=None) -> str: @LoggedFunction(logger) -def get_current_version_by_config_file(omit_pattern=None) -> str: +def get_current_version_by_config_file() -> str: """ Get current version from the version variable defined in the configuration. @@ -220,20 +218,16 @@ def get_current_version_by_config_file(omit_pattern=None) -> str: return version -def get_current_version(prerelease_version: bool = False) -> str: +def get_current_version() -> str: """ Get current version from tag or version variable, depending on configuration. - This will not return prerelease versions. :return: A string with the current version number """ - omit_pattern = None if prerelease_version else get_prerelease_pattern() if config.get("version_source") in ["tag", "tag_only"]: - return get_current_version_by_tag(omit_pattern) - current_version = get_current_version_by_config_file(omit_pattern) - if omit_pattern and omit_pattern in current_version: - return get_previous_version(current_version) - return current_version + return get_current_version_by_tag() + else: + return get_current_version_by_config_file() @LoggedFunction(logger) @@ -249,34 +243,32 @@ def get_new_version( :param prerelease: Should the version bump be marked as a prerelease :return: A string with the next version number. """ - if not level_bump: - logger.debug("No bump requested, using input version") + current_version_info = semver.VersionInfo.parse(current_version) + if prerelease and current_version_info.prerelease: + logger.debug("NextVersion: Prerelease from prerelease version (level_bump ignored)") + new_version = str( + semver.VersionInfo.parse(current_version).bump_prerelease(config.get("prerelease_tag")) + ) + elif prerelease and not current_version_info.prerelease: + logger.debug("NextVersion: Prerelease from normal version (level_bump used)") + bump = level_bump or "patch" + new_version = str( + semver.VersionInfo.parse(current_version).next_version(part=bump).bump_prerelease(config.get("prerelease_tag")) + ) + elif not level_bump: + logger.debug("NextVersion: Normal version bump with no level_bump - version unchanged") new_version = current_version else: + logger.debug("NextVersion: Normal version bump (level_bump ignored)") new_version = str( semver.VersionInfo.parse(current_version).next_version(part=level_bump) ) - if prerelease: - logger.debug("Prerelease requested") - potentialy_prereleased_current_version = get_current_version( - prerelease_version=True - ) - if get_prerelease_pattern() in potentialy_prereleased_current_version: - logger.debug("Previouse prerelease detected, increment prerelease version") - prerelease_num = ( - int(potentialy_prereleased_current_version.split(".")[-1]) + 1 - ) - else: - logger.debug("No previouse prerelease detected, starting from 0") - prerelease_num = 0 - new_version = new_version + get_prerelease_pattern() + str(prerelease_num) - return new_version @LoggedFunction(logger) -def get_previous_version(version: str, omit_pattern: str = None) -> Optional[str]: +def get_previous_version(version: str) -> Optional[str]: """ Return the version prior to the given version. @@ -292,16 +284,12 @@ def get_previous_version(version: str, omit_pattern: str = None) -> Optional[str continue if found_version: - if omit_pattern and omit_pattern in commit_message: - continue - matches = re.match(r"v?(\d+.\d+.\d+)", commit_message) - if matches: + match = re.search(rf"{version_pattern}", commit_message) + if match: logger.debug(f"Version matches regex {commit_message}") - return matches.group(1).strip() + return match.group(1).strip() - return get_last_version( - [version, get_formatted_tag(version)], omit_pattern=omit_pattern - ) + return get_last_version(pattern=version_pattern, skip_tags=[version, get_formatted_tag(version)]) @LoggedFunction(logger) diff --git a/semantic_release/vcs_helpers.py b/semantic_release/vcs_helpers.py index a58d98480..c2905b174 100644 --- a/semantic_release/vcs_helpers.py +++ b/semantic_release/vcs_helpers.py @@ -63,7 +63,7 @@ def get_commit_log(from_rev=None): @check_repo @LoggedFunction(logger) -def get_last_version(skip_tags=None, omit_pattern=None) -> Optional[str]: +def get_last_version(pattern, skip_tags=None) -> Optional[str]: """ Find the latest version using repo tags. @@ -77,29 +77,13 @@ def version_finder(tag): return tag.commit.committed_date for i in sorted(repo.tags, reverse=True, key=version_finder): - match = re.search(r"\d+\.\d+\.\d+", i.name) - if match: - # check if the omit pattern is present in the tag (e.g. -beta for pre-release tags) - if omit_pattern and omit_pattern in i.name: - continue - if i.name not in skip_tags: - return match.group(0) # Return only numeric vesion like 1.2.3 - - return None + if i.name in skip_tags: + continue + match = re.search(rf"{pattern}", i.name) + if match: + return match.group(0).strip() # Return only numeric vesion like 1.2.3 -@check_repo -@LoggedFunction(logger) -def get_version_from_tag(tag_name: str) -> Optional[str]: - """ - Get the git commit hash corresponding to a tag name. - - :param tag_name: Name of the git tag (i.e. 'v1.0.0') - :return: sha1 hash of the commit - """ - for i in repo.tags: - if i.name == tag_name: - return i.commit.hexsha return None diff --git a/tests/history/test_version.py b/tests/history/test_version.py index 0b4e2622e..8a35ed9c9 100644 --- a/tests/history/test_version.py +++ b/tests/history/test_version.py @@ -95,7 +95,7 @@ def test_should_return_correct_version_from_prerelease(self): lambda: [("211", "0.10.0"), ("13", "0.10.0-beta"), ("13", "0.9.0")], ) def test_should_return_correct_version_skip_prerelease(self): - assert get_previous_version("0.10.0-beta", omit_pattern="-beta") == "0.9.0" + assert get_previous_version("0.10.0-beta") == "0.9.0" class TestGetNewVersion: @@ -121,16 +121,12 @@ def test_none_bump(self): assert get_new_version("1.0.0", None) == "1.0.0" def test_prerelease(self): - assert get_new_version("1.0.0", None, True) == "1.0.0-beta.0" - assert get_new_version("1.0.0", "major", True) == "2.0.0-beta.0" - assert get_new_version("1.0.0", "minor", True) == "1.1.0-beta.0" - assert get_new_version("1.0.0", "patch", True) == "1.0.1-beta.0" - - def test_prerelease_bump(self, mocker): - mocker.patch( - "semantic_release.history.get_current_version", return_value="1.0.0-beta.0" - ) - assert get_new_version("1.0.0", None, True) == "1.0.0-beta.1" + assert get_new_version("1.0.0", None, True) == "1.0.1-beta.1" + assert get_new_version("1.0.0-beta.1", None, True) == "1.0.0-beta.2" + assert get_new_version("1.0.0-beta.1", "minor", True) == "1.0.0-beta.2" + assert get_new_version("1.0.0", "major", True) == "2.0.0-beta.1" + assert get_new_version("1.0.0", "minor", True) == "1.1.0-beta.1" + assert get_new_version("1.0.0", "patch", True) == "1.0.1-beta.1" @mock.patch( @@ -153,7 +149,7 @@ class TestVersionPattern: ( "path:__version__", Path("path"), - r'__version__ *[:=] *["\'](\d+\.\d+(?:\.\d+)?)["\']', + r'__version__ *[:=] *["\'](\d+.\d+.\d+(-beta.\d+)?)["\']', ), ], ) @@ -166,7 +162,7 @@ def test_from_variable(self, str, path, pattern): "str, path, pattern", [ ("path:pattern", Path("path"), r"pattern"), - ("path:Version: {version}", Path("path"), r"Version: (\d+\.\d+(?:\.\d+)?)"), + ("path:Version: {version}", Path("path"), r"Version: (\d+.\d+.\d+(-beta.\d+)?)"), ], ) def test_from_pattern(self, str, path, pattern): @@ -353,7 +349,7 @@ def test_toml_replace(self, tmp_path, key, old_content, new_content): version_variable = "path:__version__" """, patterns=[ - (Path("path"), r'__version__ *[:=] *["\'](\d+\.\d+(?:\.\d+)?)["\']'), + (Path("path"), r'__version__ *[:=] *["\'](\d+.\d+.\d+(-beta.\d+)?)["\']'), ], ), dict( @@ -362,8 +358,8 @@ def test_toml_replace(self, tmp_path, key, old_content, new_content): version_variable = "path1:var1,path2:var2" """, patterns=[ - (Path("path1"), r'var1 *[:=] *["\'](\d+\.\d+(?:\.\d+)?)["\']'), - (Path("path2"), r'var2 *[:=] *["\'](\d+\.\d+(?:\.\d+)?)["\']'), + (Path("path1"), r'var1 *[:=] *["\'](\d+.\d+.\d+(-beta.\d+)?)["\']'), + (Path("path2"), r'var2 *[:=] *["\'](\d+.\d+.\d+(-beta.\d+)?)["\']'), ], ), dict( @@ -375,8 +371,8 @@ def test_toml_replace(self, tmp_path, key, old_content, new_content): ] """, patterns=[ - (Path("path1"), r'var1 *[:=] *["\'](\d+\.\d+(?:\.\d+)?)["\']'), - (Path("path2"), r'var2 *[:=] *["\'](\d+\.\d+(?:\.\d+)?)["\']'), + (Path("path1"), r'var1 *[:=] *["\'](\d+.\d+.\d+(-beta.\d+)?)["\']'), + (Path("path2"), r'var2 *[:=] *["\'](\d+.\d+.\d+(-beta.\d+)?)["\']'), ], ), dict( diff --git a/tests/test_cli.py b/tests/test_cli.py index 1cd2e0735..bea0d08c2 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -51,7 +51,7 @@ def test_version_by_commit_should_call_correct_functions(mocker): version() - mock_current_version.assert_called_once_with(False) + mock_current_version.assert_called_once() mock_evaluate_bump.assert_called_once_with("1.2.3", None) mock_new_version.assert_called_once_with("1.2.3", "major", False) mock_set_new_version.assert_called_once_with("2.0.0") @@ -86,7 +86,7 @@ def test_version_by_tag_with_commit_version_number_should_call_correct_functions version() - mock_current_version.assert_called_once_with(False) + mock_current_version.assert_called_once() mock_evaluate_bump.assert_called_once_with("1.2.3", None) mock_new_version.assert_called_once_with("1.2.3", "major", False) mock_set_new_version.assert_called_once_with("2.0.0") @@ -148,7 +148,7 @@ def test_version_by_tag_should_call_correct_functions(mocker): version() - mock_current_version.assert_called_once_with(False) + mock_current_version.assert_called_once() mock_evaluate_bump.assert_called_once_with("1.2.3", None) mock_new_version.assert_called_once_with("1.2.3", "major", False) mock_set_new_version.assert_called_once_with("2.0.0") @@ -201,7 +201,7 @@ def test_version_by_commit_without_tag_should_call_correct_functions(mocker): version() - mock_current_version.assert_called_once_with(False) + mock_current_version.assert_called_once() mock_evaluate_bump.assert_called_once_with("1.2.3", None) mock_new_version.assert_called_once_with("1.2.3", "major", False) mock_set_new_version.assert_called_once_with("2.0.0") @@ -372,7 +372,7 @@ def test_print_version_no_change(mocker, runner, capsys): assert outerr.out == "" assert outerr.err == "No release will be made.\n" - mock_current_version.assert_called_once_with(False) + mock_current_version.assert_called_once() mock_evaluate_bump.assert_called_once_with("1.2.3", None) mock_new_version.assert_called_once_with("1.2.3", None, False) @@ -390,7 +390,7 @@ def test_print_version_change(mocker, runner, capsys): assert outerr.out == "1.3.0" assert outerr.err == "" - mock_current_version.assert_called_once_with(False) + mock_current_version.assert_called_once() mock_evaluate_bump.assert_called_once_with("1.2.3", None) @@ -404,10 +404,10 @@ def test_print_version_change_prerelease(mocker, runner, capsys): print_version(prerelease=True) outerr = capsys.readouterr() - assert outerr.out == "1.3.0-beta.0" + assert outerr.out == "1.3.0-beta.1" assert outerr.err == "" - mock_current_version.assert_called_once_with(True) + mock_current_version.assert_called_once() mock_evaluate_bump.assert_called_once_with("1.2.3", None) @@ -427,7 +427,6 @@ def test_print_version_change_prerelease_bump(mocker, runner, capsys): assert outerr.out == "1.3.0-beta.1" assert outerr.err == "" - mock_current_version.assert_called_once_with(prerelease_version=True) mock_evaluate_bump.assert_called_once_with("1.2.3", None) @@ -444,7 +443,7 @@ def test_print_version_force_major(mocker, runner, capsys): assert outerr.out == "2.0.0" assert outerr.err == "" - mock_current_version.assert_called_once_with(False) + mock_current_version.assert_called_once() mock_evaluate_bump.assert_called_once_with("1.2.3", "major") @@ -458,7 +457,7 @@ def test_print_version_force_major_prerelease(mocker, runner, capsys): print_version(force_level="major", prerelease=True) outerr = capsys.readouterr() - assert outerr.out == "2.0.0-beta.0" + assert outerr.out == "2.0.0-beta.1" assert outerr.err == "" mock_current_version.assert_called_once() @@ -481,7 +480,7 @@ def test_version_no_change(mocker, runner): version() - mock_current_version.assert_called_once_with(False) + mock_current_version.assert_called_once() mock_evaluate_bump.assert_called_once_with("1.2.3", None) mock_new_version.assert_called_once_with("1.2.3", None, False) assert not mock_set_new_version.called @@ -609,7 +608,7 @@ def test_version_retry(mocker): result = version(noop=False, retry=True, force_level=False) assert result - mock_get_current.assert_called_once_with(False) + mock_get_current.assert_called_once() mock_evaluate_bump.assert_called_once_with("current", False) mock_get_new.assert_called_once_with("current", "patch", False) @@ -904,7 +903,7 @@ def test_publish_retry_version_fail(mocker): publish(noop=False, retry=True, force_level=False) - mock_get_current.assert_called_once_with(False) + mock_get_current.assert_called_once() mock_get_previous.assert_called_once_with("current") mock_get_owner_name.assert_called_once_with() mock_ci_check.assert_called() @@ -950,7 +949,7 @@ def test_publish_bad_token(mocker): publish(noop=False, retry=True, force_level=False) - mock_get_current.assert_called_once_with(False) + mock_get_current.assert_called_once() mock_get_previous.assert_called_once_with("current") mock_get_owner_name.assert_called_once_with() mock_ci_check.assert_called() @@ -1024,7 +1023,7 @@ def test_publish_giterror_when_posting(mocker): publish(noop=False, retry=False, force_level=False) - mock_get_current.assert_called_once_with(False) + mock_get_current.assert_called_once() mock_evaluate.assert_called_once_with("current", False) mock_get_new.assert_called_once_with("current", "patch", False) mock_get_owner_name.assert_called_once_with() diff --git a/tests/test_vcs_helpers.py b/tests/test_vcs_helpers.py index 1fbabf950..9aee80385 100644 --- a/tests/test_vcs_helpers.py +++ b/tests/test_vcs_helpers.py @@ -6,6 +6,7 @@ from git import GitCommandError, Repo, TagObject from semantic_release.errors import GitError, HvcsRepoParseError +from semantic_release.history import version_pattern from semantic_release.vcs_helpers import ( check_repo, checkout, @@ -14,7 +15,6 @@ get_current_head_hash, get_last_version, get_repository_owner_and_name, - get_version_from_tag, push_new_version, tag_new_version, update_additional_files, @@ -310,14 +310,14 @@ def test_checkout_should_checkout_correct_branch(mock_git): @pytest.mark.parametrize( - "skip_tags,expected_result", + "pattern, skip_tags,expected_result", [ - (None, "2.0.0"), - (["v2.0.0"], "1.1.0"), - (["v0.1.0", "v1.0.0", "v1.1.0", "v2.0.0"], None), + ("\d+.\d+.\d+", None, "2.0.0"), + ("\d+.\d+.\d+", ["v2.0.0"], "1.1.0"), + ("\d+.\d+.\d+", ["v0.1.0", "v1.0.0", "v1.1.0", "v2.0.0"], None), ], ) -def test_get_last_version(skip_tags, expected_result): +def test_get_last_version(pattern, skip_tags, expected_result): class FakeCommit: def __init__(self, com_date): self.committed_date = com_date @@ -345,18 +345,19 @@ def __init__(self, name, sha, date, is_tag_object): FakeTag("v1.0.0", "bbbbbbbbbbbbbbbbbbbb", 2, False), ] ) - assert expected_result == get_last_version(skip_tags) - + assert expected_result == get_last_version(pattern, skip_tags) @pytest.mark.parametrize( - "skip_tags,expected_result", + "pattern, skip_tags,expected_result", [ - (None, "2.0.0"), - (["v2.0.0"], "1.1.0"), - (["v0.1.0", "v1.0.0", "v1.1.0", "v2.0.0"], None), + (version_pattern, None, "2.1.0-beta.0"), + (version_pattern, ["v2.1.0-beta.0"], "2.0.0"), + (version_pattern, ["v2.1.0-beta.0", "v2.0.0-beta.0", "v2.0.0"], "1.1.0"), + (version_pattern, ["v2.1.0-beta.0", "v2.0.0-beta.0", "v0.1.0", "v1.0.0", "v1.1.0", "v2.0.0"], None), ], ) -def test_get_last_version_with_omit_pattern(skip_tags, expected_result): +def test_get_last_version_with_real_pattern(pattern, skip_tags, expected_result): + # TODO: add some prerelease tags class FakeCommit: def __init__(self, com_date): self.committed_date = com_date @@ -379,43 +380,14 @@ def __init__(self, name, sha, date, is_tag_object): return_value=[ FakeTag("v0.1.0", "aaaaaaaaaaaaaaaaaaaa", 1, True), FakeTag("v2.0.0", "dddddddddddddddddddd", 5, True), - FakeTag("v2.1.0-beta", "ffffffffffffffffffff", 7, True), + FakeTag("v2.1.0-beta.0", "ffffffffffffffffffff", 7, True), FakeTag("badly_formatted", "eeeeeeeeeeeeeeeeeeee", 6, False), - FakeTag("v2.0.0-beta", "ffffffffffffffffffff", 4, True), + FakeTag("v2.0.0-beta.0", "ffffffffffffffffffff", 4, True), FakeTag("v1.1.0", "cccccccccccccccccccc", 3, True), FakeTag("v1.0.0", "bbbbbbbbbbbbbbbbbbbb", 2, False), ] ) - assert expected_result == get_last_version(skip_tags, omit_pattern="-beta") - - -@pytest.mark.parametrize( - "tag_name,expected_version", - [ - ("v0.1.0", "aaaaa"), - ("v1.0.0", "bbbbb"), - ("v2.0.0", None), - ], -) -def test_get_version_from_tag(tag_name, expected_version): - class FakeCommit: - def __init__(self, sha): - self.hexsha = sha - - class FakeTag: - def __init__(self, name, sha): - self.name = name - self.commit = FakeCommit(sha) - - mock.patch("semantic_release.vcs_helpers.check_repo") - git.repo.base.Repo.tags = mock.PropertyMock( - return_value=[ - FakeTag("v0.1.0", "aaaaa"), - FakeTag("v1.0.0", "bbbbb"), - FakeTag("v1.1.0", "ccccc"), - ] - ) - assert expected_version == get_version_from_tag(tag_name) + assert expected_result == get_last_version(pattern, skip_tags) def test_update_changelog_file_ok(mock_git, mocker): From d8b443e10587b9a2619e40a4c95de0193841225e Mon Sep 17 00:00:00 2001 From: Mario Jaeckle Date: Thu, 7 Apr 2022 15:46:01 +0300 Subject: [PATCH 2/6] feat: add method to find current release version --- semantic_release/history/__init__.py | 54 +++++++++++++++++++++++++++- semantic_release/vcs_helpers.py | 2 +- setup.cfg | 3 ++ tests/history/test_version.py | 46 +++++++++++++++++++++++- tests/test_cli.py | 16 ++++++--- tests/test_vcs_helpers.py | 5 ++- 6 files changed, 118 insertions(+), 8 deletions(-) diff --git a/semantic_release/history/__init__.py b/semantic_release/history/__init__.py index 3357cd757..1824f69a6 100644 --- a/semantic_release/history/__init__.py +++ b/semantic_release/history/__init__.py @@ -25,7 +25,11 @@ logger = logging.getLogger(__name__) -version_pattern = f"(\d+.\d+.\d+(-{config.get('prerelease_tag')}.\d+)?)" +prerelease_pattern = f"-{config.get('prerelease_tag')}.\d+" +version_pattern = f"(\d+.\d+.\d+({prerelease_pattern})?)" +release_version_pattern = f"(\d+.\d+.\d+(?!{prerelease_pattern}))" + +release_version_regex = rf"{release_version_pattern}" version_regex = rf"{version_pattern}" @@ -193,6 +197,21 @@ def get_current_version_by_tag() -> str: return "0.0.0" +@LoggedFunction(logger) +def get_current_release_version_by_tag() -> str: + """ + Find the current version of the package in the current working directory using git tags. + + :return: A string with the version number or 0.0.0 on failure. + """ + version = get_last_version(pattern=release_version_pattern) + if version: + return version + + logger.debug("no version found, returning default of v0.0.0") + return "0.0.0" + + @LoggedFunction(logger) def get_current_version_by_config_file() -> str: """ @@ -221,6 +240,7 @@ def get_current_version_by_config_file() -> str: def get_current_version() -> str: """ Get current version from tag or version variable, depending on configuration. + This can be either a release or prerelease version :return: A string with the current version number """ @@ -230,6 +250,20 @@ def get_current_version() -> str: return get_current_version_by_config_file() +def get_current_release_version() -> str: + """ + Get current release version from tag or commit message (no going back in config file), + depending on configuration. + This will return the current release version (NOT prerelease), instead of just the current version + + :return: A string with the current version number + """ + if config.get("version_source") == "tag": + return get_current_release_version_by_tag() + else: + return get_current_release_version_by_commits() + + @LoggedFunction(logger) def get_new_version( current_version: str, level_bump: str, prerelease: bool = False @@ -292,6 +326,24 @@ def get_previous_version(version: str) -> Optional[str]: return get_last_version(pattern=version_pattern, skip_tags=[version, get_formatted_tag(version)]) +@LoggedFunction(logger) +def get_current_release_version_by_commits() -> str: + """ + Return the current release version (NOT prerelease) version. + + :return: A string with the current version number. + """ + for commit_hash, commit_message in get_commit_log(): + logger.debug(f"Checking commit {commit_hash}") + match = re.search(rf"{release_version_pattern}", commit_message) + if match: + logger.debug(f"Version matches regex {commit_message}") + return match.group(1).strip() + + # nothing found, return the initial version + return "0.0.0" + + @LoggedFunction(logger) def set_new_version(new_version: str) -> bool: """ diff --git a/semantic_release/vcs_helpers.py b/semantic_release/vcs_helpers.py index c2905b174..9a6a72469 100644 --- a/semantic_release/vcs_helpers.py +++ b/semantic_release/vcs_helpers.py @@ -82,7 +82,7 @@ def version_finder(tag): match = re.search(rf"{pattern}", i.name) if match: - return match.group(0).strip() # Return only numeric vesion like 1.2.3 + return match.group(0).strip() return None diff --git a/setup.cfg b/setup.cfg index 7899cd137..a2c15540d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -14,5 +14,8 @@ force_grid_wrap=0 use_parentheses=True line_length=88 +[flake8] +max-line-length = 120 + [semantic_release] version_variable = semantic_release/__init__.py:__version__ diff --git a/tests/history/test_version.py b/tests/history/test_version.py index 8a35ed9c9..78e35e6cc 100644 --- a/tests/history/test_version.py +++ b/tests/history/test_version.py @@ -13,6 +13,9 @@ get_current_version, get_current_version_by_config_file, get_current_version_by_tag, + get_current_release_version, + get_current_release_version_by_tag, + get_current_release_version_by_commits, get_new_version, get_previous_version, load_version_declarations, @@ -38,11 +41,20 @@ def test_current_version_should_return_correct_version(): assert get_current_version() == semantic_release.__version__ +def test_current_release_version_should_return_correct_version(): + assert get_current_release_version() == semantic_release.__version__ + + @mock.patch("semantic_release.history.get_last_version", return_value="last_version") def test_current_version_should_return_git_version(mock_last_version): assert "last_version" == get_current_version_by_tag() +@mock.patch("semantic_release.history.get_last_version", return_value="last_version") +def test_current_release_version_should_return_git_version(mock_last_version): + assert "last_version" == get_current_release_version_by_tag() + + @mock.patch( "semantic_release.history.config.get", wrapped_config_get(version_source="tag") ) @@ -68,6 +80,15 @@ def test_current_version_should_run_with_tag_only(mocker): assert not mock_get_current_version_by_config_file.called +@mock.patch("semantic_release.history.get_last_version", return_value=None) +@mock.patch("semantic_release.history.get_current_release_version_by_commits", return_value="0.0.0") +def test_current_release_version_should_return_default_version( + mock_last_version, + mock_current_release_version_by_commits +): + assert "0.0.0" == get_current_release_version() + + class TestGetPreviousVersion: @mock.patch( "semantic_release.history.get_commit_log", @@ -78,7 +99,7 @@ def test_should_return_correct_version(self): @mock.patch( "semantic_release.history.get_commit_log", - lambda: [("211", "0.10.0"), ("13", "0.9.0")], + lambda: [("211", "v0.10.0"), ("13", "v0.9.0")], ) def test_should_return_correct_version_with_v(self): assert get_previous_version("0.10.0") == "0.9.0" @@ -98,6 +119,29 @@ def test_should_return_correct_version_skip_prerelease(self): assert get_previous_version("0.10.0-beta") == "0.9.0" +class TestGetCurrentReleaseVersionByCommits: + @mock.patch( + "semantic_release.history.get_commit_log", + lambda: [("211", "0.10.0-beta.1"), ("13", "0.9.1-beta.1"), ("13", "0.9.0")], + ) + def test_should_return_correct_version(self): + assert get_current_release_version_by_commits() == "0.9.0" + + @mock.patch( + "semantic_release.history.get_commit_log", + lambda: [("211", "v0.10.0-beta.1"), ("13", "0.9.1-beta.1"), ("13", "v0.9.0")], + ) + def test_should_return_correct_version_with_v(self): + assert get_current_release_version_by_commits() == "0.9.0" + + @mock.patch( + "semantic_release.history.get_commit_log", + lambda: [("211", "0.10.0-beta.0"), ("13", "0.9.0")], + ) + def test_should_return_correct_version_from_prerelease(self): + assert get_current_release_version_by_commits() == "0.9.0" + + class TestGetNewVersion: def test_major_bump(self): assert get_new_version("0.0.0", "major") == "1.0.0" diff --git a/tests/test_cli.py b/tests/test_cli.py index bea0d08c2..3b9b79259 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -118,12 +118,16 @@ def test_version_by_tag_only_with_commit_version_number_should_call_correct_func mock_current_version = mocker.patch( "semantic_release.cli.get_current_version", return_value="1.2.3" ) + mock_current_release_version = mocker.patch( + "semantic_release.cli.get_current_release_version", return_value="1.2.3" + ) version() - mock_current_version.assert_called_once_with(False) + mock_current_version.assert_called_once_with() + mock_current_release_version.assert_called_once() mock_evaluate_bump.assert_called_once_with("1.2.3", None) - mock_new_version.assert_called_once_with("1.2.3", "major", False) + mock_new_version.assert_called_once_with("1.2.3", "1.2.3", "major", False) assert not mock_set_new_version.called assert not mock_commit_new_version.called mock_tag_new_version.assert_called_once_with("2.0.0") @@ -171,12 +175,16 @@ def test_version_by_tag_only_should_call_correct_functions(mocker): mock_current_version = mocker.patch( "semantic_release.cli.get_current_version", return_value="1.2.3" ) + mock_current_release_version = mocker.patch( + "semantic_release.cli.get_current_release_version", return_value="1.2.3" + ) version() - mock_current_version.assert_called_once_with(False) + mock_current_version.assert_called_once_with() + mock_current_release_version.assert_called_once() mock_evaluate_bump.assert_called_once_with("1.2.3", None) - mock_new_version.assert_called_once_with("1.2.3", "major", False) + mock_new_version.assert_called_once_with("1.2.3", "1.2.3", "major", False) assert not mock_set_new_version.called mock_tag_new_version.assert_called_once_with("2.0.0") diff --git a/tests/test_vcs_helpers.py b/tests/test_vcs_helpers.py index 9aee80385..aa2e02880 100644 --- a/tests/test_vcs_helpers.py +++ b/tests/test_vcs_helpers.py @@ -6,7 +6,7 @@ from git import GitCommandError, Repo, TagObject from semantic_release.errors import GitError, HvcsRepoParseError -from semantic_release.history import version_pattern +from semantic_release.history import version_pattern, release_version_pattern from semantic_release.vcs_helpers import ( check_repo, checkout, @@ -354,6 +354,9 @@ def __init__(self, name, sha, date, is_tag_object): (version_pattern, ["v2.1.0-beta.0"], "2.0.0"), (version_pattern, ["v2.1.0-beta.0", "v2.0.0-beta.0", "v2.0.0"], "1.1.0"), (version_pattern, ["v2.1.0-beta.0", "v2.0.0-beta.0", "v0.1.0", "v1.0.0", "v1.1.0", "v2.0.0"], None), + (release_version_pattern, None, "2.0.0"), + (release_version_pattern, ["v2.0.0"], "1.1.0"), + (release_version_pattern, ["v2.0.0", "v1.1.0", "v1.0.0", "v0.1.0"], None), ], ) def test_get_last_version_with_real_pattern(pattern, skip_tags, expected_result): From 3e3963b7857949655f0fbd1ad249514a3cd7999c Mon Sep 17 00:00:00 2001 From: Mario Jaeckle Date: Thu, 7 Apr 2022 17:13:49 +0300 Subject: [PATCH 3/6] feat: correctly identify and bump new versions --- semantic_release/cli.py | 20 +++-- semantic_release/history/__init__.py | 79 ++++++++++++++----- tests/history/test_version.py | 52 ++++++++----- tests/test_cli.py | 112 ++++++++++++++++++++------- 4 files changed, 190 insertions(+), 73 deletions(-) diff --git a/semantic_release/cli.py b/semantic_release/cli.py index 087e947b1..b5d17b558 100644 --- a/semantic_release/cli.py +++ b/semantic_release/cli.py @@ -16,8 +16,10 @@ from .history import ( evaluate_version_bump, get_current_version, + get_current_release_version, get_new_version, get_previous_version, + get_previous_release_version, set_new_version, ) from .history.logs import generate_changelog @@ -99,6 +101,8 @@ def print_version(*, current=False, force_level=None, prerelease=False, **kwargs """ try: current_version = get_current_version() + current_release_version = get_current_release_version() + logger.info(f"Current version: {current_version}, Current release version: {current_release_version}") except GitError as e: print(str(e), file=sys.stderr) return False @@ -108,7 +112,7 @@ def print_version(*, current=False, force_level=None, prerelease=False, **kwargs # Find what the new version number should be level_bump = evaluate_version_bump(current_version, force_level) - new_version = get_new_version(current_version, level_bump, prerelease) + new_version = get_new_version(current_version, current_release_version, level_bump, prerelease) if should_bump_version(current_version=current_version, new_version=new_version): print(new_version, end="") return True @@ -131,14 +135,14 @@ def version(*, retry=False, noop=False, force_level=None, prerelease=False, **kw # Get the current version number try: current_version = get_current_version() - logger.info(f"Current version: {current_version}") + current_release_version = get_current_release_version() + logger.info(f"Current version: {current_version}, Current release version: {current_release_version}") except GitError as e: logger.error(str(e)) return False # Find what the new version number should be - # TODO: depending on prerelease or not we have to do this differently - level_bump = evaluate_version_bump(current_version, force_level) - new_version = get_new_version(current_version, level_bump, prerelease) + level_bump = evaluate_version_bump(current_release_version, force_level) + new_version = get_new_version(current_version, current_release_version, level_bump, prerelease) if not should_bump_version( current_version=current_version, new_version=new_version, retry=retry, noop=noop @@ -247,6 +251,8 @@ def publish( ): """Run the version task, then push to git and upload to an artifact repository / GitHub Releases.""" current_version = get_current_version() + current_release_version = get_current_release_version() + logger.info(f"Current version: {current_version}, Current release version: {current_release_version}") verbose = logger.isEnabledFor(logging.DEBUG) if retry: @@ -255,11 +261,11 @@ def publish( # "current" version will be the previous version. level_bump = None new_version = current_version - current_version = get_previous_version(current_version) + current_version = get_previous_release_version(current_version) else: # Calculate the new version level_bump = evaluate_version_bump(current_version, kwargs.get("force_level")) - new_version = get_new_version(current_version, level_bump, prerelease) + new_version = get_new_version(current_version, current_release_version, level_bump, prerelease) owner, name = get_repository_owner_and_name() diff --git a/semantic_release/history/__init__.py b/semantic_release/history/__init__.py index 1824f69a6..b8f5e3422 100644 --- a/semantic_release/history/__init__.py +++ b/semantic_release/history/__init__.py @@ -266,7 +266,7 @@ def get_current_release_version() -> str: @LoggedFunction(logger) def get_new_version( - current_version: str, level_bump: str, prerelease: bool = False + current_version: str, current_release_version: str, level_bump: str, prerelease: bool = False ) -> str: """ Calculate the next version based on the given bump level with semver. @@ -277,28 +277,42 @@ def get_new_version( :param prerelease: Should the version bump be marked as a prerelease :return: A string with the next version number. """ + # pre or release version current_version_info = semver.VersionInfo.parse(current_version) - if prerelease and current_version_info.prerelease: - logger.debug("NextVersion: Prerelease from prerelease version (level_bump ignored)") - new_version = str( - semver.VersionInfo.parse(current_version).bump_prerelease(config.get("prerelease_tag")) - ) - elif prerelease and not current_version_info.prerelease: - logger.debug("NextVersion: Prerelease from normal version (level_bump used)") - bump = level_bump or "patch" - new_version = str( - semver.VersionInfo.parse(current_version).next_version(part=bump).bump_prerelease(config.get("prerelease_tag")) - ) - elif not level_bump: - logger.debug("NextVersion: Normal version bump with no level_bump - version unchanged") - new_version = current_version + # release version + current_release_version_info = semver.VersionInfo.parse(current_release_version) + + # sanity check + # if the current version is no prerelease, than + # current_version and current_release_version must be the same + if not current_version_info.prerelease and current_version_info.compare(current_release_version_info) != 0: + raise ValueError() + + if level_bump: + next_version_info = current_release_version_info.next_version(level_bump) + elif prerelease: + # we do at least a patch for prereleases + next_version_info = current_release_version_info.next_version("patch") else: - logger.debug("NextVersion: Normal version bump (level_bump ignored)") - new_version = str( - semver.VersionInfo.parse(current_version).next_version(part=level_bump) - ) + next_version_info = current_release_version_info + + if prerelease: + next_raw_version = next_version_info.to_tuple()[:3] + current_raw_version = current_version_info.to_tuple()[:3] - return new_version + if current_version_info.prerelease and next_raw_version == current_raw_version: + # next version (based on commits) matches current prerelease version + # bump prerelase + next_prerelease_version_info = current_version_info.bump_prerelease(config.get("prerelease_tag")) + else: + # next version (based on commits) higher than current prerelease version + # new prerelease based on next version + next_prerelease_version_info = next_version_info.bump_prerelease(config.get("prerelease_tag")) + + return str(next_prerelease_version_info) + else: + # normal version bump + return str(next_version_info) @LoggedFunction(logger) @@ -326,6 +340,31 @@ def get_previous_version(version: str) -> Optional[str]: return get_last_version(pattern=version_pattern, skip_tags=[version, get_formatted_tag(version)]) +@LoggedFunction(logger) +def get_previous_release_version(version: str) -> Optional[str]: + """ + Return the version prior to the given version. + + :param version: A string with the version number. + :return: A string with the previous version number. + """ + found_version = False + for commit_hash, commit_message in get_commit_log(): + logger.debug(f"Checking commit {commit_hash}") + if version in commit_message: + found_version = True + logger.debug(f'Found version in commit "{commit_message}"') + continue + + if found_version: + match = re.search(rf"{release_version_pattern}", commit_message) + if match: + logger.debug(f"Version matches regex {commit_message}") + return match.group(1).strip() + + return get_last_version(pattern=release_version_pattern, skip_tags=[version, get_formatted_tag(version)]) + + @LoggedFunction(logger) def get_current_release_version_by_commits() -> str: """ diff --git a/tests/history/test_version.py b/tests/history/test_version.py index 78e35e6cc..6e29989c7 100644 --- a/tests/history/test_version.py +++ b/tests/history/test_version.py @@ -144,33 +144,47 @@ def test_should_return_correct_version_from_prerelease(self): class TestGetNewVersion: def test_major_bump(self): - assert get_new_version("0.0.0", "major") == "1.0.0" - assert get_new_version("0.1.0", "major") == "1.0.0" - assert get_new_version("0.1.9", "major") == "1.0.0" - assert get_new_version("10.1.0", "major") == "11.0.0" + assert get_new_version("0.0.0", "0.0.0", "major") == "1.0.0" + assert get_new_version("0.1.0", "0.1.0", "major") == "1.0.0" + assert get_new_version("0.1.9", "0.1.9", "major") == "1.0.0" + assert get_new_version("10.1.0", "10.1.0", "major") == "11.0.0" def test_minor_bump(self): - assert type(get_new_version("0.0.0", "minor")) is str - assert get_new_version("0.0.0", "minor") == "0.1.0" - assert get_new_version("1.2.0", "minor") == "1.3.0" - assert get_new_version("1.2.1", "minor") == "1.3.0" - assert get_new_version("10.1.0", "minor") == "10.2.0" + assert type(get_new_version("0.0.0", "0.0.0", "minor")) is str + assert get_new_version("0.0.0", "0.0.0", "minor") == "0.1.0" + assert get_new_version("1.2.0", "1.2.0", "minor") == "1.3.0" + assert get_new_version("1.2.1", "1.2.1", "minor") == "1.3.0" + assert get_new_version("10.1.0", "10.1.0", "minor") == "10.2.0" def test_patch_bump(self): - assert get_new_version("0.0.0", "patch") == "0.0.1" - assert get_new_version("0.1.0", "patch") == "0.1.1" - assert get_new_version("10.0.9", "patch") == "10.0.10" + assert get_new_version("0.0.0", "0.0.0", "patch") == "0.0.1" + assert get_new_version("0.1.0", "0.1.0", "patch") == "0.1.1" + assert get_new_version("10.0.9", "10.0.9", "patch") == "10.0.10" def test_none_bump(self): - assert get_new_version("1.0.0", None) == "1.0.0" + assert get_new_version("1.0.0", "1.0.0", None) == "1.0.0" def test_prerelease(self): - assert get_new_version("1.0.0", None, True) == "1.0.1-beta.1" - assert get_new_version("1.0.0-beta.1", None, True) == "1.0.0-beta.2" - assert get_new_version("1.0.0-beta.1", "minor", True) == "1.0.0-beta.2" - assert get_new_version("1.0.0", "major", True) == "2.0.0-beta.1" - assert get_new_version("1.0.0", "minor", True) == "1.1.0-beta.1" - assert get_new_version("1.0.0", "patch", True) == "1.0.1-beta.1" + assert get_new_version("1.0.1-beta.1", "1.0.0", None, True) == "1.0.1-beta.2" + assert get_new_version("1.0.1-beta.1", "1.0.0", "major", True) == "2.0.0-beta.1" + assert get_new_version("1.0.1-beta.1", "1.0.0", "minor", True) == "1.1.0-beta.1" + assert get_new_version("1.0.1-beta.1", "1.0.0", "patch", True) == "1.0.1-beta.2" + + assert get_new_version("1.0.0", "1.0.0", None, True) == "1.0.1-beta.1" + assert get_new_version("1.0.0", "1.0.0", "major", True) == "2.0.0-beta.1" + assert get_new_version("1.0.0", "1.0.0", "minor", True) == "1.1.0-beta.1" + assert get_new_version("1.0.0", "1.0.0", "patch", True) == "1.0.1-beta.1" + + assert get_new_version("0.9.0-beta.1", "1.0.0", None, True) == "1.0.1-beta.1" + assert get_new_version("0.9.0-beta.1", "1.0.0", "major", True) == "2.0.0-beta.1" + assert get_new_version("0.9.0-beta.1", "1.0.0", "minor", True) == "1.1.0-beta.1" + assert get_new_version("0.9.0-beta.1", "1.0.0", "patch", True) == "1.0.1-beta.1" + + with pytest.raises(ValueError): + get_new_version("0.9.0", "1.0.0", None, True) + + with pytest.raises(ValueError): + get_new_version("1.0.0", "0.9.0", None, True) @mock.patch( diff --git a/tests/test_cli.py b/tests/test_cli.py index 3b9b79259..feb4e1b40 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -48,12 +48,16 @@ def test_version_by_commit_should_call_correct_functions(mocker): mock_current_version = mocker.patch( "semantic_release.cli.get_current_version", return_value="1.2.3" ) + mock_current_release_version = mocker.patch( + "semantic_release.cli.get_current_release_version", return_value="1.2.3" + ) version() mock_current_version.assert_called_once() + mock_current_release_version.assert_called_once() mock_evaluate_bump.assert_called_once_with("1.2.3", None) - mock_new_version.assert_called_once_with("1.2.3", "major", False) + mock_new_version.assert_called_once_with("1.2.3", "1.2.3", "major", False) mock_set_new_version.assert_called_once_with("2.0.0") mock_commit_new_version.assert_called_once_with("2.0.0") mock_tag_new_version.assert_called_once_with("2.0.0") @@ -83,12 +87,16 @@ def test_version_by_tag_with_commit_version_number_should_call_correct_functions mock_current_version = mocker.patch( "semantic_release.cli.get_current_version", return_value="1.2.3" ) + mock_current_release_version = mocker.patch( + "semantic_release.cli.get_current_release_version", return_value="1.2.3" + ) version() mock_current_version.assert_called_once() + mock_current_release_version.assert_called_once() mock_evaluate_bump.assert_called_once_with("1.2.3", None) - mock_new_version.assert_called_once_with("1.2.3", "major", False) + mock_new_version.assert_called_once_with("1.2.3", "1.2.3", "major", False) mock_set_new_version.assert_called_once_with("2.0.0") mock_commit_new_version.assert_called_once_with("2.0.0") mock_tag_new_version.assert_called_once_with("2.0.0") @@ -149,12 +157,16 @@ def test_version_by_tag_should_call_correct_functions(mocker): mock_current_version = mocker.patch( "semantic_release.cli.get_current_version", return_value="1.2.3" ) + mock_current_release_version = mocker.patch( + "semantic_release.cli.get_current_release_version", return_value="1.2.3" + ) version() mock_current_version.assert_called_once() + mock_current_release_version.assert_called_once() mock_evaluate_bump.assert_called_once_with("1.2.3", None) - mock_new_version.assert_called_once_with("1.2.3", "major", False) + mock_new_version.assert_called_once_with("1.2.3", "1.2.3", "major", False) mock_set_new_version.assert_called_once_with("2.0.0") mock_tag_new_version.assert_called_once_with("2.0.0") @@ -206,12 +218,16 @@ def test_version_by_commit_without_tag_should_call_correct_functions(mocker): mock_current_version = mocker.patch( "semantic_release.cli.get_current_version", return_value="1.2.3" ) + mock_current_release_version = mocker.patch( + "semantic_release.cli.get_current_release_version", return_value="1.2.3" + ) version() mock_current_version.assert_called_once() + mock_current_release_version.assert_called_once() mock_evaluate_bump.assert_called_once_with("1.2.3", None) - mock_new_version.assert_called_once_with("1.2.3", "major", False) + mock_new_version.assert_called_once_with("1.2.3", "1.2.3", "major", False) mock_set_new_version.assert_called_once_with("2.0.0") mock_commit_new_version.assert_called_once_with("2.0.0") assert not mock_tag_new_version.called @@ -374,6 +390,9 @@ def test_print_version_no_change(mocker, runner, capsys): mock_current_version = mocker.patch( "semantic_release.cli.get_current_version", return_value="1.2.3" ) + mock_current_release_version = mocker.patch( + "semantic_release.cli.get_current_release_version", return_value="1.2.3" + ) print_version() outerr = capsys.readouterr() @@ -381,14 +400,18 @@ def test_print_version_no_change(mocker, runner, capsys): assert outerr.err == "No release will be made.\n" mock_current_version.assert_called_once() + mock_current_release_version.assert_called_once() mock_evaluate_bump.assert_called_once_with("1.2.3", None) - mock_new_version.assert_called_once_with("1.2.3", None, False) + mock_new_version.assert_called_once_with("1.2.3", "1.2.3", None, False) def test_print_version_change(mocker, runner, capsys): mock_current_version = mocker.patch( "semantic_release.cli.get_current_version", return_value="1.2.3" ) + mock_current_release_version = mocker.patch( + "semantic_release.cli.get_current_release_version", return_value="1.2.3" + ) mock_evaluate_bump = mocker.patch( "semantic_release.cli.evaluate_version_bump", return_value="minor" ) @@ -399,6 +422,7 @@ def test_print_version_change(mocker, runner, capsys): assert outerr.err == "" mock_current_version.assert_called_once() + mock_current_release_version.assert_called_once() mock_evaluate_bump.assert_called_once_with("1.2.3", None) @@ -406,6 +430,9 @@ def test_print_version_change_prerelease(mocker, runner, capsys): mock_current_version = mocker.patch( "semantic_release.cli.get_current_version", return_value="1.2.3" ) + mock_current_release_version = mocker.patch( + "semantic_release.cli.get_current_release_version", return_value="1.2.3" + ) mock_evaluate_bump = mocker.patch( "semantic_release.cli.evaluate_version_bump", return_value="minor" ) @@ -416,6 +443,7 @@ def test_print_version_change_prerelease(mocker, runner, capsys): assert outerr.err == "" mock_current_version.assert_called_once() + mock_current_release_version.assert_called_once() mock_evaluate_bump.assert_called_once_with("1.2.3", None) @@ -423,8 +451,8 @@ def test_print_version_change_prerelease_bump(mocker, runner, capsys): mock_current_version = mocker.patch( "semantic_release.cli.get_current_version", return_value="1.2.3" ) - mock_current_version = mocker.patch( - "semantic_release.history.get_current_version", return_value="1.3.0-beta.0" + mock_current_release_version = mocker.patch( + "semantic_release.cli.get_current_release_version", return_value="1.2.3" ) mock_evaluate_bump = mocker.patch( "semantic_release.cli.evaluate_version_bump", return_value="minor" @@ -435,6 +463,8 @@ def test_print_version_change_prerelease_bump(mocker, runner, capsys): assert outerr.out == "1.3.0-beta.1" assert outerr.err == "" + mock_current_version.assert_called_once() + mock_current_release_version.assert_called_once() mock_evaluate_bump.assert_called_once_with("1.2.3", None) @@ -442,6 +472,9 @@ def test_print_version_force_major(mocker, runner, capsys): mock_current_version = mocker.patch( "semantic_release.cli.get_current_version", return_value="1.2.3" ) + mock_current_release_version = mocker.patch( + "semantic_release.cli.get_current_release_version", return_value="1.2.3" + ) mock_evaluate_bump = mocker.patch( "semantic_release.cli.evaluate_version_bump", return_value="major" ) @@ -452,6 +485,7 @@ def test_print_version_force_major(mocker, runner, capsys): assert outerr.err == "" mock_current_version.assert_called_once() + mock_current_release_version.assert_called_once() mock_evaluate_bump.assert_called_once_with("1.2.3", "major") @@ -459,6 +493,9 @@ def test_print_version_force_major_prerelease(mocker, runner, capsys): mock_current_version = mocker.patch( "semantic_release.cli.get_current_version", return_value="1.2.3" ) + mock_current_release_version = mocker.patch( + "semantic_release.cli.get_current_release_version", return_value="1.2.3" + ) mock_evaluate_bump = mocker.patch( "semantic_release.cli.evaluate_version_bump", return_value="major" ) @@ -469,6 +506,7 @@ def test_print_version_force_major_prerelease(mocker, runner, capsys): assert outerr.err == "" mock_current_version.assert_called_once() + mock_current_release_version.assert_called_once() mock_evaluate_bump.assert_called_once_with("1.2.3", "major") @@ -485,12 +523,16 @@ def test_version_no_change(mocker, runner): mock_current_version = mocker.patch( "semantic_release.cli.get_current_version", return_value="1.2.3" ) + mock_current_release_version = mocker.patch( + "semantic_release.cli.get_current_release_version", return_value="1.2.3" + ) version() mock_current_version.assert_called_once() + mock_current_release_version.assert_called_once() mock_evaluate_bump.assert_called_once_with("1.2.3", None) - mock_new_version.assert_called_once_with("1.2.3", None, False) + mock_new_version.assert_called_once_with("1.2.3", "1.2.3", None, False) assert not mock_set_new_version.called assert not mock_commit_new_version.called assert not mock_tag_new_version.called @@ -602,8 +644,11 @@ def test_version_retry_and_giterror(mocker): def test_version_retry(mocker): - mock_get_current = mocker.patch( - "semantic_release.cli.get_current_version", return_value="current" + mock_current_version = mocker.patch( + "semantic_release.cli.get_current_version", return_value="1.2.3" + ) + mock_current_release_version = mocker.patch( + "semantic_release.cli.get_current_release_version", return_value="1.2.3" ) mock_evaluate_bump = mocker.patch( "semantic_release.cli.evaluate_version_bump", return_value="patch" @@ -616,9 +661,10 @@ def test_version_retry(mocker): result = version(noop=False, retry=True, force_level=False) assert result - mock_get_current.assert_called_once() - mock_evaluate_bump.assert_called_once_with("current", False) - mock_get_new.assert_called_once_with("current", "patch", False) + mock_current_version.assert_called_once() + mock_current_release_version.assert_called_once() + mock_evaluate_bump.assert_called_once_with("1.2.3", False) + mock_get_new.assert_called_once_with("1.2.3", "1.2.3", "patch", False) def test_publish_should_not_run_pre_commit_by_default(mocker): @@ -895,8 +941,11 @@ def test_publish_retry_version_fail(mocker): mock_get_current = mocker.patch( "semantic_release.cli.get_current_version", return_value="current" ) - mock_get_previous = mocker.patch( - "semantic_release.cli.get_previous_version", return_value="previous" + mock_current_release_version = mocker.patch( + "semantic_release.cli.get_current_release_version", return_value="1.2.3" + ) + get_previous_release_version = mocker.patch( + "semantic_release.cli.get_previous_release_version", return_value="previous" ) mock_get_owner_name = mocker.patch( "semantic_release.cli.get_repository_owner_and_name", @@ -912,7 +961,8 @@ def test_publish_retry_version_fail(mocker): publish(noop=False, retry=True, force_level=False) mock_get_current.assert_called_once() - mock_get_previous.assert_called_once_with("current") + mock_current_release_version.assert_called_once() + get_previous_release_version.assert_called_once_with("current") mock_get_owner_name.assert_called_once_with() mock_ci_check.assert_called() mock_checkout.assert_called_once_with("my_branch") @@ -925,8 +975,11 @@ def test_publish_bad_token(mocker): mock_get_current = mocker.patch( "semantic_release.cli.get_current_version", return_value="current" ) - mock_get_previous = mocker.patch( - "semantic_release.cli.get_previous_version", return_value="previous" + mock_current_release_version = mocker.patch( + "semantic_release.cli.get_current_release_version", return_value="1.2.3" + ) + get_previous_release_version = mocker.patch( + "semantic_release.cli.get_previous_release_version", return_value="previous" ) mock_get_owner_name = mocker.patch( "semantic_release.cli.get_repository_owner_and_name", @@ -958,7 +1011,8 @@ def test_publish_bad_token(mocker): publish(noop=False, retry=True, force_level=False) mock_get_current.assert_called_once() - mock_get_previous.assert_called_once_with("current") + mock_current_release_version.assert_called_once() + get_previous_release_version.assert_called_once_with("current") mock_get_owner_name.assert_called_once_with() mock_ci_check.assert_called() mock_checkout.assert_called_once_with("my_branch") @@ -978,8 +1032,11 @@ def test_publish_bad_token(mocker): def test_publish_giterror_when_posting(mocker): - mock_get_current = mocker.patch( - "semantic_release.cli.get_current_version", return_value="current" + mock_current_version = mocker.patch( + "semantic_release.cli.get_current_version", return_value="1.2.3" + ) + mock_current_release_version = mocker.patch( + "semantic_release.cli.get_current_release_version", return_value="1.2.3" ) mock_evaluate = mocker.patch( "semantic_release.cli.evaluate_version_bump", return_value="patch" @@ -1031,14 +1088,15 @@ def test_publish_giterror_when_posting(mocker): publish(noop=False, retry=False, force_level=False) - mock_get_current.assert_called_once() - mock_evaluate.assert_called_once_with("current", False) - mock_get_new.assert_called_once_with("current", "patch", False) + mock_current_version.assert_called_once() + mock_current_release_version.assert_called_once() + mock_evaluate.assert_called_once_with("1.2.3", False) + mock_get_new.assert_called_once_with("1.2.3", "1.2.3", "patch", False) mock_get_owner_name.assert_called_once_with() mock_ci_check.assert_called() mock_checkout.assert_called_once_with("my_branch") mock_should_bump_version.assert_called_once_with( - current_version="current", new_version="new", noop=False, retry=False + current_version="1.2.3", new_version="new", noop=False, retry=False ) mock_update_changelog_file.assert_called_once_with("new", "super md changelog") mock_bump_version.assert_called_once_with("new", "patch") @@ -1052,14 +1110,14 @@ def test_publish_giterror_when_posting(mocker): domain="domain", ) mock_check_token.assert_called_once_with() - mock_generate.assert_called_once_with("current") + mock_generate.assert_called_once_with("1.2.3") mock_markdown.assert_called_once_with( "owner", "name", "new", "super changelog", header=False, - previous_version="current", + previous_version="1.2.3", ) mock_post.assert_called_once_with("owner", "name", "new", "super md changelog") From f95896820b91cb63e6594deb9609f630091a150b Mon Sep 17 00:00:00 2001 From: Mario Jaeckle Date: Mon, 11 Apr 2022 13:16:26 +0300 Subject: [PATCH 4/6] fix: escape dots in regex --- semantic_release/history/__init__.py | 6 +++--- tests/history/test_version.py | 14 +++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/semantic_release/history/__init__.py b/semantic_release/history/__init__.py index b8f5e3422..7e0d617f0 100644 --- a/semantic_release/history/__init__.py +++ b/semantic_release/history/__init__.py @@ -25,9 +25,9 @@ logger = logging.getLogger(__name__) -prerelease_pattern = f"-{config.get('prerelease_tag')}.\d+" -version_pattern = f"(\d+.\d+.\d+({prerelease_pattern})?)" -release_version_pattern = f"(\d+.\d+.\d+(?!{prerelease_pattern}))" +prerelease_pattern = f"-{config.get('prerelease_tag')}\.\d+" +version_pattern = f"(\d+\.\d+\.\d+({prerelease_pattern})?)" +release_version_pattern = f"(\d+\.\d+\.\d+(?!{prerelease_pattern}))" release_version_regex = rf"{release_version_pattern}" version_regex = rf"{version_pattern}" diff --git a/tests/history/test_version.py b/tests/history/test_version.py index 6e29989c7..410095c4d 100644 --- a/tests/history/test_version.py +++ b/tests/history/test_version.py @@ -207,7 +207,7 @@ class TestVersionPattern: ( "path:__version__", Path("path"), - r'__version__ *[:=] *["\'](\d+.\d+.\d+(-beta.\d+)?)["\']', + r'__version__ *[:=] *["\'](\d+\.\d+\.\d+(-beta\.\d+)?)["\']', ), ], ) @@ -220,7 +220,7 @@ def test_from_variable(self, str, path, pattern): "str, path, pattern", [ ("path:pattern", Path("path"), r"pattern"), - ("path:Version: {version}", Path("path"), r"Version: (\d+.\d+.\d+(-beta.\d+)?)"), + ("path:Version: {version}", Path("path"), r"Version: (\d+\.\d+\.\d+(-beta\.\d+)?)"), ], ) def test_from_pattern(self, str, path, pattern): @@ -407,7 +407,7 @@ def test_toml_replace(self, tmp_path, key, old_content, new_content): version_variable = "path:__version__" """, patterns=[ - (Path("path"), r'__version__ *[:=] *["\'](\d+.\d+.\d+(-beta.\d+)?)["\']'), + (Path("path"), r'__version__ *[:=] *["\'](\d+\.\d+\.\d+(-beta\.\d+)?)["\']'), ], ), dict( @@ -416,8 +416,8 @@ def test_toml_replace(self, tmp_path, key, old_content, new_content): version_variable = "path1:var1,path2:var2" """, patterns=[ - (Path("path1"), r'var1 *[:=] *["\'](\d+.\d+.\d+(-beta.\d+)?)["\']'), - (Path("path2"), r'var2 *[:=] *["\'](\d+.\d+.\d+(-beta.\d+)?)["\']'), + (Path("path1"), r'var1 *[:=] *["\'](\d+\.\d+\.\d+(-beta\.\d+)?)["\']'), + (Path("path2"), r'var2 *[:=] *["\'](\d+\.\d+\.\d+(-beta\.\d+)?)["\']'), ], ), dict( @@ -429,8 +429,8 @@ def test_toml_replace(self, tmp_path, key, old_content, new_content): ] """, patterns=[ - (Path("path1"), r'var1 *[:=] *["\'](\d+.\d+.\d+(-beta.\d+)?)["\']'), - (Path("path2"), r'var2 *[:=] *["\'](\d+.\d+.\d+(-beta.\d+)?)["\']'), + (Path("path1"), r'var1 *[:=] *["\'](\d+\.\d+\.\d+(-beta\.\d+)?)["\']'), + (Path("path2"), r'var2 *[:=] *["\'](\d+\.\d+\.\d+(-beta\.\d+)?)["\']'), ], ), dict( From 5c270ead7c17b944be234b0f032ce621b9e34aaa Mon Sep 17 00:00:00 2001 From: Mario Jaeckle Date: Tue, 12 Apr 2022 09:56:59 +0300 Subject: [PATCH 5/6] test: sanity test --- tests/history/test_version.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/history/test_version.py b/tests/history/test_version.py index 410095c4d..5bffa36cb 100644 --- a/tests/history/test_version.py +++ b/tests/history/test_version.py @@ -141,6 +141,13 @@ def test_should_return_correct_version_with_v(self): def test_should_return_correct_version_from_prerelease(self): assert get_current_release_version_by_commits() == "0.9.0" + @mock.patch( + "semantic_release.history.get_commit_log", + lambda: [("211", "7.28.0"), ("13", "7.27.0")], + ) + def test_should_return_correct_version_without_prerelease(self): + assert get_current_release_version_by_commits() == "7.28.0" + class TestGetNewVersion: def test_major_bump(self): From 73059edc97fb22848cb8d111a6dd1ecd3e2a9b54 Mon Sep 17 00:00:00 2001 From: Mario Jaeckle Date: Tue, 12 Apr 2022 15:17:46 +0300 Subject: [PATCH 6/6] fix: adjust gh action to use the full commit history --- .github/workflows/pr.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 3e7023a34..9db18bf55 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -13,6 +13,9 @@ jobs: steps: - uses: actions/checkout@v2 + with: + ref: ${{ github.event.pull_request.head.sha }} + fetch-depth: 0 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v1 with: