diff --git a/commitizen/changelog.py b/commitizen/changelog.py index e4273db93a..bf0f18576f 100644 --- a/commitizen/changelog.py +++ b/commitizen/changelog.py @@ -166,7 +166,8 @@ def generate_tree_from_commits( change_type = change_type_map.get(change_type, change_type) if changelog_message_builder_hook: parsed_message = changelog_message_builder_hook(parsed_message, commit) - changes[change_type].append(parsed_message) + if parsed_message: + changes[change_type].append(parsed_message) # Process body from commit message body_parts = commit.body.split("\n\n") @@ -179,7 +180,12 @@ def generate_tree_from_commits( change_type = parsed_message_body.pop("change_type", None) if change_type_map: change_type = change_type_map.get(change_type, change_type) - changes[change_type].append(parsed_message_body) + if changelog_message_builder_hook: + parsed_message_body = changelog_message_builder_hook( + parsed_message_body, commit + ) + if parsed_message_body: + changes[change_type].append(parsed_message_body) yield {"version": current_tag_name, "date": current_tag_date, "changes": changes} diff --git a/commitizen/commands/changelog.py b/commitizen/commands/changelog.py index f44f59bb7c..8b3d126fe3 100644 --- a/commitizen/commands/changelog.py +++ b/commitizen/commands/changelog.py @@ -9,6 +9,7 @@ from commitizen import bump, changelog, defaults, factory, git, out from commitizen.config import BaseConfig +from commitizen.cz.base import MessageBuilderHook from commitizen.exceptions import ( DryRunExit, NoCommitsFoundError, @@ -145,7 +146,7 @@ def __call__(self): unreleased_version = self.unreleased_version changelog_meta = changelog.Metadata() change_type_map: dict | None = self.change_type_map - changelog_message_builder_hook: Callable | None = ( + changelog_message_builder_hook: MessageBuilderHook | None = ( self.cz.changelog_message_builder_hook ) merge_prerelease = self.merge_prerelease diff --git a/commitizen/cz/base.py b/commitizen/cz/base.py index 14d9e5a522..766c252eee 100644 --- a/commitizen/cz/base.py +++ b/commitizen/cz/base.py @@ -1,7 +1,7 @@ from __future__ import annotations from abc import ABCMeta, abstractmethod -from typing import Any, Callable +from typing import Any, Callable, Protocol from jinja2 import BaseLoader, PackageLoader from prompt_toolkit.styles import Style, merge_styles @@ -11,6 +11,12 @@ from commitizen.defaults import Questions +class MessageBuilderHook(Protocol): + def __call__( + self, message: dict[str, Any], commit: git.GitCommit + ) -> dict[str, Any] | None: ... + + class BaseCommitizen(metaclass=ABCMeta): bump_pattern: str | None = None bump_map: dict[str, str] | None = None @@ -37,9 +43,7 @@ class BaseCommitizen(metaclass=ABCMeta): change_type_order: list[str] | None = None # Executed per message parsed by the commitizen - changelog_message_builder_hook: None | (Callable[[dict, git.GitCommit], dict]) = ( - None - ) + changelog_message_builder_hook: MessageBuilderHook | None = None # Executed only at the end of the changelog generation changelog_hook: Callable[[str, str | None], str] | None = None diff --git a/docs/bump.md b/docs/bump.md index 9a968f7500..0e35083b0a 100644 --- a/docs/bump.md +++ b/docs/bump.md @@ -147,13 +147,13 @@ by their precedence and showcase how a release might flow through a development ### `--increment-mode` -By default, `--increment-mode` is set to `linear`, which ensures taht bumping pre-releases _maintains linearity_: -bumping of a pre-release with lower precedence than the current pre-release phase maintains the current phase of -higher precedence. For example, if the current version is `1.0.0b1` then bumping with `--prerelease alpha` will +By default, `--increment-mode` is set to `linear`, which ensures that bumping pre-releases _maintains linearity_: +bumping of a pre-release with lower precedence than the current pre-release phase maintains the current phase of +higher precedence. For example, if the current version is `1.0.0b1` then bumping with `--prerelease alpha` will continue to bump the “beta” phase. -Setting `--increment-mode` to `exact` instructs `cz bump` to instead apply the -exact changes that have been specified with `--increment` or determined from the commit log. For example, +Setting `--increment-mode` to `exact` instructs `cz bump` to instead apply the +exact changes that have been specified with `--increment` or determined from the commit log. For example, `--prerelease beta` will always result in a `b` tag, and `--increment PATCH` will always increase the patch component. Below are some examples that illustrate the difference in behavior: diff --git a/docs/customization.md b/docs/customization.md index 0c86f60d20..bc51cd2179 100644 --- a/docs/customization.md +++ b/docs/customization.md @@ -318,7 +318,7 @@ You can customize it of course, and this are the variables you need to add to yo | `commit_parser` | `str` | NO | Regex which should provide the variables explained in the [changelog description][changelog-des] | | `changelog_pattern` | `str` | NO | Regex to validate the commits, this is useful to skip commits that don't meet your ruling standards like a Merge. Usually the same as bump_pattern | | `change_type_map` | `dict` | NO | Convert the title of the change type that will appear in the changelog, if a value is not found, the original will be provided | -| `changelog_message_builder_hook` | `method: (dict, git.GitCommit) -> dict` | NO | Customize with extra information your message output, like adding links, this function is executed per parsed commit. Each GitCommit contains the following attrs: `rev`, `title`, `body`, `author`, `author_email` | +| `changelog_message_builder_hook` | `method: (dict, git.GitCommit) -> dict | None` | NO | Customize with extra information your message output, like adding links, this function is executed per parsed commit. Each GitCommit contains the following attrs: `rev`, `title`, `body`, `author`, `author_email`. Returning a falsy value ignore the commit. | | `changelog_hook` | `method: (full_changelog: str, partial_changelog: Optional[str]) -> str` | NO | Receives the whole and partial (if used incremental) changelog. Useful to send slack messages or notify a compliance department. Must return the full_changelog | ```python @@ -339,7 +339,7 @@ class StrangeCommitizen(BaseCommitizen): def changelog_message_builder_hook( self, parsed_message: dict, commit: git.GitCommit - ) -> dict: + ) -> dict | None: rev = commit.rev m = parsed_message["message"] parsed_message[ diff --git a/tests/test_changelog.py b/tests/test_changelog.py index 879443f334..0aff996ff5 100644 --- a/tests/test_changelog.py +++ b/tests/test_changelog.py @@ -1,3 +1,5 @@ +import re + from pathlib import Path import pytest @@ -1319,6 +1321,32 @@ def changelog_message_builder_hook(message: dict, commit: git.GitCommit) -> dict assert "[link](github.com/232323232) Commitizen author@cz.dev" in result +def test_changelog_message_builder_hook_can_remove_commits( + gitcommits, tags, any_changelog_format: ChangelogFormat +): + def changelog_message_builder_hook(message: dict, commit: git.GitCommit): + return None + + parser = ConventionalCommitsCz.commit_parser + changelog_pattern = ConventionalCommitsCz.changelog_pattern + loader = ConventionalCommitsCz.template_loader + template = any_changelog_format.template + tree = changelog.generate_tree_from_commits( + gitcommits, + tags, + parser, + changelog_pattern, + changelog_message_builder_hook=changelog_message_builder_hook, + ) + result = changelog.render_changelog(tree, loader, template) + + RE_HEADER = re.compile(r"^## v?\d+\.\d+\.\d+(\w)* \(\d{4}-\d{2}-\d{2}\)$") + # Rendered changelog should be empty, only containing version headers + for no, line in enumerate(result.splitlines()): + if line := line.strip(): + assert RE_HEADER.match(line), f"Line {no} should not be there: {line}" + + def test_get_smart_tag_range_returns_an_extra_for_a_range(tags): start, end = ( tags[0],