Skip to content

fix(bump): only consider commit titles when matching bump patterns#1983

Draft
bearomorphism wants to merge 2 commits intocommitizen-tools:masterfrom
bearomorphism:fix/1772-anchor-bump-pattern-to-title
Draft

fix(bump): only consider commit titles when matching bump patterns#1983
bearomorphism wants to merge 2 commits intocommitizen-tools:masterfrom
bearomorphism:fix/1772-anchor-bump-pattern-to-title

Conversation

@bearomorphism
Copy link
Copy Markdown
Collaborator

@bearomorphism bearomorphism commented May 9, 2026

Description

Closes #1772.

Why

cz bump was producing spurious PATCH version bumps on ci: commits whose bodies contained Dependabot-generated content. Dependabot's pull-request descriptions quote upstream repository changelogs verbatim, and those changelogs routinely contain lines such as fix: update @actions/artifact to ^5.0.0 for Node.js 24 punycode fix. Because find_increment in commitizen/bump.py:37 (master) iterates over every line of commit.message — which is computed as f"{self.title}\n\n{self.body}".strip() at commitizen/git.py:78–79 — those body lines are matched against the bump_pattern and counted as fix: commits, triggering a PATCH bump on a commit that is semantically a dependency-update automation entry with a ci: title.

The issue was initially closed as "working as designed" after a maintainer misread the log: the fix: line in question was not a commit in the user's repository — it was text inside the Dependabot PR body (which becomes the body of the squash-merged ci: commit). @Clockwork-Muse clarified this with a screenshot in February 2026, and @Lee-W reopened the issue. Subsequent verification against master (4.15.1) in the #1964 audit confirmed the false positive is still reproducible: a ci: commit whose body contains an unbulleted fix: … line at column 0 produces a PATCH bump and a spurious ### Fix changelog section. The Conventional Commits specification places the commit type exclusively in the title (the first line); scanning every body line for type-prefixed text contradicts the spec and produces this class of false positive on auto-generated commit bodies.

What changed

File Change
commitizen/bump.py In find_increment(), replace the commit.message.split("\n") loop at line 37 (master) with a candidate_lines list containing only commit.title plus any body lines that begin with BREAKING CHANGE: or BREAKING-CHANGE:
tests/test_bump_find_increment.py Add two regression tests: test_find_increment_ignores_type_tokens_in_commit_body (the Dependabot false-positive scenario) and test_find_increment_still_honors_breaking_change_in_body (confirming MAJOR is still triggered by a body footer)

How it works

  • Title-only type matching: candidate_lines is initialised as [commit.title]. The commit.title attribute stores only the first line of the commit message (set at commitizen/git.py:71), so no body content can reach the bump_pattern via this path.
  • Body still scanned for breaking-change footers: the body is split on "\n" and each line is tested with stripped.startswith(("BREAKING CHANGE:", "BREAKING-CHANGE:")) after a lstrip() call. Matching lines are appended to candidate_lines and then processed by the same select_pattern.search() call as the title.
  • lstrip() rather than strip() for footer indentation tolerance — the non-obvious choice: the Conventional Commits spec allows trailers to be indented (e.g., BREAKING CHANGE: x). Using lstrip() means such indented footers are still accepted, while a body line like fix: something is stripped to fix: something, which does not start with either breaking-change token and so is correctly excluded. Using strip() instead would also work here, but lstrip() is semantically more precise — the trailing content of a footer is not guaranteed to be whitespace-free, so only left-stripping is warranted for the prefix check.
  • commit.message property untouched: commitizen/git.py:77–79 defines message as title + "\n\n" + body and this property is used elsewhere (changelog rendering, the check command). Only find_increment changes which lines it examines; every other consumer of commit.message is unaffected.

Backward compatibility

  • Any commit whose title begins with a conventional type (fix:, feat:, perf:, etc.) is still bumped exactly as before.
  • BREAKING CHANGE: / BREAKING-CHANGE: footers in the commit body still trigger a MAJOR bump.
  • The feat!: / fix!: exclamation-mark breaking-change syntax in the title is unaffected — it is matched against commit.title by the same select_pattern.
  • commit.message, commit.title, and commit.body on GitCommit (commitizen/git.py:60–79) are untouched.
  • All 12 pre-existing test_find_increment_* tests in tests/test_bump_find_increment.py pass; the full bump command integration test suite (131 tests) passes with the same 4 GPG-fixture tests deselected on Windows as before.

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
  • Run uv run poe all locally to ensure this change passes linter check and tests
  • Manually test the changes (see "Steps to Test" below)
  • Update the documentation for the changes

Expected Behavior

Scenario Outcome
ci: commit whose body contains fix: … lines (Dependabot-style) No bump — body type-prefixed lines are ignored
fix: normal patch fix in the commit title PATCH bump — unchanged
feat: new feature title with BREAKING CHANGE: old API removed in the body MAJOR bump — breaking-change footer still honoured
feat!: breaking change flagged in title MAJOR bump — exclamation-mark syntax in title still honoured
ci: commit with a bulleted body line - fix: addresses CVE-… No bump — leading - prevents startswith match and the line is excluded

Steps to Test This Pull Request

git fetch fork fix/1772-anchor-bump-pattern-to-title
git checkout fork/fix/1772-anchor-bump-pattern-to-title

# 1. Targeted regression test.
uv run pytest tests/test_bump_find_increment.py -v

# 2. Reproduce-the-bug-then-verify-the-fix sequence.
python - <<'EOF'
from commitizen import bump
from commitizen.git import GitCommit
from commitizen.cz.conventional_commits import ConventionalCommitsCz

# Exact reproducer from #1772: a ci: commit whose body quotes an upstream
# changelog entry containing "fix: ..." at column 0.
DEPENDABOT_BODY = (
    "Bumps actions/upload-artifact from 5 to 6.\n"
    "fix: update @actions/artifact to ^5.0.0 for Node.js 24 punycode fix\n"
)

commit = GitCommit(
    rev="abc123",
    title="ci: bump actions/upload-artifact from 5 to 6 (#23)",
    body=DEPENDABOT_BODY,
)
result = bump.find_increment(
    [commit],
    regex=ConventionalCommitsCz.bump_pattern,
    increments_map=ConventionalCommitsCz.bump_map,
)
assert result is None, f"FAIL: expected None, got {result!r} — false-positive bump still present"
print("OK: Dependabot body does not trigger a bump")

# Confirm BREAKING CHANGE in body still works.
bc_commit = GitCommit(
    rev="def456",
    title="feat: new user interface",
    body="some detail\n\nBREAKING CHANGE: drops support for legacy config format",
)
result = bump.find_increment(
    [bc_commit],
    regex=ConventionalCommitsCz.bump_pattern,
    increments_map=ConventionalCommitsCz.bump_map,
)
assert result == "MAJOR", f"FAIL: expected MAJOR, got {result!r}"
print("OK: BREAKING CHANGE footer still triggers MAJOR")
EOF

Additional Context

This fix was surfaced during the #1964 issue audit. The issue had been incorrectly closed in February 2026 after a maintainer misidentified the fix: line as a commit in the user's own repository; @Clockwork-Muse's follow-up comment and screenshot clarified that the line comes from Dependabot quoting an upstream changelog entry — it is not a commit in the user's repository at all. @namwoam was assigned in January 2026 and identified the correct code path (commitizen/bump.py:37–38, master) but proposed an author-filter approach; this PR takes the simpler route of anchoring the type match to the commit title only, which aligns with the Conventional Commits specification without introducing new configuration surface. @namwoam is welcome to take over if still active on this — please comment and this PR will be closed.

`find_increment` previously scanned every line of every commit message
when looking for the conventional-commits type token. Auto-generated
commit bodies (notably Dependabot PR descriptions that quote upstream
changelog lines like `fix: ...`) frequently contain text that matches
the bump pattern even though no in-repo change of that type happened.
The result was false-positive `PATCH` bumps on plain `ci:` commits
(commitizen-tools#1772).

Restrict type-pattern matching to the commit title (the first line),
where the conventional-commits type lives. The body is still scanned,
but only for `BREAKING CHANGE:` / `BREAKING-CHANGE:` footers, which
the spec places there. Other body lines no longer drive the increment.

Closes commitizen-tools#1772

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

codecov Bot commented May 9, 2026

Codecov Report

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

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #1983   +/-   ##
=======================================
  Coverage   98.23%   98.23%           
=======================================
  Files          61       61           
  Lines        2779     2784    +5     
=======================================
+ Hits         2730     2735    +5     
  Misses         49       49           

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

@bearomorphism bearomorphism marked this pull request as draft May 9, 2026 13:25
@bearomorphism bearomorphism requested a review from Copilot May 9, 2026 14:03
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adjusts cz bump increment detection to align with Conventional Commits by matching bump patterns against the commit title only, while still honoring BREAKING CHANGE: / BREAKING-CHANGE: footers in the body. This prevents false-positive bumps from autogenerated commit bodies (e.g., Dependabot PR descriptions) that contain lines like fix: ....

Changes:

  • Update find_increment() to scan commit.title plus body lines that begin with BREAKING CHANGE: / BREAKING-CHANGE: (after left-stripping).
  • Add regression tests covering the Dependabot body false-positive scenario and ensuring breaking-change footers in the body still trigger MAJOR.

Reviewed changes

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

File Description
commitizen/bump.py Restricts bump-pattern matching to commit titles, with an allowlist for breaking-change footers in the body.
tests/test_bump_find_increment.py Adds regression tests for ignoring type-like tokens in commit bodies and preserving breaking-change footer behavior.

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

Comment thread tests/test_bump_find_increment.py Outdated
@bearomorphism
Copy link
Copy Markdown
Collaborator Author

drafting this since I'm going to remove find_increment

Replaced the invisible U+200B in '@actions/artifact' with plain ASCII so the fixture is readable, greppable, and produces clean diffs. Test still asserts the regression (type tokens in commit body don't trigger a bump).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bump Increments PATCH Version on ci Commit

2 participants