diff --git a/docs/commands.rst b/docs/commands.rst index 1e9359b61..185f00015 100644 --- a/docs/commands.rst +++ b/docs/commands.rst @@ -144,6 +144,33 @@ command line options:: semantic-release version --patch --print semantic-release version --prerelease --print +.. _cmd-version-option-print-tag: + +``--print-tag`` +*************** + +Same as the :ref:`cmd-version-option-print` flag but prints the complete tag +name (ex. ``v1.0.0`` or ``py-v1.0.0``) instead of the raw version number +(``1.0.0``). + +.. _cmd-version-option-print-last-released: + +``--print-last-released`` +************************* + +Print the last released version based on the Git tags. This flag is useful if you just +want to see the released version without determining what the next version will be. +Note if the version can not be found nothing will be printed. + +.. _cmd-version-option-print-last-released-tag: + +``--print-last-released-tag`` +*************** + +Same as the :ref:`cmd-version-option-print-last-released` flag but prints the +complete tag name (ex. ``v1.0.0`` or ``py-v1.0.0``) instead of the raw version +number (``1.0.0``). + .. _cmd-version-option-force-level: ``--major/--minor/--patch`` diff --git a/pyproject.toml b/pyproject.toml index 74c37290b..c8f892919 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,6 +23,7 @@ readme = "README.rst" authors = [{ name = "Rolf Erik Lekang", email = "me@rolflekang.com" }] dependencies = [ "click>=8,<9", + "click-option-group~=0.5", "gitpython>=3.0.8,<4", "requests>=2.25,<3", "jinja2>=3.1.2,<4", diff --git a/semantic_release/cli/commands/version.py b/semantic_release/cli/commands/version.py index 8f422b120..83a4364fd 100644 --- a/semantic_release/cli/commands/version.py +++ b/semantic_release/cli/commands/version.py @@ -9,6 +9,7 @@ import click import shellingham # type: ignore[import] +from click_option_group import MutuallyExclusiveOptionGroup, optgroup from git.exc import GitCommandError from semantic_release.changelog import ReleaseHistory, environment, recursive_render @@ -26,8 +27,9 @@ log = logging.getLogger(__name__) -if TYPE_CHECKING: +if TYPE_CHECKING: # pragma: no cover from git import Repo + from git.refs.tag import Tag from semantic_release.cli.config import RuntimeContext from semantic_release.version import VersionTranslator @@ -48,6 +50,13 @@ def is_forced_prerelease( return force_prerelease or ((force_level is None) and prerelease) +def last_released( + repo: Repo, translator: VersionTranslator +) -> tuple[Tag, Version] | None: + ts_and_vs = tags_and_versions(repo.tags, translator) + return ts_and_vs[0] if ts_and_vs else None + + def version_from_forced_level( repo: Repo, level_bump: LevelBump, translator: VersionTranslator ) -> Version: @@ -111,9 +120,26 @@ def shell(cmd: str, *, check: bool = True) -> subprocess.CompletedProcess: "help_option_names": ["-h", "--help"], }, ) -@click.option( +@optgroup.group("Print flags", cls=MutuallyExclusiveOptionGroup) +@optgroup.option( "--print", "print_only", is_flag=True, help="Print the next version and exit" ) +@optgroup.option( + "--print-tag", + "print_only_tag", + is_flag=True, + help="Print the next version tag and exit", +) +@optgroup.option( + "--print-last-released", + is_flag=True, + help="Print the last released version and exit", +) +@optgroup.option( + "--print-last-released-tag", + is_flag=True, + help="Print the last released version tag and exit", +) @click.option( "--prerelease", "force_prerelease", @@ -191,6 +217,9 @@ def shell(cmd: str, *, check: bool = True) -> subprocess.CompletedProcess: def version( # noqa: C901 ctx: click.Context, print_only: bool = False, + print_only_tag: bool = False, + print_last_released: bool = False, + print_last_released_tag: bool = False, force_prerelease: bool = False, prerelease_token: str | None = None, force_level: str | None = None, @@ -219,8 +248,20 @@ def version( # noqa: C901 """ runtime: RuntimeContext = ctx.obj repo = runtime.repo - parser = runtime.commit_parser translator = runtime.version_translator + + # We can short circuit updating the release if we are only printing the last released version + if print_last_released or print_last_released_tag: + if last_release := last_released(repo, translator): + if print_last_released: + click.echo(last_release[1]) + if print_last_released_tag: + click.echo(last_release[0]) + else: + log.warning("No release tags found.") + ctx.exit(0) + + parser = runtime.commit_parser prerelease = is_forced_prerelease( force_prerelease=force_prerelease, force_level=force_level, @@ -301,7 +342,10 @@ def version( # noqa: C901 ctx.call_on_close(gha_output.write_if_possible) # Print the new version so that command-line output capture will work - click.echo(str(new_version)) + if print_only_tag: + click.echo(translator.str_to_tag(str(new_version))) + else: + click.echo(str(new_version)) # If the new version has already been released, we fail and abort if strict; # otherwise we exit with 0. @@ -318,7 +362,7 @@ def version( # noqa: C901 ) ctx.exit(0) - if print_only: + if print_only or print_only_tag: ctx.exit(0) rprint(f"[bold green]The next version is: [white]{new_version!s}[/white]! :rocket:") diff --git a/tests/command_line/test_version.py b/tests/command_line/test_version.py index ba2fbeaed..6f62e24fd 100644 --- a/tests/command_line/test_version.py +++ b/tests/command_line/test_version.py @@ -743,3 +743,36 @@ def test_version_only_update_files_no_git_actions( assert len(removed) == 1 assert re.match('__version__ = ".*"', removed[0]) assert added == [f'__version__ = "{expected_new_version}"\n'] + + +def test_version_print_last_released_prints_version( + repo_with_single_branch_tag_commits: Repo, cli_runner: CliRunner +): + result = cli_runner.invoke(main, [version.name, "--print-last-released"]) + assert result.exit_code == 0 + assert result.stdout == "0.1.1\n" + + +def test_version_print_last_released_prints_released_if_commits( + repo_with_single_branch_tag_commits: Repo, + example_project_dir: ExProjectDir, + cli_runner: CliRunner, +): + new_file = example_project_dir / "temp.txt" + new_file.write_text("test --print-last-released") + + repo_with_single_branch_tag_commits.git.add(str(new_file.resolve())) + repo_with_single_branch_tag_commits.git.commit(m="fix: temp new file") + + result = cli_runner.invoke(main, [version.name, "--print-last-released"]) + assert result.exit_code == 0 + assert result.stdout == "0.1.1\n" + + +def test_version_print_last_released_prints_nothing_if_no_tags( + caplog, repo_with_no_tags_angular_commits: Repo, cli_runner: CliRunner +): + result = cli_runner.invoke(main, [version.name, "--print-last-released"]) + assert result.exit_code == 0 + assert result.stdout == "" + assert "No release tags found." in caplog.text