fix(bump): flag sibling version mismatches under --check-consistency#1978
fix(bump): flag sibling version mismatches under --check-consistency#1978bearomorphism wants to merge 2 commits intocommitizen-tools:masterfrom
Conversation
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 Report✅ All modified and coverable lines are covered by tests. 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. |
There was a problem hiding this comment.
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_filesto enumerate lines and collect regex-matching lines that appear to contain a different version (only used whencheck_consistency=True). - Adds a conservative
_LIKELY_VERSION_VALUE_REto identify version-shaped values for the sibling mismatch detection. - Adds regression tests covering both the new
--check-consistencyfailure 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.
… 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>
Description
Closes #595.
Why
commitizen/bump.py:update_version_in_filesapplies a regex pattern to each line of every file inversion_files. When a broad pattern such aspyproject.toml:versionis used, the regex legitimately matches multipleversion =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 (mastercommitizen/bump.py:88-95) replaced thecurrent_versionstring whereverpattern.search(line)was true and setcurrent_version_foundonly when the replacement changed the line. The loop never inspected lines that matched the pattern but did not containcurrent_version, so the[tool.poetry].versionline was silently skipped — the bump proceeded,--check-consistencyraised 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"runningcz bump --check-consistency --yesexits 0, bumps commitizen's version to2.6.0, and leaves the poetry version at2.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 containcurrent_versionbut (c) contains a semver-shaped value is collected as aninconsistent_linesentry. If any such entries exist after processing the file,CurrentVersionNotFoundErroris 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
commitizen/bump.pyinconsistent_lines; add_LIKELY_VERSION_VALUE_REmodule-level constant; raiseCurrentVersionNotFoundErrorwith line-level details whencheck_consistency=Trueand sibling mismatches are foundtests/test_bump_update_version_in_files.pycheck_consistencybehaviour leaves sibling untouched without raisingHow 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_versionwas 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_REis intentionally conservative. The regex\d+\.\d+\.\d+(?:[\w.\-+]*)matches the canonicalMAJOR.MINOR.PATCHsemver shape (plus optional pre-release and build-metadata suffixes). It deliberately does not match bare integers, Python import paths, comment prose, or genericversion =keywords without a version-shaped right-hand side. This prevents false positives from unrelated occurrences of the wordversionin 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 andgrep -nreport. This makes the error message actionable without requiring the user to open the file and count from zero.check_consistency=Falsepath is byte-for-byte identical to the old behaviour. Theinconsistent_lineslist is only evaluated inside theif check_consistencyblock. 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-filecontract. The regex approach is deliberately format-agnostic; the conservative_LIKELY_VERSION_VALUE_REsecondary filter is enough to catch the commonpyproject.tomlcase without committing to TOML semantics.Backward compatibility
check_consistency=False(the default) preserves the legacy behaviour exactly: only lines containingcurrent_versionare rewritten; sibling lines are left alone; no exception is raised.CurrentVersionNotFoundErrormessage 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.update_version_in_filestests and bump command tests pass unchanged.--check-consistencyis the existing flag whose scope is extended.Checklist
Was generative AI tooling used to co-author this PR?
Generated-by: Claude following the guidelines
Code Changes
uv run poe alllocally to ensure this change passes linter check and testsExpected Behavior
pyproject.tomlwith[tool.poetry].version = "2.5.7"and[tool.commitizen].version = "2.5.2",version_files = ["pyproject.toml:version"],cz bump --check-consistencyline 2: version = "2.5.7"as the inconsistent entry; file is not modifiedcz bump(no--check-consistency)2.5.7; no exception — legacy behaviour preservedversion =line in file,cz bump --check-consistencyinconsistent_linesis empty, no new errorversion_filesregex narrowed topyproject.toml:\[tool\.commitizen\]inconsistent_linesis empty regardless of--check-consistencySteps to Test This Pull Request
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_filescontract rather than introducing format-specific parsing, consistent with the design discussion in the #595 thread (@woile, @Lee-W). Users whoseversion_filesregex is already specific enough to target only the commitizen-managed line are unaffected — their_LIKELY_VERSION_VALUE_REscan will find no inconsistent lines.