Skip to content

fix(changelog): add changelog_subject_only to skip body parsing - #1974

Closed
bearomorphism wants to merge 3 commits into
commitizen-tools:masterfrom
bearomorphism:fix/1267-changelog-subject-only
Closed

fix(changelog): add changelog_subject_only to skip body parsing#1974
bearomorphism wants to merge 3 commits into
commitizen-tools:masterfrom
bearomorphism:fix/1267-changelog-subject-only

Conversation

@bearomorphism

@bearomorphism bearomorphism commented May 9, 2026

Copy link
Copy Markdown
Collaborator

Description

Closes #1267.

Why

commitizen.changelog.generate_tree_from_commits() parses both the commit subject and each \n\n-separated block of commit.body against commit_parser. So a commit such as

feat: new feature

refactor: incidental cleanup

produces two changelog entries:

### Feat
- new feature
### Refactor
- incidental cleanup

This is rarely what the author intended — they wrote one feat, the body was just a note. The maintainer ack on #1267 confirms the body-parsing is undesirable, but flipping the default would be a behaviour break for existing users (some intentionally rely on it for multi-message commits).

What changed

File Change
commitizen/defaults.py New TypedDict field changelog_subject_only: bool and DEFAULT_SETTINGS["changelog_subject_only"] = False.
commitizen/changelog.py generate_tree_from_commits() gains a subject_only: bool = False kwarg. When True, the body iteration is replaced with an empty tuple, so only the subject is matched.
commitizen/commands/changelog.py Changelog.__call__ forwards self.config.settings["changelog_subject_only"] to generate_tree_from_commits(). This single call site is shared by cz changelog and cz bump --changelog.
tests/test_changelog.py New test_generate_tree_from_commits_subject_only_skips_body_blocks exercises both modes against a commit whose body contains a parser-matching block.
tests/commands/test_changelog_command.py New test_changelog_subject_only_setting_skips_body_parsing is an end-to-end test (real config file, real cz changelog invocation) that catches typos in the config-key wiring.
tests/test_conf.py Two expected-Settings literals updated to include the new key (otherwise the strict-equality assertions in TestReadCfg::test_load_conf fail).
docs/commands/changelog.md New ### changelog_subject_only section next to the existing --merge-prerelease documentation.

How it works

  1. Default-off semantics. changelog_subject_only = false is the default in both the Settings TypedDict and DEFAULT_SETTINGS, so BaseConfig.__init__ always seeds the dict-key with False and the dict-index access in Changelog.__call__ cannot KeyError.
  2. Single chokepoint. Body iteration happens at one place — the chain([subject_match], (body_map_pat.match(block) for block in commit.body.split("\n\n"))) in generate_tree_from_commits(). Replacing the second argument of chain with () when subject_only=True is the entire behavioural change.
  3. Why from __future__ import annotations matters. The new body_matches: Iterable[re.Match[str] | None] annotation only resolves in TYPE_CHECKING blocks at runtime; without from __future__ import annotations the import would have to leave TYPE_CHECKING. The file already had it.
  4. The end-to-end test was specifically requested. Both reviewers (sonnet-4.6 and GitHub Copilot) flagged that the lib-level test alone wouldn't catch a typo in the self.config.settings["changelog_subject_only"] lookup at commands/changelog.py. The fixup commit 292feb4f adds an integration test that constructs a real config and runs cz changelog --dry-run, then verified by hand that the test fails (with KeyError) when the config key is mistyped.

Backward compatibility

  • The default false preserves the historical body-parsing behaviour for every user who hasn't opted in.
  • All 158 pre-existing tests/test_changelog.py and tests/commands/test_changelog_command.py tests pass unchanged.
  • The new subject_only kwarg on generate_tree_from_commits() defaults to False, so any external caller that imports the function continues to work.

Checklist

Was generative AI tooling used to co-author this PR?

  • Yes (please specify the tool below)

Generated-by: Claude following the guidelines

Code Changes

  • Add test cases to all the changes you introduce (1 unit test + 1 integration test)
  • Run uv run poe all locally to ensure this change passes linter check and tests (poe lint clean; 159 changelog tests pass)
  • Manually test the changes (see "Steps to Test" below)
  • Update the documentation for the changes

Documentation Changes

  • Added a ### changelog_subject_only section to docs/commands/changelog.md
  • Run uv run poe doc locally to ensure the documentation pages render correctly
  • Check and fix any broken links (internal or external)

Expected Behavior

Setting Subject feat: X + body refactor: Y Result
changelog_subject_only unset (default false) Both subject and body block parsed Two entries: one feat, one refactor (legacy)
changelog_subject_only = true Only subject parsed One entry: feat: X

Applies to both cz changelog and cz bump --changelog.

Steps to Test This Pull Request

git fetch fork fix/1267-changelog-subject-only
git checkout fork/fix/1267-changelog-subject-only

# 1. Lib-level regression test (forces the body-skip behaviour at the call site).
uv run pytest tests/test_changelog.py -k subject_only -v

# 2. End-to-end test (real config file, real cz changelog invocation).
uv run pytest tests/commands/test_changelog_command.py::test_changelog_subject_only_setting_skips_body_parsing -v

# 3. Verify the e2e test would catch a typo in the config-key wiring.
sed -i.bak 's/changelog_subject_only/changelog_subject_only_TYPO/' commitizen/commands/changelog.py
uv run pytest tests/commands/test_changelog_command.py::test_changelog_subject_only_setting_skips_body_parsing -v
# expected: KeyError: 'changelog_subject_only_TYPO'
mv commitizen/commands/changelog.py.bak commitizen/commands/changelog.py
uv run pytest tests/commands/test_changelog_command.py::test_changelog_subject_only_setting_skips_body_parsing -v
# expected: 1 passed

# 4. Full changelog suite (no regressions).
uv run pytest tests/test_changelog.py tests/commands/test_changelog_command.py -q
# expected: 159 passed

Additional Context

Surfaced while triaging open issues in #1965. Maintainer ack on #1267 confirmed body-parsing is at-best opt-in. The end-to-end integration test was added in fixup commit 292feb4f after both review passes (sonnet-4.6 and Copilot) flagged the wiring as the only path not exercised by tests.

Closes commitizen-tools#1267.

`generate_tree_from_commits()` historically parses the commit subject and
each `\n\n`-separated body block against `commit_parser`, so a commit
whose subject is `feat: ...` and whose body contains another
`refactor: ...` line produces two changelog entries instead of one.
Maintainer ack on commitizen-tools#1267 confirms this is undesirable, but changing the
default is a behavioural break.

This change introduces `changelog_subject_only` (default `false`) on
`Settings`. When set to `true`, the body iteration in
`generate_tree_from_commits()` is skipped, leaving only the subject
line to be matched. The setting is plumbed through
`commands/changelog.py` so both `cz changelog` and `cz bump --changelog`
honour it.

A regression test exercises both modes against a commit whose body
contains a parser-matching block.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@codecov

codecov Bot commented May 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.23%. Comparing base (4b93a50) to head (292feb4).
⚠️ Report is 3 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #1974   +/-   ##
=======================================
  Coverage   98.23%   98.23%           
=======================================
  Files          61       61           
  Lines        2779     2782    +3     
=======================================
+ Hits         2730     2733    +3     
  Misses         49       49           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Followup to fix(changelog): the new default key broke
`tests/test_conf.py::TestReadCfg` because the expected `Settings`
literals pinned the full dict and didn''t know about the new field.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new configuration setting to control whether changelog generation should parse commit body blocks as additional changelog entries, addressing issue #1267 while preserving historical behavior by default.

Changes:

  • Introduces changelog_subject_only (default false) to skip parsing \n\n-separated commit body blocks when generating changelogs.
  • Plumbs the setting through the Changelog command so cz changelog (and cz bump --changelog via the same command) can honor it.
  • Adds a regression test for generate_tree_from_commits(..., subject_only=True) and documents the new setting.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
commitizen/changelog.py Adds subject_only parameter to skip body-block parsing when building the changelog tree.
commitizen/commands/changelog.py Passes changelog_subject_only from config into changelog tree generation.
commitizen/defaults.py Adds changelog_subject_only to typed settings and default settings.
tests/test_changelog.py Adds regression coverage for subject-only parsing behavior.
tests/test_conf.py Updates expected/default settings fixtures to include changelog_subject_only.
docs/commands/changelog.md Documents the new changelog_subject_only setting and its effect.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 268 to 280
tree = changelog.generate_tree_from_commits(
commits,
tags,
commit_parser,
changelog_pattern,
self.unreleased_version,
change_type_map=self.change_type_map,
changelog_message_builder_hook=self.cz.changelog_message_builder_hook,
changelog_release_hook=self.cz.changelog_release_hook,
rules=self.tag_rules,
during_version_bump=self.during_version_bump,
subject_only=self.config.settings["changelog_subject_only"],
)
Add an end-to-end test that sets `changelog_subject_only = true` in
the project configuration, creates a commit with a parser-matching
block in its body, and asserts that `cz changelog --dry-run` only
emits the subject entry. Catches typos in the setting key at
`commands/changelog.py:279` (which would otherwise silently fall
back to `False`); manually verified by injecting a typo, observing
the test fail with KeyError, then restoring.

Addresses the only review finding from PR commitizen-tools#1974.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@bearomorphism

Copy link
Copy Markdown
Collaborator Author

Closing this PR per maintainer-triage policy: feature-request issues should sit with the maintainers for design / scope review before any implementation lands. The issue's type: feature (or implicit equivalent) label means it's not on a "ready-to-implement" track yet.

The implementation itself is preserved on the branch (fix/1267-changelog-subject-only) — if a maintainer decides this is the direction they want, the PR can be re-opened in one click, or the work can serve as a starting point for a maintainer-led design.

This PR is being closed so that #1267 reverts to "awaiting maintainer triage / decision" rather than "PR pending review", which is the correct state for a feature request.

Closed via the round-2 triage cleanup in #1965.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment