diff --git a/src/semantic_release/cli/commands/version.py b/src/semantic_release/cli/commands/version.py index 2342fe6f7..6dc2774d2 100644 --- a/src/semantic_release/cli/commands/version.py +++ b/src/semantic_release/cli/commands/version.py @@ -26,6 +26,7 @@ from semantic_release.errors import ( BuildDistributionsError, GitCommitEmptyIndexError, + InternalError, UnexpectedResponse, ) from semantic_release.gitproject import GitProject @@ -35,7 +36,6 @@ tags_and_versions, ) from semantic_release.version.translator import VersionTranslator -from semantic_release.version.version import Version if TYPE_CHECKING: # pragma: no cover from pathlib import Path @@ -45,6 +45,7 @@ from semantic_release.cli.cli_context import CliContextObj from semantic_release.version.declaration import VersionDeclarationABC + from semantic_release.version.version import Version log = logging.getLogger(__name__) @@ -90,7 +91,18 @@ def version_from_forced_level( # If we have no tags, return the default version if not ts_and_vs: - return Version.parse(DEFAULT_VERSION).bump(forced_level_bump) + # Since the translator is configured by the user, we can't guarantee that it will + # be able to parse the default version. So we first cast it to a tag using the default + # value and the users configured tag format, then parse it back to a version object + default_initial_version = translator.from_tag( + translator.str_to_tag(DEFAULT_VERSION) + ) + if default_initial_version is None: + # This should never happen, but if it does, it's a bug + raise InternalError( + "Translator was unable to parse the embedded default version" + ) + return default_initial_version.bump(forced_level_bump) _, latest_version = ts_and_vs[0] if forced_level_bump is not LevelBump.PRERELEASE_REVISION: diff --git a/src/semantic_release/version/algorithm.py b/src/semantic_release/version/algorithm.py index e9a254978..cc2a7dfc3 100644 --- a/src/semantic_release/version/algorithm.py +++ b/src/semantic_release/version/algorithm.py @@ -270,6 +270,7 @@ def next_version( translator.str_to_tag(DEFAULT_VERSION) ) if default_initial_version is None: + # This should never happen, but if it does, it's a bug raise InternalError( "Translator was unable to parse the embedded default version" ) diff --git a/tests/e2e/cmd_version/test_version_print.py b/tests/e2e/cmd_version/test_version_print.py index 922160358..b0226387d 100644 --- a/tests/e2e/cmd_version/test_version_print.py +++ b/tests/e2e/cmd_version/test_version_print.py @@ -19,6 +19,9 @@ repo_w_trunk_only_angular_commits, repo_w_trunk_only_angular_commits_using_tag_format, ) +from tests.fixtures.repos.trunk_based_dev.repo_w_no_tags import ( + repo_w_no_tags_angular_commits_using_tag_format, +) from tests.util import ( add_text_to_file, assert_exit_code, @@ -149,52 +152,107 @@ def test_version_print_next_version( @pytest.mark.parametrize( "repo_result, commits, force_args, next_release_version", [ - ( - lazy_fixture(repo_fixture_name), - lazy_fixture(angular_minor_commits.__name__), - cli_args, - next_release_version, - ) - for repo_fixture_name in ( - repo_w_trunk_only_angular_commits.__name__, - repo_w_trunk_only_angular_commits_using_tag_format.__name__, - ) - for cli_args, next_release_version in ( - # Dynamic version bump determination (based on commits) - ([], "0.2.0"), - # Dynamic version bump determination (based on commits) with build metadata - (["--build-metadata", "build.12345"], "0.2.0+build.12345"), - # Forced version bump - (["--prerelease"], "0.1.1-rc.1"), - (["--patch"], "0.1.2"), - (["--minor"], "0.2.0"), - (["--major"], "1.0.0"), - # Forced version bump with --build-metadata - (["--patch", "--build-metadata", "build.12345"], "0.1.2+build.12345"), - # Forced version bump with --as-prerelease - (["--prerelease", "--as-prerelease"], "0.1.1-rc.1"), - (["--patch", "--as-prerelease"], "0.1.2-rc.1"), - (["--minor", "--as-prerelease"], "0.2.0-rc.1"), - (["--major", "--as-prerelease"], "1.0.0-rc.1"), - # Forced version bump with --as-prerelease and modified --prerelease-token - ( - ["--patch", "--as-prerelease", "--prerelease-token", "beta"], - "0.1.2-beta.1", - ), - # Forced version bump with --as-prerelease and modified --prerelease-token - # and --build-metadata - ( - [ - "--patch", - "--as-prerelease", - "--prerelease-token", - "beta", - "--build-metadata", - "build.12345", - ], - "0.1.2-beta.1+build.12345", - ), - ) + *[ + pytest.param( + lazy_fixture(repo_fixture_name), + lazy_fixture(angular_minor_commits.__name__), + cli_args, + next_release_version, + marks=marks if marks else [], + ) + for repo_fixture_name, marks in ( + (repo_w_trunk_only_angular_commits.__name__, None), + ( + repo_w_trunk_only_angular_commits_using_tag_format.__name__, + pytest.mark.comprehensive, + ), + ) + for cli_args, next_release_version in ( + # Dynamic version bump determination (based on commits) + ([], "0.2.0"), + # Dynamic version bump determination (based on commits) with build metadata + (["--build-metadata", "build.12345"], "0.2.0+build.12345"), + # Forced version bump + (["--prerelease"], "0.1.1-rc.1"), + (["--patch"], "0.1.2"), + (["--minor"], "0.2.0"), + (["--major"], "1.0.0"), + # Forced version bump with --build-metadata + (["--patch", "--build-metadata", "build.12345"], "0.1.2+build.12345"), + # Forced version bump with --as-prerelease + (["--prerelease", "--as-prerelease"], "0.1.1-rc.1"), + (["--patch", "--as-prerelease"], "0.1.2-rc.1"), + (["--minor", "--as-prerelease"], "0.2.0-rc.1"), + (["--major", "--as-prerelease"], "1.0.0-rc.1"), + # Forced version bump with --as-prerelease and modified --prerelease-token + ( + ["--patch", "--as-prerelease", "--prerelease-token", "beta"], + "0.1.2-beta.1", + ), + # Forced version bump with --as-prerelease and modified --prerelease-token + # and --build-metadata + ( + [ + "--patch", + "--as-prerelease", + "--prerelease-token", + "beta", + "--build-metadata", + "build.12345", + ], + "0.1.2-beta.1+build.12345", + ), + ) + ], + *[ + pytest.param( + lazy_fixture(repo_fixture_name), + [], + cli_args, + next_release_version, + marks=pytest.mark.comprehensive, + ) + for repo_fixture_name in ( + repo_w_no_tags_angular_commits.__name__, + repo_w_no_tags_angular_commits_using_tag_format.__name__, + ) + for cli_args, next_release_version in ( + # Dynamic version bump determination (based on commits) + ([], "0.1.0"), + # Dynamic version bump determination (based on commits) with build metadata + (["--build-metadata", "build.12345"], "0.1.0+build.12345"), + # Forced version bump + (["--prerelease"], "0.0.0-rc.1"), + (["--patch"], "0.0.1"), + (["--minor"], "0.1.0"), + (["--major"], "1.0.0"), + # Forced version bump with --build-metadata + (["--patch", "--build-metadata", "build.12345"], "0.0.1+build.12345"), + # Forced version bump with --as-prerelease + (["--prerelease", "--as-prerelease"], "0.0.0-rc.1"), + (["--patch", "--as-prerelease"], "0.0.1-rc.1"), + (["--minor", "--as-prerelease"], "0.1.0-rc.1"), + (["--major", "--as-prerelease"], "1.0.0-rc.1"), + # Forced version bump with --as-prerelease and modified --prerelease-token + ( + ["--patch", "--as-prerelease", "--prerelease-token", "beta"], + "0.0.1-beta.1", + ), + # Forced version bump with --as-prerelease and modified --prerelease-token + # and --build-metadata + ( + [ + "--patch", + "--as-prerelease", + "--prerelease-token", + "beta", + "--build-metadata", + "build.12345", + ], + "0.0.1-beta.1+build.12345", + ), + ) + ], ], ) def test_version_print_tag_prints_next_tag( @@ -227,10 +285,11 @@ def test_version_print_tag_prints_next_tag( tag_format_str: str = get_cfg_value_from_def(repo_def, "tag_format_str") # type: ignore[assignment] next_release_tag = tag_format_str.format(version=next_release_version) - # Make a commit to ensure we have something to release - # otherwise the "no release will be made" logic will kick in first - add_text_to_file(repo, file_in_repo) - repo.git.commit(m=commits[-1], a=True) + if len(commits) > 1: + # Make a commit to ensure we have something to release + # otherwise the "no release will be made" logic will kick in first + add_text_to_file(repo, file_in_repo) + repo.git.commit(m=commits[-1], a=True) # Setup: take measurement before running the version command repo_status_before = repo.git.status(short=True) diff --git a/tests/fixtures/repos/trunk_based_dev/repo_w_no_tags.py b/tests/fixtures/repos/trunk_based_dev/repo_w_no_tags.py index 176d5a5d5..45c3b40dd 100644 --- a/tests/fixtures/repos/trunk_based_dev/repo_w_no_tags.py +++ b/tests/fixtures/repos/trunk_based_dev/repo_w_no_tags.py @@ -214,6 +214,50 @@ def _build_repo(cached_repo_path: Path) -> Sequence[RepoActions]: # --------------------------------------------------------------------------- # +@pytest.fixture +def repo_w_no_tags_angular_commits_using_tag_format( + build_repo_from_definition: BuildRepoFromDefinitionFn, + get_repo_definition_4_trunk_only_repo_w_no_tags: GetRepoDefinitionFn, + get_cached_repo_data: GetCachedRepoDataFn, + build_repo_or_copy_cache: BuildRepoOrCopyCacheFn, + build_spec_hash_4_repo_w_no_tags: str, + example_project_git_repo: ExProjectGitRepoFn, + example_project_dir: ExProjectDir, + change_to_ex_proj_dir: None, +) -> BuiltRepoResult: + """ + Replicates repo with no tags, but with a tag format X{version} + + Follows tag format defined in python-semantic-release#1137 + """ + repo_name = repo_w_no_tags_angular_commits_using_tag_format.__name__ + commit_type: CommitConvention = ( + repo_name.split("_commits", maxsplit=1)[0].split("_")[-1] # type: ignore[assignment] + ) + + def _build_repo(cached_repo_path: Path) -> Sequence[RepoActions]: + repo_construction_steps = get_repo_definition_4_trunk_only_repo_w_no_tags( + commit_type=commit_type, + tag_format_str="X{version}", + ) + return build_repo_from_definition(cached_repo_path, repo_construction_steps) + + build_repo_or_copy_cache( + repo_name=repo_name, + build_spec_hash=build_spec_hash_4_repo_w_no_tags, + build_repo_func=_build_repo, + dest_dir=example_project_dir, + ) + + if not (cached_repo_data := get_cached_repo_data(proj_dirname=repo_name)): + raise ValueError("Failed to retrieve repo data from cache") + + return { + "definition": cached_repo_data["build_definition"], + "repo": example_project_git_repo(), + } + + @pytest.fixture def repo_w_no_tags_angular_commits( build_trunk_only_repo_w_no_tags: BuildSpecificRepoFn,