Repository: relekang/python-semantic-release
Pull request #435 (2022-04-07 14:16:18)

Fix/Refactor: Prerelease 
## Ready for review

Related to https://github.com/relekang/python-semantic-release/issues/434

When digging into the issue I encountered some more misalignments in my previous provided code for the prerelease feature.
With this PR I want to fix this issues and also do some normalisation work in the package.

@danth @williamluke4 @relekang 
~~Curiously, locally all tests pass. I checked and the test fail because of methods which traverse the git log... 
Locally all commits starting from the current branch are analysed (ending with the init commit) - but in the ci pipeline, there is only one commit (Some merge commit I can't find nowhere else... ). Any Idea why this is happening and how to prevent it?~~
Solved: The problem was, that by default githubs `actions/checkout@v2` checks out a single merge commit. It has to be instructed to checkout the actual brach with its full history.

**Main changes:**

* The way new versions are determined now utilise the semvar package
* normalised the way versions are matched via regex
* Better and unified way to get the current release version

**Unsolved Issues:**

* The whole flow only works, if the same branch is issued for all releases and version bumps (possible improvement: https://github.com/relekang/python-semantic-release/issues/423). To make it work on different branches we need to change how tags are analysed (just in the current branch history)

**Possible improvements:**
There are now two similar methods to get the current version: 
`get_current_version`: gets the _last_ version, whatever is the last tag or in the config file
`get_current_release_version`: gets the _last not-prerelease_ version (this is needed to determine the correct level_bump)
for ease of use (they are mostly used together) and further development, this two methods could be merged and return a tuple `(current_version, current_release_version)`

jacksbox: rebased 
jacksbox: @danth / @williamluke4 
can you hit merge ;)

Patch:
diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml
index 3e7023a..9db18bf 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:
diff --git a/semantic_release/cli.py b/semantic_release/cli.py
index bed36b1..b5d17b5 100644
--- a/semantic_release/cli.py
+++ b/semantic_release/cli.py
@@ -16,8 +16,10 @@ from .dist import build_dists, remove_dists, should_build, should_remove_dist
 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
@@ -98,7 +100,9 @@ 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()
+        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
@@ -130,14 +134,15 @@ def version(*, retry=False, noop=False, force_level=None, prerelease=False, **kw
 
     # Get the current version number
     try:
-        current_version = get_current_version(prerelease)
-        logger.info(f"Current version: {current_version}")
+        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:
         logger.error(str(e))
         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, 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
@@ -207,7 +212,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 +250,9 @@ 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()
+    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:
@@ -254,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 fa9e6f6..7e0d617 100644
--- a/semantic_release/history/__init__.py
+++ b/semantic_release/history/__init__.py
@@ -25,6 +25,14 @@ from .parser_tag import parse_commit_message as tag_parser  # noqa isort:skip
 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}))"
+
+release_version_regex = rf"{release_version_pattern}"
+version_regex = rf"{version_pattern}"
+
+
 class VersionDeclaration(ABC):
     def __init__(self, path: Union[str, Path]):
         self.path = Path(path)
@@ -46,7 +54,7 @@ class VersionDeclaration(ABC):
         """
         path, variable = config_str.split(":", 1)
         pattern = (
-            rf'{variable} *[:=] *["\']{PatternVersionDeclaration.version_regex}["\']'
+            rf'{variable} *[:=] *["\']{version_regex}["\']'
         )
         return PatternVersionDeclaration(path, pattern)
 
@@ -57,7 +65,7 @@ class VersionDeclaration(ABC):
         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 +124,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 +182,29 @@ class PatternVersionDeclaration(VersionDeclaration):
         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:
+    """
+    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=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_tag(omit_pattern=None) -> str:
+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(omit_pattern=omit_pattern)
+    version = get_last_version(pattern=release_version_pattern)
     if version:
         return version
 
@@ -196,7 +213,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,25 +237,36 @@ 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.
+    This can be either a release or prerelease version
 
     :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()
+
+
+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
+    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.
@@ -249,34 +277,46 @@ 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")
-        new_version = current_version
+    # pre or release version
+    current_version_info = semver.VersionInfo.parse(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:
-        new_version = str(
-            semver.VersionInfo.parse(current_version).next_version(part=level_bump)
-        )
+        next_version_info = current_release_version_info
 
     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
-            )
+        next_raw_version = next_version_info.to_tuple()[:3]
+        current_raw_version = current_version_info.to_tuple()[:3]
+
+        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:
-            logger.debug("No previouse prerelease detected, starting from 0")
-            prerelease_num = 0
-        new_version = new_version + get_prerelease_pattern() + str(prerelease_num)
+            # 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 new_version
+        return str(next_prerelease_version_info)
+    else:
+        # normal version bump
+        return str(next_version_info)
 
 
 @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 +332,55 @@ 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(pattern=version_pattern, skip_tags=[version, get_formatted_tag(version)])
+
 
-    return get_last_version(
-        [version, get_formatted_tag(version)], omit_pattern=omit_pattern
-    )
+@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:
+    """
+    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)
diff --git a/semantic_release/vcs_helpers.py b/semantic_release/vcs_helpers.py
index a58d984..9a6a724 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 get_last_version(skip_tags=None, omit_pattern=None) -> Optional[str]:
         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()
 
-@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/setup.cfg b/setup.cfg
index 7899cd1..a2c1554 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__


Test patch:
diff --git a/tests/history/test_version.py b/tests/history/test_version.py
index 0b4e262..5bffa36 100644
--- a/tests/history/test_version.py
+++ b/tests/history/test_version.py
@@ -13,6 +13,9 @@ from semantic_release.history import (
     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 @@ class TestGetPreviousVersion:
 
     @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"
@@ -95,42 +116,82 @@ class TestGetPreviousVersion:
         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 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"
+
+    @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):
-        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.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"
+        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)
 
-    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"
+        with pytest.raises(ValueError):
+            get_new_version("1.0.0", "0.9.0", None, True)
 
 
 @mock.patch(
@@ -153,7 +214,7 @@ class TestVersionPattern:
             (
                 "path:__version__",
                 Path("path"),
-                r'__version__ *[:=] *["\'](\d+\.\d+(?:\.\d+)?)["\']',
+                r'__version__ *[:=] *["\'](\d+\.\d+\.\d+(-beta\.\d+)?)["\']',
             ),
         ],
     )
@@ -166,7 +227,7 @@ class TestVersionPattern:
         "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 +414,7 @@ class TestVersionPattern:
                         version_variable = "path:__version__"
                         """,
             patterns=[
-                (Path("path"), r'__version__ *[:=] *["\'](\d+\.\d+(?:\.\d+)?)["\']'),
+                (Path("path"), r'__version__ *[:=] *["\'](\d+\.\d+\.\d+(-beta\.\d+)?)["\']'),
             ],
         ),
         dict(
@@ -362,8 +423,8 @@ class TestVersionPattern:
                         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 +436,8 @@ class TestVersionPattern:
                         ]
                         """,
             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 1cd2e07..feb4e1b 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_with(False)
+    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_with(False)
+    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")
@@ -118,12 +126,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")
@@ -145,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_with(False)
+    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")
 
@@ -171,12 +187,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")
 
@@ -198,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_with(False)
+    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
@@ -366,21 +390,28 @@ 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()
     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_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"
     )
@@ -390,7 +421,8 @@ 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_current_release_version.assert_called_once()
     mock_evaluate_bump.assert_called_once_with("1.2.3", None)
 
 
@@ -398,16 +430,20 @@ 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"
     )
 
     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_current_release_version.assert_called_once()
     mock_evaluate_bump.assert_called_once_with("1.2.3", None)
 
 
@@ -415,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"
@@ -427,7 +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_with(prerelease_version=True)
+    mock_current_version.assert_called_once()
+    mock_current_release_version.assert_called_once()
     mock_evaluate_bump.assert_called_once_with("1.2.3", None)
 
 
@@ -435,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"
     )
@@ -444,7 +484,8 @@ 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_current_release_version.assert_called_once()
     mock_evaluate_bump.assert_called_once_with("1.2.3", "major")
 
 
@@ -452,16 +493,20 @@ 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"
     )
 
     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()
+    mock_current_release_version.assert_called_once()
     mock_evaluate_bump.assert_called_once_with("1.2.3", "major")
 
 
@@ -478,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_with(False)
+    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
@@ -595,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"
@@ -609,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_with(False)
-    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):
@@ -888,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",
@@ -904,8 +960,9 @@ 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_previous.assert_called_once_with("current")
+    mock_get_current.assert_called_once()
+    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")
@@ -918,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",
@@ -950,8 +1010,9 @@ def test_publish_bad_token(mocker):
 
     publish(noop=False, retry=True, force_level=False)
 
-    mock_get_current.assert_called_once_with(False)
-    mock_get_previous.assert_called_once_with("current")
+    mock_get_current.assert_called_once()
+    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")
@@ -971,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"
@@ -1024,14 +1088,15 @@ def test_publish_giterror_when_posting(mocker):
 
     publish(noop=False, retry=False, force_level=False)
 
-    mock_get_current.assert_called_once_with(False)
-    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")
@@ -1045,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")
 
diff --git a/tests/test_vcs_helpers.py b/tests/test_vcs_helpers.py
index 1fbabf9..aa2e028 100644
--- a/tests/test_vcs_helpers.py
+++ b/tests/test_vcs_helpers.py
@@ -6,6 +6,7 @@ import pytest
 from git import GitCommandError, Repo, TagObject
 
 from semantic_release.errors import GitError, HvcsRepoParseError
+from semantic_release.history import version_pattern, release_version_pattern
 from semantic_release.vcs_helpers import (
     check_repo,
     checkout,
@@ -14,7 +15,6 @@ from semantic_release.vcs_helpers import (
     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,22 @@ def test_get_last_version(skip_tags, expected_result):
             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),
+        (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_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 +383,14 @@ def test_get_last_version_with_omit_pattern(skip_tags, expected_result):
         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):