Skip to content

Commit

Permalink
feat(changelog): adds a changelog_release_hook called for each rele…
Browse files Browse the repository at this point in the history
…ase in the changelog (#1018)
  • Loading branch information
noirbizarre authored Mar 19, 2024
1 parent 5d64224 commit f72828a
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 3 deletions.
16 changes: 14 additions & 2 deletions commitizen/changelog.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@

from commitizen import out
from commitizen.bump import normalize_tag
from commitizen.cz.base import ChangelogReleaseHook
from commitizen.exceptions import InvalidConfigurationError, NoCommitsFoundError
from commitizen.git import GitCommit, GitTag
from commitizen.version_schemes import (
Expand Down Expand Up @@ -113,6 +114,7 @@ def generate_tree_from_commits(
unreleased_version: str | None = None,
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,
) -> Iterable[dict]:
Expand Down Expand Up @@ -143,11 +145,14 @@ def generate_tree_from_commits(
commit_tag, used_tags, merge_prerelease, scheme=scheme
):
used_tags.append(commit_tag)
yield {
release = {
"version": current_tag_name,
"date": current_tag_date,
"changes": changes,
}
if changelog_release_hook:
release = changelog_release_hook(release, commit_tag)
yield release
current_tag_name = commit_tag.name
current_tag_date = commit_tag.date
changes = defaultdict(list)
Expand Down Expand Up @@ -178,7 +183,14 @@ def generate_tree_from_commits(
change_type_map,
)

yield {"version": current_tag_name, "date": current_tag_date, "changes": changes}
release = {
"version": current_tag_name,
"date": current_tag_date,
"changes": changes,
}
if changelog_release_hook:
release = changelog_release_hook(release, commit_tag)
yield release


def process_commit_message(
Expand Down
6 changes: 5 additions & 1 deletion commitizen/commands/changelog.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from commitizen import bump, changelog, defaults, factory, git, out

from commitizen.config import BaseConfig
from commitizen.cz.base import MessageBuilderHook
from commitizen.cz.base import MessageBuilderHook, ChangelogReleaseHook
from commitizen.exceptions import (
DryRunExit,
NoCommitsFoundError,
Expand Down Expand Up @@ -150,6 +150,9 @@ def __call__(self):
changelog_message_builder_hook: MessageBuilderHook | None = (
self.cz.changelog_message_builder_hook
)
changelog_release_hook: ChangelogReleaseHook | None = (
self.cz.changelog_release_hook
)
merge_prerelease = self.merge_prerelease

if self.export_template_to:
Expand Down Expand Up @@ -203,6 +206,7 @@ def __call__(self):
unreleased_version,
change_type_map=change_type_map,
changelog_message_builder_hook=changelog_message_builder_hook,
changelog_release_hook=changelog_release_hook,
merge_prerelease=merge_prerelease,
scheme=self.scheme,
)
Expand Down
9 changes: 9 additions & 0 deletions commitizen/cz/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ def __call__(
) -> dict[str, Any] | Iterable[dict[str, Any]] | None: ...


class ChangelogReleaseHook(Protocol):
def __call__(
self, release: dict[str, Any], tag: git.GitTag | None
) -> dict[str, Any]: ...


class BaseCommitizen(metaclass=ABCMeta):
bump_pattern: str | None = None
bump_map: dict[str, str] | None = None
Expand Down Expand Up @@ -48,6 +54,9 @@ class BaseCommitizen(metaclass=ABCMeta):
# Executed only at the end of the changelog generation
changelog_hook: Callable[[str, str | None], str] | None = None

# Executed for each release in the changelog
changelog_release_hook: ChangelogReleaseHook | None = None

# Plugins can override templates and provide extra template data
template_loader: BaseLoader = PackageLoader("commitizen", "templates")
template_extras: dict[str, Any] = {}
Expand Down
5 changes: 5 additions & 0 deletions docs/customization.md
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,7 @@ You can customize it of course, and this are the variables you need to add to yo
| `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 | list | 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 |
| `changelog_release_hook` | `method: (release: dict, tag: git.GitTag) -> dict` | NO | Receives each generated changelog release and its associated tag. Useful to enrich a releases before they are rendered. Must return the update release

```python
from commitizen.cz.base import BaseCommitizen
Expand Down Expand Up @@ -347,6 +348,10 @@ class StrangeCommitizen(BaseCommitizen):
] = f"{m} {rev} [{commit.author}]({commit.author_email})"
return parsed_message
def changelog_release_hook(self, release: dict, tag: git.GitTag) -> dict:
release["author"] = tag.author
return release
def changelog_hook(
self, full_changelog: str, partial_changelog: Optional[str]
) -> str:
Expand Down
22 changes: 22 additions & 0 deletions tests/commands/test_changelog_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,28 @@ def test_changelog_hook_customize(mocker: MockFixture, config_customize):
changelog_hook_mock.assert_called_with(full_changelog, full_changelog)


@pytest.mark.usefixtures("tmp_commitizen_project")
def test_changelog_release_hook(mocker: MockFixture, config):
def changelog_release_hook(release: dict, tag: git.GitTag) -> dict:
return release

for i in range(3):
create_file_and_commit("feat: new file")
create_file_and_commit("refactor: is in changelog")
create_file_and_commit("Merge into master")
git.tag(f"0.{i + 1}.0")

# changelog = Changelog(config, {})
changelog = Changelog(
config, {"unreleased_version": None, "incremental": True, "dry_run": False}
)
mocker.patch.object(changelog.cz, "changelog_release_hook", changelog_release_hook)
spy = mocker.spy(changelog.cz, "changelog_release_hook")
changelog()

assert spy.call_count == 3


@pytest.mark.usefixtures("tmp_commitizen_project")
def test_changelog_with_non_linear_merges_commit_order(
mocker: MockFixture, config_customize
Expand Down
21 changes: 21 additions & 0 deletions tests/test_changelog.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import re

from pathlib import Path
from typing import Optional

import pytest
from jinja2 import FileSystemLoader
Expand Down Expand Up @@ -1404,6 +1405,26 @@ def changelog_message_builder_hook(message: dict, commit: git.GitCommit):
), f"Line {no}: type {change_type} should have been overridden"


def test_render_changelog_with_changelog_release_hook(
gitcommits, tags, any_changelog_format: ChangelogFormat
):
def changelog_release_hook(release: dict, tag: Optional[git.GitTag]) -> dict:
release["extra"] = "whatever"
return release

parser = ConventionalCommitsCz.commit_parser
changelog_pattern = ConventionalCommitsCz.changelog_pattern
tree = changelog.generate_tree_from_commits(
gitcommits,
tags,
parser,
changelog_pattern,
changelog_release_hook=changelog_release_hook,
)
for release in tree:
assert release["extra"] == "whatever"


def test_get_smart_tag_range_returns_an_extra_for_a_range(tags):
start, end = (
tags[0],
Expand Down

0 comments on commit f72828a

Please sign in to comment.