diff --git a/docs/commands.rst b/docs/commands.rst index c582f0db7..267a9fe5e 100644 --- a/docs/commands.rst +++ b/docs/commands.rst @@ -75,6 +75,12 @@ Force a minor release, ignoring the version bump determined from commit messages Force a major release, ignoring the version bump determined from commit messages. +``--prerelease`` +........... + +Makes the next release a prerelease, version bumps are still determined or can be forced, +but the `prerelease_tag` (see :ref:`config-prerelease_tag`) will be appended to version number. + ``--noop`` .......... diff --git a/docs/configuration.rst b/docs/configuration.rst index 03019b3e3..abbd875c8 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -33,12 +33,12 @@ The file and variable name of where the version number is stored, for example:: semantic_release/__init__.py:__version__ -You can specify multiple version variables (i.e. in different files) by +You can specify multiple version variables (i.e. in different files) by providing comma-separated list of such strings:: semantic_release/__init__.py:__version__,docs/conf.py:version -In ``pyproject.toml`` specifically, you can also use the TOML list syntax to +In ``pyproject.toml`` specifically, you can also use the TOML list syntax to specify multiple versions: .. code-block:: toml @@ -67,14 +67,14 @@ identified using an arbitrary regular expression:: README.rst:VERSION (\d+\.\d+\.\d+) -The regular expression must contain a parenthesized group that matches the -version number itself. Anything outside that group is just context. For -example, the above specifies that there is a version number in ``README.rst`` +The regular expression must contain a parenthesized group that matches the +version number itself. Anything outside that group is just context. For +example, the above specifies that there is a version number in ``README.rst`` preceded by the string "VERSION". -If the pattern contains the string ``{version}``, it will be replaced with the -regular expression used internally by ``python-semantic-release`` to match -semantic version numbers. So the above example would probably be better +If the pattern contains the string ``{version}``, it will be replaced with the +regular expression used internally by ``python-semantic-release`` to match +semantic version numbers. So the above example would probably be better written as:: README.rst:VERSION {version} @@ -95,6 +95,17 @@ The way we get and set the new version. Can be `commit` or `tag`. Default: `commit` +.. _config-prerelease_tag: + +``prerelease_tag`` +------------------ +Defined the prerelease marker appended to the version when doing a prerelease. + +- The format of a prerelease version will be `{tag_format}-{prerelease_tag}.`, + e.g. `1.0.0-beta.0` or `1.1.0-beta.1` + +Default: `beta` + .. _config-tag_commit: ``tag_commit`` diff --git a/semantic_release/cli.py b/semantic_release/cli.py index 0950e9d2c..9052b9b1d 100644 --- a/semantic_release/cli.py +++ b/semantic_release/cli.py @@ -18,7 +18,7 @@ get_current_version, get_new_version, get_previous_version, - set_new_version, + set_new_version ) from .history.logs import generate_changelog from .hvcs import ( @@ -66,8 +66,12 @@ click.option( "--patch", "force_level", flag_value="patch", help="Force patch version." ), + click.option( + "--prerelease", is_flag=True, help="Creates a prerelease version." + ), click.option("--post", is_flag=True, help="Post changelog."), - click.option("--retry", is_flag=True, help="Retry the same release, do not bump."), + click.option("--retry", is_flag=True, + help="Retry the same release, do not bump."), click.option( "--noop", is_flag=True, @@ -92,7 +96,7 @@ def common_options(func): return func -def print_version(*, current=False, force_level=None, **kwargs): +def print_version(*, current=False, force_level=None, prerelease=False, **kwargs): """ Print the current or new version to standard output. """ @@ -107,7 +111,7 @@ def print_version(*, current=False, force_level=None, **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) + new_version = get_new_version(current_version, level_bump, prerelease) if should_bump_version(current_version=current_version, new_version=new_version): print(new_version, end="") return True @@ -116,7 +120,7 @@ def print_version(*, current=False, force_level=None, **kwargs): return False -def version(*, retry=False, noop=False, force_level=None, **kwargs): +def version(*, retry=False, noop=False, force_level=None, prerelease=False, **kwargs): """ Detect the new version according to git log and semver. @@ -136,7 +140,7 @@ def version(*, retry=False, noop=False, force_level=None, **kwargs): return False # 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) + new_version = get_new_version(current_version, level_bump, prerelease) if not should_bump_version( current_version=current_version, new_version=new_version, retry=retry, noop=noop @@ -228,13 +232,14 @@ def changelog(*, unreleased=False, noop=False, post=False, **kwargs): owner, name, current_version, - markdown_changelog(owner, name, current_version, log, header=False), + markdown_changelog( + owner, name, current_version, log, header=False), ) else: logger.error("Missing token: cannot post changelog to HVCS") -def publish(retry: bool = False, noop: bool = False, **kwargs): +def publish(retry: bool = False, noop: bool = False, prerelease=False, **kwargs): """Run the version task, then push to git and upload to an artifact repository / GitHub Releases.""" current_version = get_current_version() @@ -248,8 +253,9 @@ def publish(retry: bool = False, noop: bool = False, **kwargs): current_version = get_previous_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) + level_bump = evaluate_version_bump( + current_version, kwargs.get("force_level")) + new_version = get_new_version(current_version, level_bump, prerelease) owner, name = get_repository_owner_and_name() diff --git a/semantic_release/defaults.cfg b/semantic_release/defaults.cfg index a999a60a3..05b153337 100644 --- a/semantic_release/defaults.cfg +++ b/semantic_release/defaults.cfg @@ -42,3 +42,4 @@ upload_to_repository=true upload_to_pypi=true upload_to_release=true version_source=commit +prerelease_tag=beta diff --git a/semantic_release/history/__init__.py b/semantic_release/history/__init__.py index b7160a0c5..47bc36332 100644 --- a/semantic_release/history/__init__.py +++ b/semantic_release/history/__init__.py @@ -176,14 +176,18 @@ 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() -> str: +def get_current_version_by_tag(omit_pattern=None) -> 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() + version = get_last_version(omit_pattern=omit_pattern) if version: return version @@ -192,7 +196,7 @@ def get_current_version_by_tag() -> str: @LoggedFunction(logger) -def get_current_version_by_config_file() -> str: +def get_current_version_by_config_file(omit_pattern=None) -> str: """ Get current version from the version variable defined in the configuration. @@ -216,35 +220,55 @@ def get_current_version_by_config_file() -> str: return version -def get_current_version() -> str: +def get_current_version(prerelease_version: bool = False) -> 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") == "tag": - return get_current_version_by_tag() - return get_current_version_by_config_file() + 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 @LoggedFunction(logger) -def get_new_version(current_version: str, level_bump: str) -> str: +def get_new_version(current_version: str, level_bump: str, prerelease: bool = False) -> str: """ Calculate the next version based on the given bump level with semver. :param current_version: The version the package has now. :param level_bump: The level of the version number that should be bumped. Should be `'major'`, `'minor'` or `'patch'`. + :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, returning input version") - return current_version - return str(semver.VersionInfo.parse(current_version).next_version(part=level_bump)) + logger.debug("No bump requested, using input version") + new_version = current_version + else: + 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) -> Optional[str]: +def get_previous_version(version: str, omit_pattern: str = None) -> Optional[str]: """ Return the version prior to the given version. @@ -260,12 +284,14 @@ def get_previous_version(version: str) -> 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: logger.debug(f"Version matches regex {commit_message}") return matches.group(1).strip() - return get_last_version([version, get_formatted_tag(version)]) + return get_last_version([version, get_formatted_tag(version)], omit_pattern=omit_pattern) @LoggedFunction(logger) diff --git a/semantic_release/vcs_helpers.py b/semantic_release/vcs_helpers.py index 78c6e5a28..a58d98480 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) -> Optional[str]: +def get_last_version(skip_tags=None, omit_pattern=None) -> Optional[str]: """ Find the latest version using repo tags. @@ -78,8 +78,12 @@ def version_finder(tag): for i in sorted(repo.tags, reverse=True, key=version_finder): match = re.search(r"\d+\.\d+\.\d+", i.name) - if match and i.name not in skip_tags: - return match.group(0) # Return only numeric vesion like 1.2.3 + 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 diff --git a/tests/history/test_version.py b/tests/history/test_version.py index a8c111cb5..a37df1b6f 100644 --- a/tests/history/test_version.py +++ b/tests/history/test_version.py @@ -65,6 +65,21 @@ def test_should_return_correct_version(self): def test_should_return_correct_version_with_v(self): assert get_previous_version("0.10.0") == "0.9.0" + @mock.patch( + "semantic_release.history.get_commit_log", + lambda: [("211", "0.10.0-beta"), ("13", "0.9.0")], + ) + def test_should_return_correct_version_from_prerelease(self): + assert get_previous_version("0.10.0-beta") == "0.9.0" + + @mock.patch( + "semantic_release.history.get_commit_log", + 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" + class TestGetNewVersion: def test_major_bump(self): @@ -88,6 +103,19 @@ def test_patch_bump(self): 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" + @mock.patch( "semantic_release.history.config.get", @@ -253,11 +281,11 @@ def test_toml_parse(self, tmp_path, key, content, hits): name = "my-package" version = "0.1.0" description = "A super package" - + [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" - + [tool.semantic_release] version_toml = "pyproject.toml:tool.poetry.version" """ @@ -268,11 +296,11 @@ def test_toml_parse(self, tmp_path, key, content, hits): name = "my-package" version = "-" description = "A super package" - + [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" - + [tool.semantic_release] version_toml = "pyproject.toml:tool.poetry.version" """ diff --git a/tests/test_cli.py b/tests/test_cli.py index a50323141..821fd073d 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -22,6 +22,7 @@ def test_main_should_call_correct_function(mocker, runner): noop=False, post=False, force_level=None, + prerelease=False, retry=False, define=(), ) @@ -36,7 +37,8 @@ def test_version_by_commit_should_call_correct_functions(mocker): ), ) mock_tag_new_version = mocker.patch("semantic_release.cli.tag_new_version") - mock_commit_new_version = mocker.patch("semantic_release.cli.commit_new_version") + mock_commit_new_version = mocker.patch( + "semantic_release.cli.commit_new_version") mock_set_new_version = mocker.patch("semantic_release.cli.set_new_version") mock_new_version = mocker.patch( "semantic_release.cli.get_new_version", return_value="2.0.0" @@ -52,7 +54,7 @@ def test_version_by_commit_should_call_correct_functions(mocker): mock_current_version.assert_called_once_with() mock_evaluate_bump.assert_called_once_with("1.2.3", None) - mock_new_version.assert_called_once_with("1.2.3", "major") + mock_new_version.assert_called_once_with("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") @@ -72,7 +74,8 @@ def test_version_by_tag_with_commit_version_number_should_call_correct_functions mock_set_new_version = mocker.patch("semantic_release.cli.set_new_version") mock_tag_new_version = mocker.patch("semantic_release.cli.tag_new_version") - mock_commit_new_version = mocker.patch("semantic_release.cli.commit_new_version") + mock_commit_new_version = mocker.patch( + "semantic_release.cli.commit_new_version") mock_new_version = mocker.patch( "semantic_release.cli.get_new_version", return_value="2.0.0" ) @@ -87,7 +90,7 @@ def test_version_by_tag_with_commit_version_number_should_call_correct_functions mock_current_version.assert_called_once_with() mock_evaluate_bump.assert_called_once_with("1.2.3", None) - mock_new_version.assert_called_once_with("1.2.3", "major") + mock_new_version.assert_called_once_with("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") @@ -114,7 +117,7 @@ def test_version_by_tag_should_call_correct_functions(mocker): mock_current_version.assert_called_once_with() mock_evaluate_bump.assert_called_once_with("1.2.3", None) - mock_new_version.assert_called_once_with("1.2.3", "major") + mock_new_version.assert_called_once_with("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") @@ -126,7 +129,8 @@ def test_version_by_commit_without_tag_should_call_correct_functions(mocker): ) mock_set_new_version = mocker.patch("semantic_release.cli.set_new_version") mock_tag_new_version = mocker.patch("semantic_release.cli.tag_new_version") - mock_commit_new_version = mocker.patch("semantic_release.cli.commit_new_version") + mock_commit_new_version = mocker.patch( + "semantic_release.cli.commit_new_version") mock_new_version = mocker.patch( "semantic_release.cli.get_new_version", return_value="2.0.0" ) @@ -141,7 +145,7 @@ def test_version_by_commit_without_tag_should_call_correct_functions(mocker): mock_current_version.assert_called_once_with() mock_evaluate_bump.assert_called_once_with("1.2.3", None) - mock_new_version.assert_called_once_with("1.2.3", "major") + mock_new_version.assert_called_once_with("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 @@ -154,6 +158,7 @@ def test_force_major(mocker, runner): noop=False, post=False, force_level="major", + prerelease=False, retry=False, define=(), ) @@ -168,6 +173,7 @@ def test_force_minor(mocker, runner): noop=False, post=False, force_level="minor", + prerelease=False, retry=False, define=(), ) @@ -182,6 +188,7 @@ def test_force_patch(mocker, runner): noop=False, post=False, force_level="patch", + prerelease=False, retry=False, define=(), ) @@ -196,6 +203,7 @@ def test_retry(mocker, runner): noop=False, post=False, force_level=None, + prerelease=False, retry=True, define=(), ) @@ -206,7 +214,8 @@ def test_noop_mode(mocker): mock_tag_new_version = mocker.patch("semantic_release.cli.tag_new_version") mock_set_new = mocker.patch("semantic_release.cli.commit_new_version") mock_commit_new = mocker.patch("semantic_release.cli.set_new_version") - mocker.patch("semantic_release.cli.evaluate_version_bump", lambda *x: "major") + mocker.patch("semantic_release.cli.evaluate_version_bump", + lambda *x: "major") version(noop=True) @@ -221,6 +230,7 @@ def test_cli_print_version(mocker, runner): mock_print_version.assert_called_once_with( current=False, force_level=None, + prerelease=False, noop=False, post=False, retry=False, @@ -235,6 +245,22 @@ def test_cli_print_version_force_major(mocker, runner): mock_print_version.assert_called_once_with( current=False, force_level="major", + prerelease=False, + noop=False, + post=False, + retry=False, + define=(), + ) + assert result.exit_code == 0 + + +def test_cli_print_version_prerelease(mocker, runner): + mock_print_version = mocker.patch("semantic_release.cli.print_version") + result = runner.invoke(main, ["print-version", "--prerelease"]) + mock_print_version.assert_called_once_with( + current=False, + force_level=None, + prerelease=True, noop=False, post=False, retry=False, @@ -249,6 +275,7 @@ def test_cli_print_version_current(mocker, runner): mock_print_version.assert_called_once_with( current=True, force_level=None, + prerelease=False, noop=False, post=False, retry=False, @@ -263,6 +290,7 @@ def test_cli_print_version_next(mocker, runner): mock_print_version.assert_called_once_with( current=False, force_level=None, + prerelease=False, noop=False, post=False, retry=False, @@ -289,7 +317,7 @@ def test_print_version_no_change(mocker, runner, capsys): mock_current_version.assert_called_once_with() mock_evaluate_bump.assert_called_once_with("1.2.3", None) - mock_new_version.assert_called_once_with("1.2.3", None) + mock_new_version.assert_called_once_with("1.2.3", None, False) def test_print_version_change(mocker, runner, capsys): @@ -309,6 +337,43 @@ def test_print_version_change(mocker, runner, capsys): mock_evaluate_bump.assert_called_once_with("1.2.3", None) +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_evaluate_bump = mocker.patch( + "semantic_release.cli.evaluate_version_bump", return_value="minor" + ) + + print_version(prerelease=True) + outerr = capsys.readouterr() + assert outerr.out == "1.3.0-beta.0" + assert outerr.err == "" + + mock_current_version.assert_called_once() + mock_evaluate_bump.assert_called_once_with("1.2.3", None) + + +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_evaluate_bump = mocker.patch( + "semantic_release.cli.evaluate_version_bump", return_value="minor" + ) + + print_version(prerelease=True) + outerr = capsys.readouterr() + assert outerr.out == "1.3.0-beta.1" + assert outerr.err == "" + + mock_current_version.assert_called_once() + mock_evaluate_bump.assert_called_once_with("1.2.3", None) + + 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" @@ -326,9 +391,27 @@ def test_print_version_force_major(mocker, runner, capsys): mock_evaluate_bump.assert_called_once_with("1.2.3", "major") +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_evaluate_bump = mocker.patch( + "semantic_release.cli.evaluate_version_bump", return_value="major" + ) + + print_version(force_level="major", prerelease=True) + outerr = capsys.readouterr() + assert outerr.out == "2.0.0-beta.0" + assert outerr.err == "" + + mock_current_version.assert_called_once() + mock_evaluate_bump.assert_called_once_with("1.2.3", "major") + + def test_version_no_change(mocker, runner): mock_tag_new_version = mocker.patch("semantic_release.cli.tag_new_version") - mock_commit_new_version = mocker.patch("semantic_release.cli.commit_new_version") + mock_commit_new_version = mocker.patch( + "semantic_release.cli.commit_new_version") mock_set_new_version = mocker.patch("semantic_release.cli.set_new_version") mock_new_version = mocker.patch( "semantic_release.cli.get_new_version", return_value="1.2.3" @@ -344,7 +427,7 @@ def test_version_no_change(mocker, runner): mock_current_version.assert_called_once_with() mock_evaluate_bump.assert_called_once_with("1.2.3", None) - mock_new_version.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 assert not mock_commit_new_version.called assert not mock_tag_new_version.called @@ -358,7 +441,8 @@ def test_version_check_build_status_fails(mocker): mock_commit_new = mocker.patch("semantic_release.cli.commit_new_version") mock_set_new = mocker.patch("semantic_release.cli.set_new_version") mocker.patch("semantic_release.cli.config.get", lambda *x: True) - mocker.patch("semantic_release.cli.evaluate_version_bump", lambda *x: "major") + mocker.patch("semantic_release.cli.evaluate_version_bump", + lambda *x: "major") version() @@ -374,7 +458,8 @@ def test_version_by_commit_check_build_status_succeeds(mocker): "semantic_release.cli.check_build_status", return_value=True ) mock_tag_new_version = mocker.patch("semantic_release.cli.tag_new_version") - mocker.patch("semantic_release.cli.evaluate_version_bump", lambda *x: "major") + mocker.patch("semantic_release.cli.evaluate_version_bump", + lambda *x: "major") mock_commit_new = mocker.patch("semantic_release.cli.commit_new_version") mock_set_new = mocker.patch("semantic_release.cli.set_new_version") @@ -400,7 +485,8 @@ def test_version_by_tag_check_build_status_succeeds(mocker): ) mock_set_new_version = mocker.patch("semantic_release.cli.set_new_version") mock_tag_new_version = mocker.patch("semantic_release.cli.tag_new_version") - mocker.patch("semantic_release.cli.evaluate_version_bump", lambda *x: "major") + mocker.patch("semantic_release.cli.evaluate_version_bump", + lambda *x: "major") version() @@ -410,10 +496,12 @@ def test_version_by_tag_check_build_status_succeeds(mocker): def test_version_check_build_status_not_called_if_disabled(mocker): - mock_check_build_status = mocker.patch("semantic_release.cli.check_build_status") + mock_check_build_status = mocker.patch( + "semantic_release.cli.check_build_status") mocker.patch("semantic_release.cli.config.get", lambda *x, **y: False) mocker.patch("semantic_release.cli.tag_new_version") - mocker.patch("semantic_release.cli.evaluate_version_bump", lambda *x: "major") + mocker.patch("semantic_release.cli.evaluate_version_bump", + lambda *x: "major") mocker.patch("semantic_release.cli.commit_new_version") mocker.patch("semantic_release.cli.set_new_version") @@ -424,7 +512,8 @@ def test_version_check_build_status_not_called_if_disabled(mocker): def test_version_retry_and_giterror(mocker): mocker.patch( - "semantic_release.cli.get_current_version", mock.Mock(side_effect=GitError()) + "semantic_release.cli.get_current_version", mock.Mock( + side_effect=GitError()) ) result = version(retry=True) @@ -449,7 +538,7 @@ def test_version_retry(mocker): assert result mock_get_current.assert_called_once_with() mock_evaluate_bump.assert_called_once_with("current", False) - mock_get_new.assert_called_once_with("current", "patch") + mock_get_new.assert_called_once_with("current", "patch", False) def test_publish_should_not_run_pre_commit_by_default(mocker): @@ -460,7 +549,8 @@ def test_publish_should_not_run_pre_commit_by_default(mocker): mocker.patch("semantic_release.cli.post_changelog", lambda *x: True) mocker.patch("semantic_release.cli.push_new_version", return_value=True) mocker.patch("semantic_release.cli.should_bump_version", return_value=True) - mocker.patch("semantic_release.cli.markdown_changelog", lambda *x, **y: "CHANGES") + mocker.patch("semantic_release.cli.markdown_changelog", + lambda *x, **y: "CHANGES") mocker.patch("semantic_release.cli.update_changelog_file") mocker.patch("semantic_release.cli.bump_version") mocker.patch("semantic_release.cli.get_new_version", lambda *x: "2.0.0") @@ -474,7 +564,8 @@ def test_publish_should_not_run_pre_commit_by_default(mocker): upload_to_release=False, ), ) - mocker.patch("semantic_release.cli.update_changelog_file", lambda *x, **y: None) + mocker.patch("semantic_release.cli.update_changelog_file", + lambda *x, **y: None) publish() @@ -489,7 +580,8 @@ def test_publish_should_not_run_pre_commit_with_empty_command(mocker): mocker.patch("semantic_release.cli.post_changelog", lambda *x: True) mocker.patch("semantic_release.cli.push_new_version", return_value=True) mocker.patch("semantic_release.cli.should_bump_version", return_value=True) - mocker.patch("semantic_release.cli.markdown_changelog", lambda *x, **y: "CHANGES") + mocker.patch("semantic_release.cli.markdown_changelog", + lambda *x, **y: "CHANGES") mocker.patch("semantic_release.cli.update_changelog_file") mocker.patch("semantic_release.cli.bump_version") mocker.patch("semantic_release.cli.get_new_version", lambda *x: "2.0.0") @@ -504,7 +596,8 @@ def test_publish_should_not_run_pre_commit_with_empty_command(mocker): pre_commit_command="", ), ) - mocker.patch("semantic_release.cli.update_changelog_file", lambda *x, **y: None) + mocker.patch("semantic_release.cli.update_changelog_file", + lambda *x, **y: None) publish() @@ -519,7 +612,8 @@ def test_publish_should_run_pre_commit_if_provided(mocker): mocker.patch("semantic_release.cli.post_changelog", lambda *x: True) mocker.patch("semantic_release.cli.push_new_version", return_value=True) mocker.patch("semantic_release.cli.should_bump_version", return_value=True) - mocker.patch("semantic_release.cli.markdown_changelog", lambda *x, **y: "CHANGES") + mocker.patch("semantic_release.cli.markdown_changelog", + lambda *x, **y: "CHANGES") mocker.patch("semantic_release.cli.update_changelog_file") mocker.patch("semantic_release.cli.bump_version") mocker.patch("semantic_release.cli.get_new_version", lambda *x: "2.0.0") @@ -534,7 +628,8 @@ def test_publish_should_run_pre_commit_if_provided(mocker): pre_commit_command='echo "Hello, world."', ), ) - mocker.patch("semantic_release.cli.update_changelog_file", lambda *x, **y: None) + mocker.patch("semantic_release.cli.update_changelog_file", + lambda *x, **y: None) publish() @@ -545,11 +640,14 @@ def test_publish_should_not_upload_to_pypi_if_option_is_false(mocker): mocker.patch("semantic_release.cli.checkout") mocker.patch("semantic_release.cli.ci_checks.check") mock_repository = mocker.patch.object(ArtifactRepo, "upload") - mock_upload_release = mocker.patch("semantic_release.cli.upload_to_release") + mock_upload_release = mocker.patch( + "semantic_release.cli.upload_to_release") mocker.patch("semantic_release.cli.post_changelog", lambda *x: True) mocker.patch("semantic_release.cli.push_new_version", return_value=True) - mocker.patch("semantic_release.cli.should_bump_version", return_value=False) - mocker.patch("semantic_release.cli.markdown_changelog", lambda *x, **y: "CHANGES") + mocker.patch("semantic_release.cli.should_bump_version", + return_value=False) + mocker.patch("semantic_release.cli.markdown_changelog", + lambda *x, **y: "CHANGES") mocker.patch("semantic_release.cli.update_changelog_file") mocker.patch("semantic_release.cli.bump_version") mocker.patch("semantic_release.cli.get_new_version", lambda *x: "2.0.0") @@ -562,7 +660,8 @@ def test_publish_should_not_upload_to_pypi_if_option_is_false(mocker): upload_to_release=False, ), ) - mocker.patch("semantic_release.cli.update_changelog_file", lambda *x, **y: None) + mocker.patch("semantic_release.cli.update_changelog_file", + lambda *x, **y: None) publish() @@ -574,11 +673,14 @@ def test_publish_should_not_upload_to_repository_if_option_is_false(mocker): mocker.patch("semantic_release.cli.checkout") mocker.patch("semantic_release.cli.ci_checks.check") mock_repository = mocker.patch.object(ArtifactRepo, "upload") - mock_upload_release = mocker.patch("semantic_release.cli.upload_to_release") + mock_upload_release = mocker.patch( + "semantic_release.cli.upload_to_release") mocker.patch("semantic_release.cli.post_changelog", lambda *x: True) mocker.patch("semantic_release.cli.push_new_version", return_value=True) - mocker.patch("semantic_release.cli.should_bump_version", return_value=False) - mocker.patch("semantic_release.cli.markdown_changelog", lambda *x, **y: "CHANGES") + mocker.patch("semantic_release.cli.should_bump_version", + return_value=False) + mocker.patch("semantic_release.cli.markdown_changelog", + lambda *x, **y: "CHANGES") mocker.patch("semantic_release.cli.update_changelog_file") mocker.patch("semantic_release.cli.bump_version") mocker.patch("semantic_release.cli.get_new_version", lambda *x: "2.0.0") @@ -591,7 +693,8 @@ def test_publish_should_not_upload_to_repository_if_option_is_false(mocker): upload_to_release=False, ), ) - mocker.patch("semantic_release.cli.update_changelog_file", lambda *x, **y: None) + mocker.patch("semantic_release.cli.update_changelog_file", + lambda *x, **y: None) publish() @@ -602,11 +705,13 @@ def test_publish_should_not_upload_to_repository_if_option_is_false(mocker): def test_publish_should_do_nothing_when_not_should_bump_version(mocker): mocker.patch("semantic_release.cli.checkout") mocker.patch("semantic_release.cli.get_new_version", lambda *x: "2.0.0") - mocker.patch("semantic_release.cli.evaluate_version_bump", lambda *x: "feature") + mocker.patch("semantic_release.cli.evaluate_version_bump", + lambda *x: "feature") mocker.patch("semantic_release.cli.generate_changelog") mock_log = mocker.patch("semantic_release.cli.post_changelog") mock_repository = mocker.patch.object(ArtifactRepo, "upload") - mock_upload_release = mocker.patch("semantic_release.cli.upload_to_release") + mock_upload_release = mocker.patch( + "semantic_release.cli.upload_to_release") mock_push = mocker.patch("semantic_release.cli.push_new_version") mock_ci_check = mocker.patch("semantic_release.ci_checks.check") mock_should_bump_version = mocker.patch( @@ -646,9 +751,11 @@ def test_publish_should_call_functions(mocker): "semantic_release.cli.get_repository_owner_and_name", return_value=("relekang", "python-semantic-release"), ) - mocker.patch("semantic_release.cli.evaluate_version_bump", lambda *x: "feature") + mocker.patch("semantic_release.cli.evaluate_version_bump", + lambda *x: "feature") mocker.patch("semantic_release.cli.generate_changelog") - mocker.patch("semantic_release.cli.markdown_changelog", lambda *x, **y: "CHANGES") + mocker.patch("semantic_release.cli.markdown_changelog", + lambda *x, **y: "CHANGES") mocker.patch("semantic_release.cli.update_changelog_file") mocker.patch("semantic_release.cli.bump_version") mocker.patch("semantic_release.cli.get_new_version", lambda *x: "2.0.0") @@ -692,9 +799,11 @@ def test_publish_should_skip_build_when_command_is_empty(mocker): "semantic_release.cli.get_repository_owner_and_name", return_value=("relekang", "python-semantic-release"), ) - mocker.patch("semantic_release.cli.evaluate_version_bump", lambda *x: "feature") + mocker.patch("semantic_release.cli.evaluate_version_bump", + lambda *x: "feature") mocker.patch("semantic_release.cli.generate_changelog") - mocker.patch("semantic_release.cli.markdown_changelog", lambda *x, **y: "CHANGES") + mocker.patch("semantic_release.cli.markdown_changelog", + lambda *x, **y: "CHANGES") mocker.patch("semantic_release.cli.update_changelog_file") mocker.patch("semantic_release.cli.bump_version") mocker.patch("semantic_release.cli.get_new_version", lambda *x: "2.0.0") @@ -774,7 +883,8 @@ def test_publish_bad_token(mocker): remove_dist=False, ), ) - mock_should_bump_version = mocker.patch("semantic_release.cli.should_bump_version") + mock_should_bump_version = mocker.patch( + "semantic_release.cli.should_bump_version") mock_get_token = mocker.patch( "semantic_release.cli.get_token", return_value="SUPERTOKEN" ) @@ -857,21 +967,23 @@ def test_publish_giterror_when_posting(mocker): "semantic_release.cli.update_changelog_file" ) mock_post = mocker.patch( - "semantic_release.cli.post_changelog", mock.Mock(side_effect=GitError()) + "semantic_release.cli.post_changelog", mock.Mock( + side_effect=GitError()) ) publish(noop=False, retry=False, force_level=False) mock_get_current.assert_called_once_with() mock_evaluate.assert_called_once_with("current", False) - mock_get_new.assert_called_once_with("current", "patch") + mock_get_new.assert_called_once_with("current", "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 ) - mock_update_changelog_file.assert_called_once_with("new", "super md changelog") + mock_update_changelog_file.assert_called_once_with( + "new", "super md changelog") mock_bump_version.assert_called_once_with("new", "patch") mock_get_token.assert_called_once_with() mock_get_domain.assert_called_once_with() @@ -892,17 +1004,20 @@ def test_publish_giterror_when_posting(mocker): header=False, previous_version="current", ) - mock_post.assert_called_once_with("owner", "name", "new", "super md changelog") + mock_post.assert_called_once_with( + "owner", "name", "new", "super md changelog") def test_changelog_should_call_functions(mocker, runner): - mock_changelog = mocker.patch("semantic_release.cli.changelog", return_value=True) + mock_changelog = mocker.patch( + "semantic_release.cli.changelog", return_value=True) result = runner.invoke(main, ["changelog"]) assert result.exit_code == 0 mock_changelog.assert_called_once_with( noop=False, post=False, force_level=None, + prerelease=False, retry=False, unreleased=False, define=(), @@ -929,7 +1044,8 @@ def test_overload_by_cli(mocker, runner): def test_changelog_noop(mocker): - mocker.patch("semantic_release.cli.get_current_version", return_value="current") + mocker.patch("semantic_release.cli.get_current_version", + return_value="current") mock_previous_version = mocker.patch( "semantic_release.cli.get_previous_version", return_value="previous" ) @@ -954,7 +1070,8 @@ def test_changelog_noop(mocker): def test_changelog_post_unreleased_no_token(mocker): - mocker.patch("semantic_release.cli.get_current_version", return_value="current") + mocker.patch("semantic_release.cli.get_current_version", + return_value="current") mock_previous_version = mocker.patch( "semantic_release.cli.get_previous_version", return_value="previous" ) @@ -983,7 +1100,8 @@ def test_changelog_post_unreleased_no_token(mocker): def test_changelog_post_complete(mocker): - mocker.patch("semantic_release.cli.get_current_version", return_value="current") + mocker.patch("semantic_release.cli.get_current_version", + return_value="current") mock_previous_version = mocker.patch( "semantic_release.cli.get_previous_version", return_value="previous" ) diff --git a/tests/test_vcs_helpers.py b/tests/test_vcs_helpers.py index 0227d0b91..1fbabf950 100644 --- a/tests/test_vcs_helpers.py +++ b/tests/test_vcs_helpers.py @@ -348,6 +348,47 @@ def __init__(self, name, sha, date, is_tag_object): assert expected_result == get_last_version(skip_tags) +@pytest.mark.parametrize( + "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), + ], +) +def test_get_last_version_with_omit_pattern(skip_tags, expected_result): + class FakeCommit: + def __init__(self, com_date): + self.committed_date = com_date + + class FakeTagObject: + def __init__(self, tag_date): + self.tagged_date = tag_date + + class FakeTag: + def __init__(self, name, sha, date, is_tag_object): + self.name = name + self.tag = FakeTagObject(date) + if is_tag_object: + self.commit = TagObject(Repo(), sha) + else: + self.commit = FakeCommit(date) + + mock.patch("semantic_release.vcs_helpers.check_repo") + git.repo.base.Repo.tags = mock.PropertyMock( + 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("badly_formatted", "eeeeeeeeeeeeeeeeeeee", 6, False), + FakeTag("v2.0.0-beta", "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", [