Skip to content

Commit 123e27b

Browse files
committed
feat(commands): add --add-author paramater to include author in changelog
Signed-off-by: Emmanuel Arias <eamanu@debian.org>
1 parent b78d581 commit 123e27b

21 files changed

Lines changed: 270 additions & 18 deletions

‎commitizen/changelog.py‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
from collections import OrderedDict, defaultdict
3232
from dataclasses import dataclass
3333
from datetime import date
34+
from enum import Enum
3435
from itertools import chain
3536
from typing import TYPE_CHECKING, Any
3637

@@ -85,6 +86,13 @@ class IncrementalMergeInfo:
8586
index: int | None = None
8687

8788

89+
class AuthorSource(str, Enum):
90+
"""Source of the author shown in each changelog entry (`--add-author`)"""
91+
92+
AUTHOR = "author"
93+
SIGNED_OFF_BY = "signed-off-by"
94+
95+
8896
def get_commit_tag(commit: GitCommit, tags: list[GitTag]) -> GitTag | None:
8997
return next((tag for tag in tags if tag.rev == commit.rev), None)
9098

@@ -100,6 +108,7 @@ def generate_tree_from_commits(
100108
changelog_release_hook: ChangelogReleaseHook | None = None,
101109
rules: TagRules | None = None,
102110
during_version_bump: bool = False,
111+
author_source: AuthorSource | None = None,
103112
) -> Generator[dict[str, Any], None, None]:
104113
pat = re.compile(changelog_pattern)
105114
map_pat = re.compile(commit_parser, re.MULTILINE)
@@ -159,6 +168,7 @@ def generate_tree_from_commits(
159168
commit,
160169
changes,
161170
change_type_map,
171+
author_source,
162172
)
163173

164174
release = {
@@ -171,12 +181,33 @@ def generate_tree_from_commits(
171181
yield release
172182

173183

184+
def resolve_changelog_author(
185+
commit: GitCommit, author_source: AuthorSource
186+
) -> tuple[str, str]:
187+
"""Get the author name and email to show in a changelog entry.
188+
189+
Args:
190+
commit: The commit of the changelog entry.
191+
author_source: Where to read the author from.
192+
193+
Returns:
194+
A `(name, email)` tuple. For `SIGNED_OFF_BY`, it is the first
195+
`Signed-off-by` trailer, or the commit author when there is no trailer.
196+
"""
197+
if author_source is AuthorSource.SIGNED_OFF_BY and (
198+
signed_off_by := commit.signed_off_by
199+
):
200+
return signed_off_by
201+
return commit.author, commit.author_email
202+
203+
174204
def process_commit_message(
175205
hook: MessageBuilderHook | None,
176206
parsed: re.Match[str],
177207
commit: GitCommit,
178208
ref_changes: MutableMapping[str | None, list],
179209
change_type_map: Mapping[str, str] | None = None,
210+
author_source: AuthorSource | None = None,
180211
) -> None:
181212
message: dict[str, Any] = {
182213
"sha1": commit.rev,
@@ -185,6 +216,10 @@ def process_commit_message(
185216
"author_email": commit.author_email,
186217
**parsed.groupdict(),
187218
}
219+
if author_source is not None:
220+
name, email = resolve_changelog_author(commit, author_source)
221+
message["changelog_author"] = name
222+
message["changelog_author_email"] = email
188223

189224
processed_msg = hook(message, commit) if hook else message
190225
if not processed_msg:

‎commitizen/cli.py‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,19 @@ def __call__(
7979
"metavar": "EXTRA",
8080
"help": "Changelog extra variables (in the form 'key=value').",
8181
},
82+
{
83+
"name": ["--add-author"],
84+
"nargs": "?",
85+
"const": "author",
86+
"default": None,
87+
"choices": ["author", "signed-off-by"],
88+
"help": (
89+
"Add the author to each changelog entry. "
90+
"'author' (default) uses the commit author. "
91+
"'signed-off-by' uses the first Signed-off-by trailer, "
92+
"or the commit author if the commit has no trailer."
93+
),
94+
},
8295
)
8396

8497
data = {

‎commitizen/commands/bump.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040

4141

4242
class BumpArgs(Settings, total=False):
43+
add_author: str | None
4344
allow_no_commit: bool | None
4445
annotated_tag_message: str | None
4546
build_metadata: str | None
@@ -330,6 +331,7 @@ def __call__(self) -> None:
330331
"dry_run": dry_run,
331332
# governs logic for merge_prerelease
332333
"during_version_bump": self.arguments["prerelease"] is None,
334+
"add_author": self.arguments.get("add_author"),
333335
}
334336
if self.changelog_to_stdout:
335337
try:

‎commitizen/commands/changelog.py‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ class ChangelogArgs(TypedDict, total=False):
4444
export_template: str
4545
during_version_bump: bool | None
4646
allow_no_commit: bool | None # Internal-only when invoked by bump.
47+
add_author: str | None
4748

4849

4950
class Changelog:
@@ -127,6 +128,8 @@ def __init__(self, config: BaseConfig, arguments: ChangelogArgs) -> None:
127128
self.during_version_bump: bool = arguments.get("during_version_bump") or False
128129
# Internal flag used when changelog is invoked from `cz bump --allow-no-commit`.
129130
self.allow_no_commit: bool = bool(arguments.get("allow_no_commit"))
131+
add_author = arguments.get("add_author")
132+
self.author_source = changelog.AuthorSource(add_author) if add_author else None
130133

131134
def _find_incremental_rev(self, latest_version: str, tags: Iterable[GitTag]) -> str:
132135
"""Try to find the 'start_rev'.
@@ -277,6 +280,7 @@ def __call__(self) -> None:
277280
changelog_release_hook=self.cz.changelog_release_hook,
278281
rules=self.tag_rules,
279282
during_version_bump=self.during_version_bump,
283+
author_source=self.author_source,
280284
)
281285
if self.change_type_order:
282286
tree = changelog.generate_ordered_changelog_tree(

‎commitizen/git.py‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
import os
4+
import re
45
from enum import Enum
56
from functools import lru_cache
67
from pathlib import Path
@@ -14,6 +15,12 @@
1415
from collections.abc import Sequence
1516

1617

18+
_SIGNED_OFF_BY_PATTERN = re.compile(
19+
r"^Signed-off-by:\s*(?P<name>.+?)\s*<(?P<email>[^>]+)>\s*$",
20+
re.MULTILINE | re.IGNORECASE,
21+
)
22+
23+
1724
class EOLType(Enum):
1825
"""The EOL type from `git config core.eol`."""
1926

@@ -78,6 +85,18 @@ def __init__(
7885
def message(self) -> str:
7986
return f"{self.title}\n\n{self.body}".strip()
8087

88+
@property
89+
def signed_off_by(self) -> tuple[str, str] | None:
90+
"""Get the first `Signed-off-by` trailer of the commit body.
91+
92+
Returns:
93+
`(name, email)` tuple, or `None` when the body has no trailer.
94+
"""
95+
match = _SIGNED_OFF_BY_PATTERN.search(self.body)
96+
if match is None:
97+
return None
98+
return match.group("name"), match.group("email")
99+
81100
@classmethod
82101
def from_rev_and_commit(cls, rev_and_commit: str) -> GitCommit:
83102
"""Create a GitCommit instance from a formatted commit string.

‎commitizen/templates/CHANGELOG.adoc.j2‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@
1010

1111
{% for change in changes %}
1212
{% if change.scope %}
13-
* *{{ change.scope }}*: {{ change.message }}
13+
* *{{ change.scope }}*: {{ change.message }}{{ " (" ~ change.changelog_author ~ " <" ~ change.changelog_author_email ~ ">)" if change.changelog_author }}
1414
{% elif change.message %}
15-
* {{ change.message }}
15+
* {{ change.message }}{{ " (" ~ change.changelog_author ~ " <" ~ change.changelog_author_email ~ ">)" if change.changelog_author }}
1616
{% endif %}
1717
{% endfor %}
1818
{% endfor %}

‎commitizen/templates/CHANGELOG.md.j2‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@
1010

1111
{% for change in changes %}
1212
{% if change.scope %}
13-
- **{{ change.scope }}**: {{ change.message }}
13+
- **{{ change.scope }}**: {{ change.message }}{{ " (" ~ change.changelog_author ~ " <" ~ change.changelog_author_email ~ ">)" if change.changelog_author }}
1414
{% elif change.message %}
15-
- {{ change.message }}
15+
- {{ change.message }}{{ " (" ~ change.changelog_author ~ " <" ~ change.changelog_author_email ~ ">)" if change.changelog_author }}
1616
{% endif %}
1717
{% endfor %}
1818
{% endfor %}

‎commitizen/templates/CHANGELOG.rst.j2‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@
1414

1515
{% for change in changes %}
1616
{% if change.scope %}
17-
- **{{ change.scope }}**: {{ change.message }}
17+
- **{{ change.scope }}**: {{ change.message }}{{ " (" ~ change.changelog_author ~ " <" ~ change.changelog_author_email ~ ">)" if change.changelog_author }}
1818
{% elif change.message %}
19-
- {{ change.message }}
19+
- {{ change.message }}{{ " (" ~ change.changelog_author ~ " <" ~ change.changelog_author_email ~ ">)" if change.changelog_author }}
2020
{% endif %}
2121
{% endfor %}
2222
{% endfor %}

‎commitizen/templates/CHANGELOG.textile.j2‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ h3. {{ change_key }}
1010

1111
{% for change in changes %}
1212
{% if change.scope %}
13-
- *{{ change.scope }}*: {{ change.message }}
13+
- *{{ change.scope }}*: {{ change.message }}{{ " (" ~ change.changelog_author ~ " <" ~ change.changelog_author_email ~ ">)" if change.changelog_author }}
1414
{% elif change.message %}
15-
- {{ change.message }}
15+
- {{ change.message }}{{ " (" ~ change.changelog_author ~ " <" ~ change.changelog_author_email ~ ">)" if change.changelog_author }}
1616
{% endif %}
1717
{% endfor %}
1818
{% endfor %}

‎tests/commands/test_common_command/test_command_shows_description_when_use_help_option_py_3_10_bump_.txt‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ usage: cz bump [-h] [--dry-run] [--files-only] [--version-files-only]
88
[--annotated-tag-message ANNOTATED_TAG_MESSAGE] [--gpg-sign]
99
[--changelog-to-stdout] [--git-output-to-stderr] [--retry]
1010
[--major-version-zero] [--template TEMPLATE] [--extra EXTRA]
11-
[--file-name FILE_NAME] [--prerelease-offset PRERELEASE_OFFSET]
11+
[--add-author [{author,signed-off-by}]] [--file-name FILE_NAME]
12+
[--prerelease-offset PRERELEASE_OFFSET]
1213
[--version-scheme {pep440,semver,semver2}]
1314
[--version-type {pep440,semver,semver2}]
1415
[--build-metadata BUILD_METADATA] [--get-next]
@@ -73,6 +74,11 @@ options:
7374
working directory).
7475
--extra EXTRA, -e EXTRA
7576
Changelog extra variables (in the form 'key=value').
77+
--add-author [{author,signed-off-by}]
78+
Add the author to each changelog entry. 'author'
79+
(default) uses the commit author. 'signed-off-by' uses
80+
the first Signed-off-by trailer, or the commit author
81+
if the commit has no trailer.
7682
--file-name FILE_NAME
7783
File name of changelog (default: 'CHANGELOG.md').
7884
--prerelease-offset PRERELEASE_OFFSET

0 commit comments

Comments
 (0)