diff --git a/commitizen/bump.py b/commitizen/bump.py
index ed410bbcc1..1c0f7ac2d8 100644
--- a/commitizen/bump.py
+++ b/commitizen/bump.py
@@ -217,42 +217,6 @@ def _version_to_regex(version: str) -> str:
     return version.replace(".", r"\.").replace("+", r"\+")
 
 
-def normalize_tag(
-    version: Union[VersionProtocol, str],
-    tag_format: Optional[str] = None,
-    version_type_cls: Optional[Type[VersionProtocol]] = None,
-) -> str:
-    """The tag and the software version might be different.
-
-    That's why this function exists.
-
-    Example:
-    | tag | version (PEP 0440) |
-    | --- | ------- |
-    | v0.9.0 | 0.9.0 |
-    | ver1.0.0 | 1.0.0 |
-    | ver1.0.0.a0 | 1.0.0a0 |
-    """
-    if version_type_cls is None:
-        version_type_cls = Version
-    if isinstance(version, str):
-        version = version_type_cls(version)
-
-    if not tag_format:
-        return str(version)
-
-    major, minor, patch = version.release
-    prerelease = ""
-    # version.pre is needed for mypy check
-    if version.is_prerelease and version.pre:
-        prerelease = f"{version.pre[0]}{version.pre[1]}"
-
-    t = Template(tag_format)
-    return t.safe_substitute(
-        version=version, major=major, minor=minor, patch=patch, prerelease=prerelease
-    )
-
-
 def create_commit_message(
     current_version: Union[Version, str],
     new_version: Union[Version, str],
diff --git a/commitizen/changelog.py b/commitizen/changelog.py
index 74cee3260a..e42824dc4b 100644
--- a/commitizen/changelog.py
+++ b/commitizen/changelog.py
@@ -37,9 +37,9 @@
 from packaging.version import InvalidVersion, Version
 
 from commitizen import defaults
-from commitizen.bump import normalize_tag
 from commitizen.exceptions import InvalidConfigurationError, NoCommitsFoundError
 from commitizen.git import GitCommit, GitTag
+from commitizen.tags import tag_from_version
 
 if sys.version_info >= (3, 8):
     from commitizen.version_types import VersionProtocol
@@ -341,13 +341,13 @@ def get_oldest_and_newest_rev(
     except ValueError:
         newest = version
 
-    newest_tag = normalize_tag(
+    newest_tag = tag_from_version(
         newest, tag_format=tag_format, version_type_cls=version_type_cls
     )
 
     oldest_tag = None
     if oldest:
-        oldest_tag = normalize_tag(
+        oldest_tag = tag_from_version(
             oldest, tag_format=tag_format, version_type_cls=version_type_cls
         )
 
diff --git a/commitizen/cli.py b/commitizen/cli.py
index ed89b5675a..355a37b1ce 100644
--- a/commitizen/cli.py
+++ b/commitizen/cli.py
@@ -1,8 +1,8 @@
 import argparse
 import logging
 import sys
-from pathlib import Path
 from functools import partial
+from pathlib import Path
 from types import TracebackType
 from typing import List
 
@@ -274,6 +274,13 @@
                             "If not set, it will include prereleases in the changelog"
                         ),
                     },
+                    {
+                        "name": "--tag-regex",
+                        "help": (
+                            "regex match for tags represented "
+                            "within the changelog. default: '.*'"
+                        ),
+                    },
                 ],
             },
             {
diff --git a/commitizen/commands/bump.py b/commitizen/commands/bump.py
index 53e194bc6f..f33734ca76 100644
--- a/commitizen/commands/bump.py
+++ b/commitizen/commands/bump.py
@@ -22,6 +22,7 @@
     NoVersionSpecifiedError,
 )
 from commitizen.providers import get_provider
+from commitizen.tags import tag_from_version
 
 logger = getLogger("commitizen")
 
@@ -161,7 +162,7 @@ def __call__(self):  # noqa: C901
                     f"--major-version-zero is meaningless for current version {current_version}"
                 )
 
-        current_tag_version: str = bump.normalize_tag(
+        current_tag_version: str = tag_from_version(
             current_version,
             tag_format=tag_format,
             version_type_cls=self.version_type,
@@ -223,7 +224,7 @@ def __call__(self):  # noqa: C901
                 version_type_cls=self.version_type,
             )
 
-        new_tag_version = bump.normalize_tag(
+        new_tag_version = tag_from_version(
             new_version,
             tag_format=tag_format,
             version_type_cls=self.version_type,
diff --git a/commitizen/commands/changelog.py b/commitizen/commands/changelog.py
index 69d223ed49..24685face7 100644
--- a/commitizen/commands/changelog.py
+++ b/commitizen/commands/changelog.py
@@ -1,12 +1,14 @@
 import os.path
+import re
 from difflib import SequenceMatcher
 from operator import itemgetter
 from typing import Callable, Dict, List, Optional
 
 from packaging.version import parse
 
-from commitizen import bump, changelog, defaults, factory, git, out, version_types
+from commitizen import changelog, defaults, factory, git, out, version_types
 from commitizen.config import BaseConfig
+from commitizen.defaults import DEFAULT_SETTINGS
 from commitizen.exceptions import (
     DryRunExit,
     NoCommitsFoundError,
@@ -16,6 +18,7 @@
     NotAllowed,
 )
 from commitizen.git import GitTag, smart_open
+from commitizen.tags import make_tag_pattern, tag_from_version
 
 
 class Changelog:
@@ -55,8 +58,8 @@ def __init__(self, config: BaseConfig, args):
             or defaults.change_type_order
         )
         self.rev_range = args.get("rev_range")
-        self.tag_format = args.get("tag_format") or self.config.settings.get(
-            "tag_format"
+        self.tag_format: str = args.get("tag_format") or self.config.settings.get(
+            "tag_format", DEFAULT_SETTINGS["tag_format"]
         )
         self.merge_prerelease = args.get(
             "merge_prerelease"
@@ -65,6 +68,11 @@ def __init__(self, config: BaseConfig, args):
         version_type = self.config.settings.get("version_type")
         self.version_type = version_type and version_types.VERSION_TYPES[version_type]
 
+        tag_regex = args.get("tag_regex") or self.config.settings.get("tag_regex")
+        if not tag_regex:
+            tag_regex = make_tag_pattern(self.tag_format)
+        self.tag_pattern = re.compile(str(tag_regex), re.VERBOSE | re.IGNORECASE)
+
     def _find_incremental_rev(self, latest_version: str, tags: List[GitTag]) -> str:
         """Try to find the 'start_rev'.
 
@@ -138,7 +146,7 @@ def __call__(self):
         # Don't continue if no `file_name` specified.
         assert self.file_name
 
-        tags = git.get_tags()
+        tags = git.get_tags(pattern=self.tag_pattern)
         if not tags:
             tags = []
 
@@ -148,7 +156,7 @@ def __call__(self):
             changelog_meta = changelog.get_metadata(self.file_name)
             latest_version = changelog_meta.get("latest_version")
             if latest_version:
-                latest_tag_version: str = bump.normalize_tag(
+                latest_tag_version: str = tag_from_version(
                     latest_version,
                     tag_format=self.tag_format,
                     version_type_cls=self.version_type,
diff --git a/commitizen/commands/init.py b/commitizen/commands/init.py
index 08fdadef77..c79bc331be 100644
--- a/commitizen/commands/init.py
+++ b/commitizen/commands/init.py
@@ -10,7 +10,7 @@
 from commitizen.__version__ import __version__
 from commitizen.config import BaseConfig, JsonConfig, TomlConfig, YAMLConfig
 from commitizen.cz import registry
-from commitizen.defaults import config_files
+from commitizen.defaults import DEFAULT_SETTINGS, config_files
 from commitizen.exceptions import InitFailedError, NoAnswersError
 from commitizen.git import get_latest_tag_name, get_tag_names, smart_open
 from commitizen.version_types import VERSION_TYPES
@@ -203,14 +203,15 @@ def _ask_tag_format(self, latest_tag) -> str:
                 f'Is "{tag_format}" the correct tag format?', style=self.cz.style
             ).unsafe_ask()
 
+        default_format = DEFAULT_SETTINGS["tag_format"]
         if not is_correct_format:
             tag_format = questionary.text(
-                'Please enter the correct version format: (default: "$version")',
+                f'Please enter the correct version format: (default: "{default_format}")',
                 style=self.cz.style,
             ).unsafe_ask()
 
             if not tag_format:
-                tag_format = "$version"
+                tag_format = default_format
         return tag_format
 
     def _ask_version_provider(self) -> str:
diff --git a/commitizen/defaults.py b/commitizen/defaults.py
index a7c285edba..91035ae995 100644
--- a/commitizen/defaults.py
+++ b/commitizen/defaults.py
@@ -35,7 +35,7 @@ class Settings(TypedDict, total=False):
     version: Optional[str]
     version_files: List[str]
     version_provider: Optional[str]
-    tag_format: Optional[str]
+    tag_format: str
     bump_message: Optional[str]
     allow_abort: bool
     changelog_file: str
@@ -68,7 +68,7 @@ class Settings(TypedDict, total=False):
     "version": None,
     "version_files": [],
     "version_provider": "commitizen",
-    "tag_format": None,  # example v$version
+    "tag_format": "$version",  # example v$version
     "bump_message": None,  # bumped v$current_version to $new_version
     "allow_abort": False,
     "changelog_file": "CHANGELOG.md",
diff --git a/commitizen/git.py b/commitizen/git.py
index 2c2cb5b368..3eb8b33a87 100644
--- a/commitizen/git.py
+++ b/commitizen/git.py
@@ -1,4 +1,5 @@
 import os
+import re
 from enum import Enum
 from os import linesep
 from pathlib import Path
@@ -140,7 +141,7 @@ def get_filenames_in_commit(git_reference: str = ""):
         raise GitCommandError(c.err)
 
 
-def get_tags(dateformat: str = "%Y-%m-%d") -> List[GitTag]:
+def get_tags(dateformat: str = "%Y-%m-%d", *, pattern: re.Pattern) -> List[GitTag]:
     inner_delimiter = "---inner_delimiter---"
     formatter = (
         f'"%(refname:lstrip=2){inner_delimiter}'
@@ -163,7 +164,9 @@ def get_tags(dateformat: str = "%Y-%m-%d") -> List[GitTag]:
         for line in c.out.split("\n")[:-1]
     ]
 
-    return git_tags
+    filtered_git_tags = [t for t in git_tags if pattern.fullmatch(t.name)]
+
+    return filtered_git_tags
 
 
 def tag_exist(tag: str) -> bool:
diff --git a/commitizen/tags.py b/commitizen/tags.py
new file mode 100644
index 0000000000..32ac6f890b
--- /dev/null
+++ b/commitizen/tags.py
@@ -0,0 +1,65 @@
+import re
+import sys
+from string import Template
+from typing import Any, Optional, Type, Union
+
+from packaging.version import VERSION_PATTERN, Version
+
+if sys.version_info >= (3, 8):
+    from commitizen.version_types import VersionProtocol
+else:
+    # workaround mypy issue for 3.7 python
+    VersionProtocol = Any
+
+
+def tag_from_version(
+    version: Union[VersionProtocol, str],
+    tag_format: str,
+    version_type_cls: Optional[Type[VersionProtocol]] = None,
+) -> str:
+    """The tag and the software version might be different.
+
+    That's why this function exists.
+
+    Example:
+    | tag | version (PEP 0440) |
+    | --- | ------- |
+    | v0.9.0 | 0.9.0 |
+    | ver1.0.0 | 1.0.0 |
+    | ver1.0.0.a0 | 1.0.0a0 |
+    """
+    if version_type_cls is None:
+        version_type_cls = Version
+    if isinstance(version, str):
+        version = version_type_cls(version)
+
+    major, minor, patch = version.release
+    prerelease = ""
+    # version.pre is needed for mypy check
+    if version.is_prerelease and version.pre:
+        prerelease = f"{version.pre[0]}{version.pre[1]}"
+
+    t = Template(tag_format)
+    return t.safe_substitute(
+        version=version, major=major, minor=minor, patch=patch, prerelease=prerelease
+    )
+
+
+def make_tag_pattern(tag_format: str) -> str:
+    """Make regex pattern to match all tags created by tag_format."""
+    escaped_format = re.escape(tag_format)
+    escaped_format = re.sub(
+        r"\\\$(version|major|minor|patch|prerelease)", r"$\1", escaped_format
+    )
+    # pre-release part of VERSION_PATTERN
+    pre_release_pattern = r"([-_\.]?(a|b|c|rc|alpha|beta|pre|preview)([-_\.]?[0-9]+)?)?"
+    filter_regex = Template(escaped_format).safe_substitute(
+        # VERSION_PATTERN allows the v prefix, but we'd rather have users configure it
+        # explicitly.
+        version=VERSION_PATTERN.lstrip("\n v?"),
+        major="[0-9]+",
+        minor="[0-9]+",
+        patch="[0-9]+",
+        prerelease=pre_release_pattern,
+    )
+    return filter_regex
diff --git a/docs/bump.md b/docs/bump.md
index 1c96fb43aa..a573a3dd91 100644
--- a/docs/bump.md
+++ b/docs/bump.md
@@ -327,7 +327,7 @@ In your `pyproject.toml` or `.cz.toml`
 tag_format = "v$major.$minor.$patch$prerelease"
 ```
 
-The variables must be preceded by a `$` sign.
+The variables must be preceded by a `$` sign. Default is `$version`.
 
 Supported variables:
 
diff --git a/docs/changelog.md b/docs/changelog.md
index d6799e198f..24730dabc8 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -186,6 +186,28 @@ cz changelog --merge-prerelease
 changelog_merge_prerelease = true
 ```
 
+### `tag-regex`
+
+This value can be set in the `toml` file with the key `tag_regex` under `tools.commitizen`.
+
+`tag_regex` is the regex pattern that selects tags to include in the changelog.
+By default, the changelog will capture all git tags matching the `tag_format`, including pre-releases.
+
+Example use-cases:
+
+- Exclude pre-releases from the changelog
+- Include existing tags that do not follow `tag_format` in the changelog
+
+```bash
+cz changelog --tag-regex="[0-9]*\\.[0-9]*\\.[0-9]"
+```
+
+```toml
+[tools.commitizen]
+# ...
+tag_regex = "[0-9]*\\.[0-9]*\\.[0-9]"
+```
+
 ## Hooks
 
 Supported hook methods:
diff --git a/docs/config.md b/docs/config.md
index f93aca60e7..07f36ebc18 100644
--- a/docs/config.md
+++ b/docs/config.md
@@ -38,10 +38,18 @@ Version provider used to read and write version [Read more](#version-providers)
 
 Type: `str`
 
-Default: `None`
+Default: `$version`
 
 Format for the git tag, useful for old projects, that use a convention like `"v1.2.1"`. [Read more][tag_format]
 
+### `tag_regex`
+
+Type: `str`
+
+Default: Based on `tag_format`
+
+Tags must match this to be included in the changelog (e.g. `"([0-9.])*"` to exclude pre-releases). [Read more][tag_regex]
+
 ### `update_changelog_on_bump`
 
 Type: `bool`
@@ -339,6 +347,7 @@ setup(
 
 [version_files]: bump.md#version_files
 [tag_format]: bump.md#tag_format
+[tag_regex]: changelog.md#tag_regex
 [bump_message]: bump.md#bump_message
 [major-version-zero]: bump.md#-major-version-zero
 [prerelease-offset]: bump.md#-prerelease_offset
diff --git a/poetry.toml b/poetry.toml
new file mode 100644
index 0000000000..ab1033bd37
--- /dev/null
+++ b/poetry.toml
@@ -0,0 +1,2 @@
+[virtualenvs]
+in-project = true
diff --git a/tests/commands/conftest.py b/tests/commands/conftest.py
index 91931849b2..c62af685fa 100644
--- a/tests/commands/conftest.py
+++ b/tests/commands/conftest.py
@@ -44,10 +44,5 @@ def config_customize():
 
 
 @pytest.fixture()
-def changelog_path() -> str:
+def changelog_path(tmp_commitizen_project) -> str:
     return os.path.join(os.getcwd(), "CHANGELOG.md")
-
-
-@pytest.fixture()
-def config_path() -> str:
-    return os.path.join(os.getcwd(), "pyproject.toml")
diff --git a/tests/commands/test_bump_command.py b/tests/commands/test_bump_command.py
index f11a485db4..6468bdfe8e 100644
--- a/tests/commands/test_bump_command.py
+++ b/tests/commands/test_bump_command.py
@@ -533,6 +533,24 @@ def test_bump_with_changelog_config(mocker: MockFixture, changelog_path, config_
     assert "0.2.0" in out
 
 
+@pytest.mark.usefixtures("tmp_commitizen_project")
+def test_bump_with_changelog_excludes_custom_tags(mocker: MockFixture, changelog_path):
+    create_file_and_commit("feat(user): new file")
+    git.tag("custom-tag")
+    create_file_and_commit("feat(user): Another new file")
+    testargs = ["cz", "bump", "--yes", "--changelog"]
+    mocker.patch.object(sys, "argv", testargs)
+    cli.main()
+    tag_exists = git.tag_exist("0.2.0")
+    assert tag_exists is True
+
+    with open(changelog_path, "r") as f:
+        out = f.read()
+    assert out.startswith("#")
+    assert "## 0.2.0" in out
+    assert "custom-tag" not in out
+
+
 @pytest.mark.usefixtures("tmp_commitizen_project")
 def test_prevent_prerelease_when_no_increment_detected(mocker: MockFixture, capsys):
     create_file_and_commit("feat: new file")
diff --git a/tests/commands/test_changelog_command.py b/tests/commands/test_changelog_command.py
index 30033d9c7d..6c215138db 100644
--- a/tests/commands/test_changelog_command.py
+++ b/tests/commands/test_changelog_command.py
@@ -1,6 +1,8 @@
 import itertools
 import sys
 from datetime import datetime
+from typing import List
+from unittest.mock import patch
 
 import pytest
 from pytest_mock import MockFixture
@@ -1271,3 +1273,53 @@ def test_changelog_prerelease_rev_with_use_version_type_semver(
     out, _ = capsys.readouterr()
 
     file_regression.check(out, extension=".second-prerelease.md")
+
+
+@pytest.mark.parametrize(
+    "config_file, expected_versions",
+    [
+        pytest.param("", ["Unreleased"], id="v-prefix-not-configured"),
+        pytest.param(
+            'tag_format = "v$version"',
+            ["v1.1.0", "v1.1.0-beta", "v1.0.0"],
+            id="v-prefix-configured-as-tag-format",
+        ),
+        pytest.param(
+            'tag_format = "v$version"\n' + 'tag_regex = ".*"',
+            ["v1.1.0", "custom-tag", "v1.1.0-beta", "v1.0.0"],
+            id="tag-regex-matches-all-tags",
+        ),
+        pytest.param(
+            'tag_format = "v$version"\n' + r'tag_regex = "v[0-9\\.]*"',
+            ["v1.1.0", "v1.0.0"],
+            id="tag-regex-excludes-pre-releases",
+        ),
+    ],
+)
+def test_changelog_tag_regex(
+    config_path, changelog_path, config_file: str, expected_versions: List[str]
+):
+    with open(config_path, "a") as f:
+        f.write(config_file)
+
+    # Create 4 tags with one valid feature each
+    create_file_and_commit("feat: initial")
+    git.tag("v1.0.0")
+    create_file_and_commit("feat: add 1")
+    git.tag("v1.1.0-beta")
+    create_file_and_commit("feat: add 2")
+    git.tag("custom-tag")
+    create_file_and_commit("feat: add 3")
+    git.tag("v1.1.0")
+
+    # call CLI
+    with patch.object(sys, "argv", ["cz", "changelog"]):
+        cli.main()
+
+    # open CLI output
+    with open(changelog_path, "r") as f:
+        out = f.read()
+
+    headings = [line for line in out.splitlines() if line.startswith("## ")]
+    changelog_versions = [heading[3:].split()[0] for heading in headings]
+    assert changelog_versions == expected_versions
diff --git a/tests/conftest.py b/tests/conftest.py
index e2177472db..d070140133 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -8,8 +8,8 @@
 
 from commitizen import cmd, defaults
 from commitizen.config import BaseConfig
-from commitizen.cz.base import BaseCommitizen
 from commitizen.cz import registry
+from commitizen.cz.base import BaseCommitizen
 from tests.utils import create_file_and_commit
 
 SIGNER = "GitHub Action"
@@ -122,7 +122,7 @@ def config():
 
 
 @pytest.fixture()
-def config_path() -> str:
+def config_path(tmp_commitizen_project) -> str:
     return os.path.join(os.getcwd(), "pyproject.toml")
 
 
diff --git a/tests/test_bump_normalize_tag.py b/tests/test_bump_normalize_tag.py
deleted file mode 100644
index 3bc9828a2f..0000000000
--- a/tests/test_bump_normalize_tag.py
+++ /dev/null
@@ -1,23 +0,0 @@
-import pytest
-from packaging.version import Version
-
-from commitizen import bump
-
-conversion = [
-    (("1.2.3", "v$version"), "v1.2.3"),
-    (("1.2.3a2", "v$version"), "v1.2.3a2"),
-    (("1.2.3b2", "v$version"), "v1.2.3b2"),
-    (("1.2.3", "ver$major.$minor.$patch"), "ver1.2.3"),
-    (("1.2.3a0", "ver$major.$minor.$patch.$prerelease"), "ver1.2.3.a0"),
-    (("1.2.3rc2", "$major.$minor.$patch.$prerelease-majestic"), "1.2.3.rc2-majestic"),
-    (("1.2.3+1.0.0", "v$version"), "v1.2.3+1.0.0"),
-    (("1.2.3+1.0.0", "v$version-local"), "v1.2.3+1.0.0-local"),
-    (("1.2.3+1.0.0", "ver$major.$minor.$patch"), "ver1.2.3"),
-]
-
-
-@pytest.mark.parametrize("test_input,expected", conversion)
-def test_create_tag(test_input, expected):
-    version, format = test_input
-    new_tag = bump.normalize_tag(Version(version), format)
-    assert new_tag == expected
diff --git a/tests/test_changelog.py b/tests/test_changelog.py
index 78a2697c36..d77a8b624a 100644
--- a/tests/test_changelog.py
+++ b/tests/test_changelog.py
@@ -1,3 +1,5 @@
+from pathlib import Path
+
 import pytest
 
 from commitizen import changelog, defaults, git
@@ -495,7 +497,7 @@ def tags() -> list:
 
 @pytest.fixture
 def changelog_content() -> str:
-    changelog_path = "tests/CHANGELOG_FOR_TEST.md"
+    changelog_path = Path(__file__).parent / "CHANGELOG_FOR_TEST.md"
     with open(changelog_path, "r") as f:
         return f.read()
 
diff --git a/tests/test_conf.py b/tests/test_conf.py
index 4226096371..29597fad6a 100644
--- a/tests/test_conf.py
+++ b/tests/test_conf.py
@@ -1,5 +1,4 @@
 import json
-import os
 from pathlib import Path
 
 import pytest
@@ -41,11 +40,11 @@
 }
 
 
-_settings = {
+_read_settings = {
     "name": "cz_jira",
     "version": "1.0.0",
     "version_provider": "commitizen",
-    "tag_format": None,
+    "tag_format": "$version",
     "bump_message": None,
     "allow_abort": False,
     "version_files": ["commitizen/__version__.py", "pyproject.toml"],
@@ -67,7 +66,7 @@
     "name": "cz_jira",
     "version": "2.0.0",
     "version_provider": "commitizen",
-    "tag_format": None,
+    "tag_format": "$version",
     "bump_message": None,
     "allow_abort": False,
     "version_files": ["commitizen/__version__.py", "pyproject.toml"],
@@ -85,16 +84,6 @@
     "version_type": None,
 }
 
-_read_settings = {
-    "name": "cz_jira",
-    "version": "1.0.0",
-    "version_files": ["commitizen/__version__.py", "pyproject.toml"],
-    "style": [["pointer", "reverse"], ["question", "underline"]],
-    "changelog_file": "CHANGELOG.md",
-    "pre_bump_hooks": ["scripts/generate_documentation.sh"],
-    "post_bump_hooks": ["scripts/slack_notification.sh"],
-}
-
 
 @pytest.fixture
 def config_files_manager(request, tmpdir):
@@ -111,7 +100,7 @@ def config_files_manager(request, tmpdir):
 
 
 def test_find_git_project_root(tmpdir):
-    assert git.find_git_project_root() == Path(os.getcwd())
+    assert git.find_git_project_root() == Path(__file__).parent.parent
 
     with tmpdir.as_cwd() as _:
         assert git.find_git_project_root() is None
@@ -133,7 +122,7 @@ class TestReadCfg:
     )
     def test_load_conf(_, config_files_manager):
         cfg = config.read_cfg()
-        assert cfg.settings == _settings
+        assert cfg.settings == _read_settings
 
     def test_conf_returns_default_when_no_files(_, tmpdir):
         with tmpdir.as_cwd():
@@ -148,7 +137,7 @@ def test_load_empty_pyproject_toml_and_cz_toml_with_config(_, tmpdir):
             p.write(PYPROJECT)
 
             cfg = config.read_cfg()
-            assert cfg.settings == _settings
+            assert cfg.settings == _read_settings
 
 
 class TestTomlConfig:
diff --git a/tests/test_git.py b/tests/test_git.py
index 81089f6759..cadf89e988 100644
--- a/tests/test_git.py
+++ b/tests/test_git.py
@@ -1,5 +1,6 @@
 import inspect
 import os
+import re
 import shutil
 from typing import List, Optional
 
@@ -7,6 +8,7 @@
 from pytest_mock import MockFixture
 
 from commitizen import cmd, exceptions, git
+from commitizen.tags import make_tag_pattern
 from tests.utils import FakeCommand, create_file_and_commit
 
 
@@ -28,7 +30,7 @@ def test_get_tags(mocker: MockFixture):
     )
     mocker.patch("commitizen.cmd.run", return_value=FakeCommand(out=tag_str))
 
-    git_tags = git.get_tags()
+    git_tags = git.get_tags(pattern=re.compile(r"v[0-9\.]+"))
     latest_git_tag = git_tags[0]
     assert latest_git_tag.rev == "333"
     assert latest_git_tag.name == "v1.0.0"
@@ -37,7 +39,60 @@ def test_get_tags(mocker: MockFixture):
     mocker.patch(
         "commitizen.cmd.run", return_value=FakeCommand(out="", err="No tag available")
     )
-    assert git.get_tags() == []
+    assert git.get_tags(pattern=re.compile(r"v[0-9\.]+")) == []
+
+
+@pytest.mark.parametrize(
+    "pattern, expected_tags",
+    [
+        pytest.param(
+            make_tag_pattern(tag_format="$version"),
+            [],  # No versions with normal 1.2.3 pattern
+            id="default-tag-format",
+        ),
+        pytest.param(
+            make_tag_pattern(tag_format="$major-$minor-$patch$prerelease"),
+            ["1-0-0", "1-0-0alpha2"],
+            id="tag-format-with-hyphens",
+        ),
+        pytest.param(
+            r"[0-9]+\-[0-9]+\-[0-9]+",
+            ["1-0-0"],
+            id="tag-regex-with-hyphens-that-excludes-alpha",
+        ),
+        pytest.param(
+            make_tag_pattern(tag_format="v$version"),
+            ["v0.5.0", "v0.0.1-pre"],
+            id="tag-format-with-v-prefix",
+        ),
+        pytest.param(
+            make_tag_pattern(tag_format="custom-prefix-$version"),
+            ["custom-prefix-0.0.1"],
+            id="tag-format-with-custom-prefix",
+        ),
+        pytest.param(
+            ".*",
+            ["1-0-0", "1-0-0alpha2", "v0.5.0", "v0.0.1-pre", "custom-prefix-0.0.1"],
+            id="custom-tag-regex-to-include-all-tags",
+        ),
+    ],
+)
+def test_get_tags_filtering(
+    mocker: MockFixture, pattern: str, expected_tags: List[str]
+):
+    tag_str = (
+        "1-0-0---inner_delimiter---333---inner_delimiter---2020-01-20---inner_delimiter---\n"
+        "1-0-0alpha2---inner_delimiter---333---inner_delimiter---2020-01-20---inner_delimiter---\n"
+        "v0.5.0---inner_delimiter---222---inner_delimiter---2020-01-17---inner_delimiter---\n"
+        "v0.0.1-pre---inner_delimiter---111---inner_delimiter---2020-01-17---inner_delimiter---\n"
+        "custom-prefix-0.0.1---inner_delimiter---111---inner_delimiter---2020-01-17---inner_delimiter---\n"
+        "custom-non-release-tag"
+    )
+    mocker.patch("commitizen.cmd.run", return_value=FakeCommand(out=tag_str))
+
+    git_tags = git.get_tags(pattern=re.compile(pattern, flags=re.VERBOSE))
+    actual_name_list = [t.name for t in git_tags]
+    assert actual_name_list == expected_tags
 
 
 def test_get_tag_names(mocker: MockFixture):
diff --git a/tests/test_tags.py b/tests/test_tags.py
new file mode 100644
index 0000000000..efebe43f32
--- /dev/null
+++ b/tests/test_tags.py
@@ -0,0 +1,76 @@
+import re
+from typing import Dict
+
+import pytest
+from packaging.version import Version
+
+from commitizen.tags import make_tag_pattern, tag_from_version
+
+TAG_FORMATS: Dict[str, Dict[str, list]] = {
+    "v$version": {
+        "tags_per_version": [
+            ("1.2.3", "v1.2.3"),
+            ("1.2.3a2", "v1.2.3a2"),
+            ("1.2.3b2", "v1.2.3b2"),
+            ("1.2.3+1.0.0", "v1.2.3+1.0.0"),
+        ],
+        "invalid_tags": ["1.2.3", "unknown-tag", "v1-2-3"],
+    },
+    "ver$major-$minor-$patch$prerelease": {
+        "tags_per_version": [
+            ("1.2.3", "ver1-2-3"),
+            ("1.2.3a0", "ver1-2-3a0"),
+            ("1.2.3+1.0.0", "ver1-2-3"),
+        ],
+        "invalid_tags": ["1.2.3", "unknown-tag", "v1-2-3", "v1.0.0", "ver1.0.0+123"],
+    },
+    "ver$major.$minor.$patch$prerelease-majestic": {
+        "tags_per_version": [
+            ("1.2.3rc2", "ver1.2.3rc2-majestic"),
+        ],
+        "invalid_tags": ["1.2.3", "unknown-tag", "v1-2-3", "v1.0.0", "ver1.0.0"],
+    },
+    "v$version-local": {
+        "tags_per_version": [("1.2.3+1.0.0", "v1.2.3+1.0.0-local")],
+        "invalid_tags": ["1.2.3", "unknown-tag", "v1-2-3", "v1.0.0", "ver1.0.0"],
+    },
+}
+
+
+@pytest.mark.parametrize(
+    "tag_format, version, expected_tag_name",
+    [
+        (tag_format, version, expected_tag_name)
+        for tag_format, format_dict in TAG_FORMATS.items()
+        for version, expected_tag_name in format_dict["tags_per_version"]
+    ],
+)
+def test_tag_from_version(tag_format, version, expected_tag_name):
+    new_tag = tag_from_version(Version(version), tag_format)
+    assert new_tag == expected_tag_name
+
+
+@pytest.mark.parametrize(
+    "tag_format,tag_name",
+    [
+        (tag_format, tag_name)
+        for tag_format, format_dict in TAG_FORMATS.items()
+        for _, tag_name in format_dict["tags_per_version"]
+    ],
+)
+def test_make_tag_pattern_matches(tag_format: str, tag_name: str):
+    pattern = re.compile(make_tag_pattern(tag_format=tag_format), flags=re.VERBOSE)
+    assert pattern.fullmatch(tag_name)
+
+
+@pytest.mark.parametrize(
+    "tag_format,invalid_tag_name",
+    [
+        (tag_format, invalid_tag_name)
+        for tag_format, format_dict in TAG_FORMATS.items()
+        for invalid_tag_name in format_dict["invalid_tags"]
+    ],
+)
+def test_make_tag_pattern_does_not_match(tag_format: str, invalid_tag_name: str):
+    pattern = re.compile(make_tag_pattern(tag_format=tag_format), flags=re.VERBOSE)
+    assert pattern.fullmatch(invalid_tag_name) is None