Skip to content

fix(bump): flag sibling version mismatches under --check-consistency#1978

Open
bearomorphism wants to merge 2 commits intocommitizen-tools:masterfrom
bearomorphism:fix/595-check-consistency-version-provider
Open

fix(bump): flag sibling version mismatches under --check-consistency#1978
bearomorphism wants to merge 2 commits intocommitizen-tools:masterfrom
bearomorphism:fix/595-check-consistency-version-provider

Conversation

@bearomorphism
Copy link
Copy Markdown
Collaborator

@bearomorphism bearomorphism commented May 9, 2026

Description

Closes #595.

Why

commitizen/bump.py:update_version_in_files applies a regex pattern to each line of every file in version_files. When a broad pattern such as pyproject.toml:version is used, the regex legitimately matches multiple version = lines — one under [tool.commitizen] holding the current commitizen-managed version, and one under [tool.poetry] (or [project] for PEP-621) holding a potentially out-of-sync value maintained by a different tool. The old inner loop (master commitizen/bump.py:88-95) replaced the current_version string wherever pattern.search(line) was true and set current_version_found only when the replacement changed the line. The loop never inspected lines that matched the pattern but did not contain current_version, so the [tool.poetry].version line was silently skipped — the bump proceeded, --check-consistency raised no error, and the file was left with two different version strings.

The original reporter @gpongelli observed the mismatch in commitizen 2.34.0; maintainer @Lee-W confirmed the reproduction and noted that the regex-based approach made a proper same-file consistency check architecturally difficult. A follow-up comment from @woile noted the workaround (use a more specific regex such as pyproject.toml:\[tool\.poetry\][.\s\D]*^version). The triage audit on #1964 confirmed the issue is still present in master (v4.15.1): a pyproject.toml with [tool.poetry].version = "2.5.7" and [tool.commitizen].version = "2.5.2" running cz bump --check-consistency --yes exits 0, bumps commitizen's version to 2.6.0, and leaves the poetry version at 2.5.7 — still mismatched and silent.

This PR narrows the fix: when check_consistency=True, any line that (a) matches the version-files regex and (b) does not contain current_version but (c) contains a semver-shaped value is collected as an inconsistent_lines entry. If any such entries exist after processing the file, CurrentVersionNotFoundError is raised with a message that names the file path, the line number, and the offending version string so the user knows exactly which tool is out of sync. The default path (check_consistency=False) is completely unchanged.

What changed

File Change
commitizen/bump.py Refactor inner loop (was lines 88–95) to enumerate lines and track inconsistent_lines; add _LIKELY_VERSION_VALUE_RE module-level constant; raise CurrentVersionNotFoundError with line-level details when check_consistency=True and sibling mismatches are found
tests/test_bump_update_version_in_files.py Two new regression tests: sibling detection raises with both version strings in the error message; legacy no-check_consistency behaviour leaves sibling untouched without raising

How it works

  • Two-phase consistency check. The existing check (master commitizen/bump.py:98-103) validates that at least one line was updated — i.e., current_version was found. The new check is a second gate that validates that no other line looks like it stores a different version. Both checks run after the full file is read, before any write occurs, so a failed consistency check always leaves the file on disk untouched.

  • _LIKELY_VERSION_VALUE_RE is intentionally conservative. The regex \d+\.\d+\.\d+(?:[\w.\-+]*) matches the canonical MAJOR.MINOR.PATCH semver shape (plus optional pre-release and build-metadata suffixes). It deliberately does not match bare integers, Python import paths, comment prose, or generic version = keywords without a version-shaped right-hand side. This prevents false positives from unrelated occurrences of the word version in configuration comments or code that the user's regex happens to match.

  • Line numbers are 1-based in the error message. enumerate(version_file, 1) is used so that the reported line number matches what editors and grep -n report. This makes the error message actionable without requiring the user to open the file and count from zero.

  • check_consistency=False path is byte-for-byte identical to the old behaviour. The inconsistent_lines list is only evaluated inside the if check_consistency block. No code runs on the fast path for users who do not pass --check-consistency.

  • Why not a TOML-aware parser? @woile and @Lee-W discussed this in the issue thread: a format-specific parser (TOML, JSON, YAML) would add maintenance burden and break the general version_files: any-file contract. The regex approach is deliberately format-agnostic; the conservative _LIKELY_VERSION_VALUE_RE secondary filter is enough to catch the common pyproject.toml case without committing to TOML semantics.

Backward compatibility

  • check_consistency=False (the default) preserves the legacy behaviour exactly: only lines containing current_version are rewritten; sibling lines are left alone; no exception is raised.
  • The new CurrentVersionNotFoundError message format is a superset of the existing one — both state the file path and the fact that the version wasn't found; the new message additionally names the offending line.
  • All pre-existing update_version_in_files tests and bump command tests pass unchanged.
  • No CLI flags or configuration keys are added; --check-consistency is the existing flag whose scope is extended.

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
pyproject.toml with [tool.poetry].version = "2.5.7" and [tool.commitizen].version = "2.5.2", version_files = ["pyproject.toml:version"], cz bump --check-consistency Exits non-zero; error message names line 2: version = "2.5.7" as the inconsistent entry; file is not modified
Same setup, cz bump (no --check-consistency) Bumps only the commitizen version line; poetry line is left at 2.5.7; no exception — legacy behaviour preserved
Single version = line in file, cz bump --check-consistency Works as before — inconsistent_lines is empty, no new error
version_files regex narrowed to pyproject.toml:\[tool\.commitizen\] Poetry line never matches the regex; inconsistent_lines is empty regardless of --check-consistency

Steps to Test This Pull Request

git fetch fork fix/595-check-consistency-version-mismatch
git checkout fork/fix/595-check-consistency-version-mismatch

# 1. Targeted regression tests.
uv run pytest \
  tests/test_bump_update_version_in_files.py::test_update_version_in_files_check_consistency_detects_sibling_version \
  tests/test_bump_update_version_in_files.py::test_update_version_in_files_check_consistency_off_keeps_legacy_behaviour \
  -v

# 2. Reproduce-the-bug-then-verify-the-fix sequence (exact scenario from #595).
mkdir cz595 && cd cz595
git init -b main
git config user.name test && git config user.email test@example.com

cat > pyproject.toml << 'EOF'
[tool.poetry]
name = "cool_proj"
version = "2.5.7"

[tool.commitizen]
name = "cz_conventional_commits"
version = "2.5.2"
version_files = ["pyproject.toml:version"]
EOF
git add pyproject.toml && git commit -m "feat: initial setup"

# Before fix: exits 0, silently bumps commitizen version only, leaves poetry at 2.5.7.
# After fix:  exits non-zero, prints offending line (version = "2.5.7"), file untouched.
cz bump --check-consistency --yes

# Verify file was NOT modified on failure:
grep 'version = "2.5.2"' pyproject.toml   # must still be present

# Verify legacy behaviour (no --check-consistency) is unchanged:
cz bump --yes
grep 'version = "2.6.0"' pyproject.toml   # commitizen line bumped
grep 'version = "2.5.7"' pyproject.toml   # poetry line untouched — expected

Additional Context

This fix was scoped during the issue audit in #1964, which confirmed the silent-pass behaviour is still present in master (v4.15.1). The implementation deliberately stays within the existing regex-based version_files contract rather than introducing format-specific parsing, consistent with the design discussion in the #595 thread (@woile, @Lee-W). Users whose version_files regex is already specific enough to target only the commitizen-managed line are unaffected — their _LIKELY_VERSION_VALUE_RE scan will find no inconsistent lines.

When `version_files` regex matches multiple version-shaped values in
the same file (the typical pyproject.toml pattern with both
`[tool.poetry].version` / `[project].version` and
`[tool.commitizen].version`), the previous behaviour silently rewrote
only the line containing the current version and left the sibling
out-of-sync.

Under `--check-consistency`, also fail when a matching line holds a
different version-shaped value. The error names the offending line so
users can either align the sources, narrow the `version_files` regex
or drop `--check-consistency`.

The default (`check_consistency = False`) is unchanged: only lines
containing `current_version` are rewritten, others are passed through.

Closes commitizen-tools#595

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.24%. Comparing base (4b93a50) to head (ceeefb0).
⚠️ Report is 3 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #1978   +/-   ##
=======================================
  Coverage   98.23%   98.24%           
=======================================
  Files          61       61           
  Lines        2779     2790   +11     
=======================================
+ Hits         2730     2741   +11     
  Misses         49       49           

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

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

This PR tightens cz bump --check-consistency by detecting “sibling” version values in the same file when a broad version_files regex matches multiple version-like assignments (e.g., pyproject.toml with both [tool.poetry].version and [tool.commitizen].version). It aims to prevent silent version mismatches by raising an actionable error that points to the offending line(s).

Changes:

  • Refactors update_version_in_files to enumerate lines and collect regex-matching lines that appear to contain a different version (only used when check_consistency=True).
  • Adds a conservative _LIKELY_VERSION_VALUE_RE to identify version-shaped values for the sibling mismatch detection.
  • Adds regression tests covering both the new --check-consistency failure behavior and the legacy non-consistency behavior.

Reviewed changes

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

File Description
commitizen/bump.py Adds sibling-version mismatch detection and enhanced consistency error messaging during version file updates.
tests/test_bump_update_version_in_files.py Adds regression tests for sibling mismatch detection and confirms legacy behavior when consistency checking is off.

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

Comment thread commitizen/bump.py
Comment thread commitizen/bump.py Outdated
… preserve trailing whitespace

* skip _LIKELY_VERSION_VALUE_RE.search and inconsistent_lines tracking when check_consistency=False, so the legacy fast path stays unchanged as the PR claimed

* use rstrip('\r\n') instead of rstrip() so the reported line content matches the actual file content (preserves meaningful trailing whitespace)

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.

Discussion on check-consistency

2 participants