Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(tags): adds legacy_tag_formats and ignored_tag_formats settings #1297

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 1 addition & 29 deletions commitizen/bump.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from commitizen.defaults import MAJOR, MINOR, PATCH, bump_message, encoding
from commitizen.exceptions import CurrentVersionNotFoundError
from commitizen.git import GitCommit, smart_open
from commitizen.version_schemes import DEFAULT_SCHEME, Increment, Version, VersionScheme
from commitizen.version_schemes import Increment, Version

VERSION_TYPES = [None, PATCH, MINOR, MAJOR]

Expand Down Expand Up @@ -131,34 +131,6 @@ def _version_to_regex(version: str) -> str:
return version.replace(".", r"\.").replace("+", r"\+")


def normalize_tag(
version: Version | str,
tag_format: str,
scheme: VersionScheme | None = 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 |
"""
scheme = scheme or DEFAULT_SCHEME
version = scheme(version) if isinstance(version, str) else version

major, minor, patch = version.release
prerelease = version.prerelease or ""

t = Template(tag_format)
return t.safe_substitute(
version=version, major=major, minor=minor, patch=patch, prerelease=prerelease
)


def create_commit_message(
current_version: Version | str,
new_version: Version | str,
Expand Down
84 changes: 26 additions & 58 deletions commitizen/changelog.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,21 +42,13 @@
Template,
)

from commitizen import out
from commitizen.bump import normalize_tag
from commitizen.cz.base import ChangelogReleaseHook
from commitizen.defaults import get_tag_regexes
from commitizen.exceptions import InvalidConfigurationError, NoCommitsFoundError
from commitizen.git import GitCommit, GitTag
from commitizen.version_schemes import (
DEFAULT_SCHEME,
BaseVersion,
InvalidVersion,
)
from commitizen.tags import TagRules

if TYPE_CHECKING:
from commitizen.cz.base import MessageBuilderHook
from commitizen.version_schemes import VersionScheme


@dataclass
Expand All @@ -69,50 +61,19 @@ class Metadata:
unreleased_end: int | None = None
latest_version: str | None = None
latest_version_position: int | None = None
latest_version_tag: str | None = None

def __post_init__(self):
if self.latest_version and not self.latest_version_tag:
# Test syntactic sugar
# latest version tag is optional if same as latest version
self.latest_version_tag = self.latest_version


def get_commit_tag(commit: GitCommit, tags: list[GitTag]) -> GitTag | None:
return next((tag for tag in tags if tag.rev == commit.rev), None)


def tag_included_in_changelog(
tag: GitTag,
used_tags: list,
merge_prerelease: bool,
scheme: VersionScheme = DEFAULT_SCHEME,
) -> bool:
if tag in used_tags:
return False

try:
version = scheme(tag.name)
except InvalidVersion:
return False

if merge_prerelease and version.is_prerelease:
return False

return True


def get_version_tags(
scheme: type[BaseVersion], tags: list[GitTag], tag_format: str
) -> list[GitTag]:
valid_tags: list[GitTag] = []
TAG_FORMAT_REGEXS = get_tag_regexes(scheme.parser.pattern)
tag_format_regex = tag_format
for pattern, regex in TAG_FORMAT_REGEXS.items():
tag_format_regex = tag_format_regex.replace(pattern, regex)
for tag in tags:
if re.match(tag_format_regex, tag.name):
valid_tags.append(tag)
else:
out.warn(
f"InvalidVersion {tag.name} doesn't match configured tag format {tag_format}"
)
return valid_tags


def generate_tree_from_commits(
commits: list[GitCommit],
tags: list[GitTag],
Expand All @@ -122,13 +83,13 @@ def generate_tree_from_commits(
change_type_map: dict[str, str] | None = None,
changelog_message_builder_hook: MessageBuilderHook | None = None,
changelog_release_hook: ChangelogReleaseHook | None = None,
merge_prerelease: bool = False,
scheme: VersionScheme = DEFAULT_SCHEME,
rules: TagRules | None = None,
) -> Iterable[dict]:
pat = re.compile(changelog_pattern)
map_pat = re.compile(commit_parser, re.MULTILINE)
body_map_pat = re.compile(commit_parser, re.MULTILINE | re.DOTALL)
current_tag: GitTag | None = None
rules = rules or TagRules()

# Check if the latest commit is not tagged
if commits:
Expand All @@ -148,8 +109,10 @@ def generate_tree_from_commits(
for commit in commits:
commit_tag = get_commit_tag(commit, tags)

if commit_tag is not None and tag_included_in_changelog(
commit_tag, used_tags, merge_prerelease, scheme=scheme
if (
commit_tag
and commit_tag not in used_tags
and rules.include_in_changelog(commit_tag)
):
used_tags.append(commit_tag)
release = {
Expand Down Expand Up @@ -343,8 +306,7 @@ def get_smart_tag_range(
def get_oldest_and_newest_rev(
tags: list[GitTag],
version: str,
tag_format: str,
scheme: VersionScheme | None = None,
rules: TagRules,
) -> tuple[str | None, str | None]:
"""Find the tags for the given version.

Expand All @@ -358,22 +320,28 @@ def get_oldest_and_newest_rev(
oldest, newest = version.split("..")
except ValueError:
newest = version
newest_tag = normalize_tag(newest, tag_format=tag_format, scheme=scheme)
if not (newest_tag := rules.find_tag_for(tags, newest)):
raise NoCommitsFoundError("Could not find a valid revision range.")

oldest_tag = None
oldest_tag_name = None
if oldest:
oldest_tag = normalize_tag(oldest, tag_format=tag_format, scheme=scheme)
if not (oldest_tag := rules.find_tag_for(tags, oldest)):
raise NoCommitsFoundError("Could not find a valid revision range.")
oldest_tag_name = oldest_tag.name

tags_range = get_smart_tag_range(tags, newest=newest_tag, oldest=oldest_tag)
tags_range = get_smart_tag_range(
tags, newest=newest_tag.name, oldest=oldest_tag_name
)
if not tags_range:
raise NoCommitsFoundError("Could not find a valid revision range.")

oldest_rev: str | None = tags_range[-1].name
newest_rev = newest_tag
newest_rev = newest_tag.name

# check if it's the first tag created
# and it's also being requested as part of the range
if oldest_rev == tags[-1].name and oldest_rev == oldest_tag:
if oldest_rev == tags[-1].name and oldest_rev == oldest_tag_name:
return None, newest_rev

# when they are the same, and it's also the
Expand Down
19 changes: 2 additions & 17 deletions commitizen/changelog_formats/asciidoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,12 @@ class AsciiDoc(BaseFormat):

RE_TITLE = re.compile(r"^(?P<level>=+) (?P<title>.*)$")

def parse_version_from_title(self, line: str) -> str | None:
def parse_version_from_title(self, line: str) -> tuple[str, str] | None:
m = self.RE_TITLE.match(line)
if not m:
return None
# Capture last match as AsciiDoc use postfixed URL labels
matches = list(re.finditer(self.version_parser, m.group("title")))
if not matches:
return None
if "version" in matches[-1].groupdict():
return matches[-1].group("version")
partial_matches = matches[-1].groupdict()
try:
partial_version = f"{partial_matches['major']}.{partial_matches['minor']}.{partial_matches['patch']}"
except KeyError:
return None

if partial_matches.get("prerelease"):
partial_version = f"{partial_version}-{partial_matches['prerelease']}"
if partial_matches.get("devrelease"):
partial_version = f"{partial_version}{partial_matches['devrelease']}"
return partial_version
return self.tag_rules.search_version(m.group("title"), last=True)

def parse_title_level(self, line: str) -> int | None:
m = self.RE_TITLE.match(line)
Expand Down
25 changes: 10 additions & 15 deletions commitizen/changelog_formats/base.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
from __future__ import annotations

import os
import re
from abc import ABCMeta
from re import Pattern
from typing import IO, Any, ClassVar

from commitizen.changelog import Metadata
from commitizen.changelog import Metadata, TagRules
from commitizen.config.base_config import BaseConfig
from commitizen.defaults import get_tag_regexes
from commitizen.version_schemes import get_version_scheme

from . import ChangelogFormat
Expand All @@ -28,15 +25,12 @@ def __init__(self, config: BaseConfig):
self.config = config
self.encoding = self.config.settings["encoding"]
self.tag_format = self.config.settings["tag_format"]

@property
def version_parser(self) -> Pattern:
tag_regex: str = self.tag_format
version_regex = get_version_scheme(self.config).parser.pattern
TAG_FORMAT_REGEXS = get_tag_regexes(version_regex)
for pattern, regex in TAG_FORMAT_REGEXS.items():
tag_regex = tag_regex.replace(pattern, regex)
return re.compile(tag_regex)
self.tag_rules = TagRules(
scheme=get_version_scheme(self.config.settings),
tag_format=self.tag_format,
legacy_tag_formats=self.config.settings["legacy_tag_formats"],
ignored_tag_formats=self.config.settings["ignored_tag_formats"],
)

def get_metadata(self, filepath: str) -> Metadata:
if not os.path.isfile(filepath):
Expand Down Expand Up @@ -65,15 +59,16 @@ def get_metadata_from_file(self, file: IO[Any]) -> Metadata:
# Try to find the latest release done
version = self.parse_version_from_title(line)
if version:
meta.latest_version = version
meta.latest_version = version[0]
meta.latest_version_tag = version[1]
meta.latest_version_position = index
break # there's no need for more info
if meta.unreleased_start is not None and meta.unreleased_end is None:
meta.unreleased_end = index

return meta

def parse_version_from_title(self, line: str) -> str | None:
def parse_version_from_title(self, line: str) -> tuple[str, str] | None:
"""
Extract the version from a title line if any
"""
Expand Down
21 changes: 2 additions & 19 deletions commitizen/changelog_formats/markdown.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,11 @@ class Markdown(BaseFormat):

RE_TITLE = re.compile(r"^(?P<level>#+) (?P<title>.*)$")

def parse_version_from_title(self, line: str) -> str | None:
def parse_version_from_title(self, line: str) -> tuple[str, str] | None:
m = self.RE_TITLE.match(line)
if not m:
return None
m = re.search(self.version_parser, m.group("title"))
if not m:
return None
if "version" in m.groupdict():
return m.group("version")
matches = m.groupdict()
try:
partial_version = (
f"{matches['major']}.{matches['minor']}.{matches['patch']}"
)
except KeyError:
return None

if matches.get("prerelease"):
partial_version = f"{partial_version}-{matches['prerelease']}"
if matches.get("devrelease"):
partial_version = f"{partial_version}{matches['devrelease']}"
return partial_version
return self.tag_rules.search_version(m.group("title"))

def parse_title_level(self, line: str) -> int | None:
m = self.RE_TITLE.match(line)
Expand Down
31 changes: 5 additions & 26 deletions commitizen/changelog_formats/restructuredtext.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from __future__ import annotations

import re
import sys
from itertools import zip_longest
from typing import IO, TYPE_CHECKING, Any, Union
Expand Down Expand Up @@ -64,31 +63,11 @@ def get_metadata_from_file(self, file: IO[Any]) -> Metadata:
elif unreleased_title_kind and unreleased_title_kind == kind:
meta.unreleased_end = index
# Try to find the latest release done
m = re.search(self.version_parser, title)
if m:
matches = m.groupdict()
if "version" in matches:
version = m.group("version")
meta.latest_version = version
meta.latest_version_position = index
break # there's no need for more info
try:
partial_version = (
f"{matches['major']}.{matches['minor']}.{matches['patch']}"
)
if matches.get("prerelease"):
partial_version = (
f"{partial_version}-{matches['prerelease']}"
)
if matches.get("devrelease"):
partial_version = (
f"{partial_version}{matches['devrelease']}"
)
meta.latest_version = partial_version
meta.latest_version_position = index
break
except KeyError:
pass
if version := self.tag_rules.search_version(title):
meta.latest_version = version[0]
meta.latest_version_tag = version[1]
meta.latest_version_position = index
break
if meta.unreleased_start is not None and meta.unreleased_end is None:
meta.unreleased_end = (
meta.latest_version_position if meta.latest_version else index + 1
Expand Down
24 changes: 2 additions & 22 deletions commitizen/changelog_formats/textile.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,30 +10,10 @@ class Textile(BaseFormat):

RE_TITLE = re.compile(r"^h(?P<level>\d)\. (?P<title>.*)$")

def parse_version_from_title(self, line: str) -> str | None:
def parse_version_from_title(self, line: str) -> tuple[str, str] | None:
if not self.RE_TITLE.match(line):
return None
m = re.search(self.version_parser, line)
if not m:
return None
if "version" in m.groupdict():
return m.group("version")
matches = m.groupdict()
if not all(
[
version_segment in matches
for version_segment in ("major", "minor", "patch")
]
):
return None

partial_version = f"{matches['major']}.{matches['minor']}.{matches['patch']}"

if matches.get("prerelease"):
partial_version = f"{partial_version}-{matches['prerelease']}"
if matches.get("devrelease"):
partial_version = f"{partial_version}{matches['devrelease']}"
return partial_version
return self.tag_rules.search_version(line)

def parse_title_level(self, line: str) -> int | None:
m = self.RE_TITLE.match(line)
Expand Down
Loading
Loading