Skip to content

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

Open
bearomorphism wants to merge 2 commits into
commitizen-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 into
commitizen-tools:masterfrom
bearomorphism:fix/595-check-consistency-version-provider

Conversation

@bearomorphism

@bearomorphism bearomorphism commented May 9, 2026

Copy link
Copy Markdown
Collaborator

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

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.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.

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

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