Skip to content
Merged
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
47 changes: 38 additions & 9 deletions src/semantic_release/cli/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,17 @@

import logging
import os
import re
from collections.abc import Mapping
from dataclasses import dataclass, is_dataclass
from enum import Enum
from functools import reduce
from pathlib import Path
from re import (
Pattern,
compile as regexp,
error as RegExpError, # noqa: N812
escape as regex_escape,
)
from typing import Any, ClassVar, Dict, List, Literal, Optional, Tuple, Type, Union

from git import Actor, InvalidGitRepositoryError
Expand Down Expand Up @@ -157,6 +163,20 @@ class ChangelogConfig(BaseModel):
insertion_flag: str = ""
template_dir: str = "templates"

@field_validator("exclude_commit_patterns", mode="after")
@classmethod
def validate_match(cls, patterns: Tuple[str, ...]) -> Tuple[str, ...]:
curr_index = 0
try:
for i, pattern in enumerate(patterns):
curr_index = i
regexp(pattern)
except RegExpError as err:
raise ValueError(
f"exclude_commit_patterns[{curr_index}]: Invalid regular expression"
) from err
return patterns

@field_validator("changelog_file", mode="after")
@classmethod
def changelog_file_deprecation_warning(cls, val: str) -> str:
Expand Down Expand Up @@ -228,8 +248,8 @@ def validate_match(cls, match: str) -> str:
return ".*"

try:
re.compile(match)
except re.error as err:
regexp(match)
except RegExpError as err:
raise ValueError(f"Invalid regex {match!r}") from err
return match

Expand Down Expand Up @@ -513,7 +533,7 @@ class RuntimeContext:
assets: List[str]
commit_author: Actor
commit_message: str
changelog_excluded_commit_patterns: Tuple[re.Pattern[str], ...]
changelog_excluded_commit_patterns: Tuple[Pattern[str], ...]
version_declarations: Tuple[VersionDeclarationABC, ...]
hvcs_client: hvcs.HvcsBase
changelog_insertion_flag: str
Expand Down Expand Up @@ -545,7 +565,7 @@ def select_branch_options(
choices: Dict[str, BranchConfig], active_branch: str
) -> BranchConfig:
for group, options in choices.items():
if re.match(options.match, active_branch):
if regexp(options.match).match(active_branch):
log.info(
"Using group %r options, as %r matches %r",
group,
Expand Down Expand Up @@ -639,12 +659,21 @@ def from_raw_config( # noqa: C901

# We always exclude PSR's own release commits from the Changelog
# when parsing commits
_psr_release_commit_re = re.compile(
raw.commit_message.replace(r"{version}", r"(?P<version>.*)")
psr_release_commit_regex = regexp(
reduce(
lambda regex_str, pattern: str(regex_str).replace(*pattern),
(
# replace the version holder with a regex pattern to match various versions
(regex_escape("{version}"), r"(?P<version>\d+\.\d+\.\d+\S*)"),
# TODO: add any other placeholders here
),
# We use re.escape to ensure that the commit message is treated as a literal
regex_escape(raw.commit_message),
)
)
changelog_excluded_commit_patterns = (
_psr_release_commit_re,
*(re.compile(pattern) for pattern in raw.changelog.exclude_commit_patterns),
psr_release_commit_regex,
*(regexp(pattern) for pattern in raw.changelog.exclude_commit_patterns),
)

_commit_author_str = cls.resolve_from_env(raw.commit_author) or ""
Expand Down
75 changes: 0 additions & 75 deletions tests/e2e/cmd_version/bump_version/conftest.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,17 @@
from __future__ import annotations

import os
import shutil
from re import IGNORECASE, compile as regexp
from typing import TYPE_CHECKING

import pytest
from git import Repo

if TYPE_CHECKING:
from pathlib import Path
from re import Pattern
from typing import Protocol

from tests.fixtures.git_repo import BuildRepoFromDefinitionFn, RepoActionConfigure

class GetSanitizedMdChangelogContentFn(Protocol):
def __call__(self, repo_dir: Path) -> str: ...

class GetSanitizedRstChangelogContentFn(Protocol):
def __call__(self, repo_dir: Path) -> str: ...

class InitMirrorRepo4RebuildFn(Protocol):
def __call__(
self,
Expand Down Expand Up @@ -67,69 +58,3 @@ def _init_mirror_repo_for_rebuild(
return mirror_repo_dir

return _init_mirror_repo_for_rebuild


@pytest.fixture(scope="session")
def long_hash_pattern() -> Pattern:
return regexp(r"\b([0-9a-f]{40})\b", IGNORECASE)


@pytest.fixture(scope="session")
def short_hash_pattern() -> Pattern:
return regexp(r"\b([0-9a-f]{7})\b", IGNORECASE)


@pytest.fixture(scope="session")
def get_sanitized_rst_changelog_content(
changelog_rst_file: Path,
default_rst_changelog_insertion_flag: str,
long_hash_pattern: Pattern,
short_hash_pattern: Pattern,
) -> GetSanitizedRstChangelogContentFn:
rst_short_hash_link_pattern = regexp(r"(_[0-9a-f]{7})\b", IGNORECASE)

def _get_sanitized_rst_changelog_content(repo_dir: Path) -> str:
# TODO: v10 change -- default turns to update so this is not needed
# Because we are in init mode, the insertion flag is not present in the changelog
# we must take it out manually because our repo generation fixture includes it automatically
with (repo_dir / changelog_rst_file).open(newline=os.linesep) as rfd:
# use os.linesep here because the insertion flag is os-specific
# but convert the content to universal newlines for comparison
changelog_content = (
rfd.read()
.replace(f"{default_rst_changelog_insertion_flag}{os.linesep}", "")
.replace("\r", "")
)

changelog_content = long_hash_pattern.sub("0" * 40, changelog_content)
changelog_content = short_hash_pattern.sub("0" * 7, changelog_content)
return rst_short_hash_link_pattern.sub(f'_{"0" * 7}', changelog_content)

return _get_sanitized_rst_changelog_content


@pytest.fixture(scope="session")
def get_sanitized_md_changelog_content(
changelog_md_file: Path,
default_md_changelog_insertion_flag: str,
long_hash_pattern: Pattern,
short_hash_pattern: Pattern,
) -> GetSanitizedMdChangelogContentFn:
def _get_sanitized_md_changelog_content(repo_dir: Path) -> str:
# TODO: v10 change -- default turns to update so this is not needed
# Because we are in init mode, the insertion flag is not present in the changelog
# we must take it out manually because our repo generation fixture includes it automatically
with (repo_dir / changelog_md_file).open(newline=os.linesep) as rfd:
# use os.linesep here because the insertion flag is os-specific
# but convert the content to universal newlines for comparison
changelog_content = (
rfd.read()
.replace(f"{default_md_changelog_insertion_flag}{os.linesep}", "")
.replace("\r", "")
)

changelog_content = long_hash_pattern.sub("0" * 40, changelog_content)

return short_hash_pattern.sub("0" * 7, changelog_content)

return _get_sanitized_md_changelog_content
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,8 @@
from click.testing import CliRunner
from requests_mock import Mocker

from tests.e2e.cmd_version.bump_version.conftest import (
GetSanitizedMdChangelogContentFn,
GetSanitizedRstChangelogContentFn,
InitMirrorRepo4RebuildFn,
)
from tests.e2e.cmd_version.bump_version.conftest import InitMirrorRepo4RebuildFn
from tests.e2e.conftest import GetSanitizedChangelogContentFn
from tests.fixtures.example_project import ExProjectDir
from tests.fixtures.git_repo import (
BuildRepoFromDefinitionFn,
Expand Down Expand Up @@ -71,8 +68,8 @@ def test_gitflow_repo_rebuild_1_channel(
post_mocker: Mocker,
default_tag_format_str: str,
version_py_file: Path,
get_sanitized_md_changelog_content: GetSanitizedMdChangelogContentFn,
get_sanitized_rst_changelog_content: GetSanitizedRstChangelogContentFn,
get_sanitized_md_changelog_content: GetSanitizedChangelogContentFn,
get_sanitized_rst_changelog_content: GetSanitizedChangelogContentFn,
):
# build target repo into a temporary directory
target_repo_dir = example_project_dir / repo_fixture_name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,8 @@
from click.testing import CliRunner
from requests_mock import Mocker

from tests.e2e.cmd_version.bump_version.conftest import (
GetSanitizedMdChangelogContentFn,
GetSanitizedRstChangelogContentFn,
InitMirrorRepo4RebuildFn,
)
from tests.e2e.cmd_version.bump_version.conftest import InitMirrorRepo4RebuildFn
from tests.e2e.conftest import GetSanitizedChangelogContentFn
from tests.fixtures.example_project import ExProjectDir
from tests.fixtures.git_repo import (
BuildRepoFromDefinitionFn,
Expand Down Expand Up @@ -71,8 +68,8 @@ def test_gitflow_repo_rebuild_2_channels(
post_mocker: Mocker,
default_tag_format_str: str,
version_py_file: Path,
get_sanitized_md_changelog_content: GetSanitizedMdChangelogContentFn,
get_sanitized_rst_changelog_content: GetSanitizedRstChangelogContentFn,
get_sanitized_md_changelog_content: GetSanitizedChangelogContentFn,
get_sanitized_rst_changelog_content: GetSanitizedChangelogContentFn,
):
# build target repo into a temporary directory
target_repo_dir = example_project_dir / repo_fixture_name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,8 @@
from click.testing import CliRunner
from requests_mock import Mocker

from tests.e2e.cmd_version.bump_version.conftest import (
GetSanitizedMdChangelogContentFn,
GetSanitizedRstChangelogContentFn,
InitMirrorRepo4RebuildFn,
)
from tests.e2e.cmd_version.bump_version.conftest import InitMirrorRepo4RebuildFn
from tests.e2e.conftest import GetSanitizedChangelogContentFn
from tests.fixtures.example_project import ExProjectDir
from tests.fixtures.git_repo import (
BuildRepoFromDefinitionFn,
Expand Down Expand Up @@ -73,8 +70,8 @@ def test_gitflow_repo_rebuild_3_channels(
post_mocker: Mocker,
default_tag_format_str: str,
version_py_file: Path,
get_sanitized_md_changelog_content: GetSanitizedMdChangelogContentFn,
get_sanitized_rst_changelog_content: GetSanitizedRstChangelogContentFn,
get_sanitized_md_changelog_content: GetSanitizedChangelogContentFn,
get_sanitized_rst_changelog_content: GetSanitizedChangelogContentFn,
):
# build target repo into a temporary directory
target_repo_dir = example_project_dir / repo_fixture_name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,8 @@
from click.testing import CliRunner
from requests_mock import Mocker

from tests.e2e.cmd_version.bump_version.conftest import (
GetSanitizedMdChangelogContentFn,
GetSanitizedRstChangelogContentFn,
InitMirrorRepo4RebuildFn,
)
from tests.e2e.cmd_version.bump_version.conftest import InitMirrorRepo4RebuildFn
from tests.e2e.conftest import GetSanitizedChangelogContentFn
from tests.fixtures.example_project import ExProjectDir
from tests.fixtures.git_repo import (
BuildRepoFromDefinitionFn,
Expand Down Expand Up @@ -71,8 +68,8 @@ def test_gitflow_repo_rebuild_4_channels(
post_mocker: Mocker,
default_tag_format_str: str,
version_py_file: Path,
get_sanitized_md_changelog_content: GetSanitizedMdChangelogContentFn,
get_sanitized_rst_changelog_content: GetSanitizedRstChangelogContentFn,
get_sanitized_md_changelog_content: GetSanitizedChangelogContentFn,
get_sanitized_rst_changelog_content: GetSanitizedChangelogContentFn,
):
# build target repo into a temporary directory
target_repo_dir = example_project_dir / repo_fixture_name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,8 @@
from click.testing import CliRunner
from requests_mock import Mocker

from tests.e2e.cmd_version.bump_version.conftest import (
GetSanitizedMdChangelogContentFn,
GetSanitizedRstChangelogContentFn,
InitMirrorRepo4RebuildFn,
)
from tests.e2e.cmd_version.bump_version.conftest import InitMirrorRepo4RebuildFn
from tests.e2e.conftest import GetSanitizedChangelogContentFn
from tests.fixtures.example_project import ExProjectDir
from tests.fixtures.git_repo import (
BuildRepoFromDefinitionFn,
Expand Down Expand Up @@ -71,8 +68,8 @@ def test_githubflow_repo_rebuild_1_channel(
post_mocker: Mocker,
default_tag_format_str: str,
version_py_file: Path,
get_sanitized_md_changelog_content: GetSanitizedMdChangelogContentFn,
get_sanitized_rst_changelog_content: GetSanitizedRstChangelogContentFn,
get_sanitized_md_changelog_content: GetSanitizedChangelogContentFn,
get_sanitized_rst_changelog_content: GetSanitizedChangelogContentFn,
):
# build target repo into a temporary directory
target_repo_dir = example_project_dir / repo_fixture_name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,8 @@
from click.testing import CliRunner
from requests_mock import Mocker

from tests.e2e.cmd_version.bump_version.conftest import (
GetSanitizedMdChangelogContentFn,
GetSanitizedRstChangelogContentFn,
InitMirrorRepo4RebuildFn,
)
from tests.e2e.cmd_version.bump_version.conftest import InitMirrorRepo4RebuildFn
from tests.e2e.conftest import GetSanitizedChangelogContentFn
from tests.fixtures.example_project import ExProjectDir
from tests.fixtures.git_repo import (
BuildRepoFromDefinitionFn,
Expand Down Expand Up @@ -71,8 +68,8 @@ def test_githubflow_repo_rebuild_2_channels(
post_mocker: Mocker,
default_tag_format_str: str,
version_py_file: Path,
get_sanitized_md_changelog_content: GetSanitizedMdChangelogContentFn,
get_sanitized_rst_changelog_content: GetSanitizedRstChangelogContentFn,
get_sanitized_md_changelog_content: GetSanitizedChangelogContentFn,
get_sanitized_rst_changelog_content: GetSanitizedChangelogContentFn,
):
# build target repo into a temporary directory
target_repo_dir = example_project_dir / repo_fixture_name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,8 @@
from click.testing import CliRunner
from requests_mock import Mocker

from tests.e2e.cmd_version.bump_version.conftest import (
GetSanitizedMdChangelogContentFn,
GetSanitizedRstChangelogContentFn,
InitMirrorRepo4RebuildFn,
)
from tests.e2e.cmd_version.bump_version.conftest import InitMirrorRepo4RebuildFn
from tests.e2e.conftest import GetSanitizedChangelogContentFn
from tests.fixtures.example_project import ExProjectDir
from tests.fixtures.git_repo import (
BuildRepoFromDefinitionFn,
Expand Down Expand Up @@ -71,8 +68,8 @@ def test_trunk_repo_rebuild_only_official_releases(
post_mocker: Mocker,
default_tag_format_str: str,
version_py_file: Path,
get_sanitized_md_changelog_content: GetSanitizedMdChangelogContentFn,
get_sanitized_rst_changelog_content: GetSanitizedRstChangelogContentFn,
get_sanitized_md_changelog_content: GetSanitizedChangelogContentFn,
get_sanitized_rst_changelog_content: GetSanitizedChangelogContentFn,
):
# build target repo into a temporary directory
target_repo_dir = example_project_dir / repo_fixture_name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,8 @@
from click.testing import CliRunner
from requests_mock import Mocker

from tests.e2e.cmd_version.bump_version.conftest import (
GetSanitizedMdChangelogContentFn,
GetSanitizedRstChangelogContentFn,
InitMirrorRepo4RebuildFn,
)
from tests.e2e.cmd_version.bump_version.conftest import InitMirrorRepo4RebuildFn
from tests.e2e.conftest import GetSanitizedChangelogContentFn
from tests.fixtures.example_project import ExProjectDir
from tests.fixtures.git_repo import (
BuildRepoFromDefinitionFn,
Expand Down Expand Up @@ -72,8 +69,8 @@ def test_trunk_repo_rebuild_dual_version_spt_official_releases_only(
post_mocker: Mocker,
default_tag_format_str: str,
version_py_file: Path,
get_sanitized_md_changelog_content: GetSanitizedMdChangelogContentFn,
get_sanitized_rst_changelog_content: GetSanitizedRstChangelogContentFn,
get_sanitized_md_changelog_content: GetSanitizedChangelogContentFn,
get_sanitized_rst_changelog_content: GetSanitizedChangelogContentFn,
):
# build target repo into a temporary directory
target_repo_dir = example_project_dir / repo_fixture_name
Expand Down
Loading
Loading