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