From b08899303dcdc1e3fcdc512129549da894783ec5 Mon Sep 17 00:00:00 2001 From: Tim Hsiung Date: Sat, 9 May 2026 19:57:17 +0800 Subject: [PATCH 1/2] fix(bump): flag sibling version mismatches under --check-consistency 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 #595 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- commitizen/bump.py | 43 +++++++++++++---- tests/test_bump_update_version_in_files.py | 55 ++++++++++++++++++++++ 2 files changed, 90 insertions(+), 8 deletions(-) diff --git a/commitizen/bump.py b/commitizen/bump.py index 030c8f1e5..6d2fca780 100644 --- a/commitizen/bump.py +++ b/commitizen/bump.py @@ -82,17 +82,27 @@ def update_version_in_files( for path, pattern in _resolve_files_and_regexes(version_files, current_version): current_version_found = False + inconsistent_lines: list[tuple[int, str]] = [] bumped_lines = [] with open(path, encoding=encoding) as version_file: - for line in version_file: - bumped_line = ( - line.replace(current_version, new_version) - if pattern.search(line) - else line - ) - - current_version_found = current_version_found or bumped_line != line + for lineno, line in enumerate(version_file, 1): + if pattern.search(line): + if current_version in line: + bumped_line = line.replace(current_version, new_version) + current_version_found = True + else: + # The version-files regex matched this line, but the + # current version isn't on it. If the line looks like + # it sets a version-shaped value, this is almost + # certainly an inconsistent source we'd otherwise miss + # silently (#595). + bumped_line = line + if _LIKELY_VERSION_VALUE_RE.search(line): + inconsistent_lines.append((lineno, line.rstrip())) + else: + bumped_line = line + bumped_lines.append(bumped_line) if check_consistency and not current_version_found: @@ -102,6 +112,17 @@ def update_version_in_files( "version_files are possibly inconsistent." ) + if check_consistency and inconsistent_lines: + details = "\n".join(f" line {n}: {text}" for n, text in inconsistent_lines) + raise CurrentVersionNotFoundError( + f"Found line(s) in {path} matching the version regex but " + f"holding a version other than {current_version}:\n" + f"{details}\n" + "This usually means another tool (e.g. poetry, pep621) is " + "tracking a different version. Either align them, narrow the " + "`version_files` regex, or drop `--check-consistency`." + ) + bumped_version_file_content = "".join(bumped_lines) # Write the file out again @@ -112,6 +133,12 @@ def update_version_in_files( return updated_files +# Lines that look like ``key = "1.2.3"`` / ``key: 1.2.3-rc.0`` etc. -- enough +# to catch the typical pyproject.toml ``[tool.poetry].version = "..."`` and +# ``[project].version = "..."`` cases handled by ``--check-consistency``. +_LIKELY_VERSION_VALUE_RE = re.compile(r"\d+\.\d+\.\d+(?:[\w.\-+]*)") + + def _resolve_files_and_regexes( patterns: Iterable[str], version: str ) -> Generator[tuple[str, re.Pattern], None, None]: diff --git a/tests/test_bump_update_version_in_files.py b/tests/test_bump_update_version_in_files.py index 80823a4e1..f25f21ce1 100644 --- a/tests/test_bump_update_version_in_files.py +++ b/tests/test_bump_update_version_in_files.py @@ -302,6 +302,61 @@ def test_update_version_in_files_with_check_consistency_true_failure( assert expected_msg in str(excinfo.value) +def test_update_version_in_files_check_consistency_detects_sibling_version(tmp_path): + """Regression test for #595: when a single file's version regex matches + multiple version-shaped values (typical pyproject.toml with + ``[tool.poetry].version`` and ``[tool.commitizen].version``), + ``--check-consistency`` should flag the lines whose version doesn't match + the current one instead of silently leaving the sibling out of date. + """ + pyproject = tmp_path / "pyproject.toml" + pyproject.write_text( + '[tool.poetry]\nversion = "2.5.7"\n\n[tool.commitizen]\nversion = "2.5.2"\n', + encoding="utf-8", + ) + + with pytest.raises(CurrentVersionNotFoundError) as excinfo: + bump.update_version_in_files( + current_version="2.5.2", + new_version="2.6.0", + version_files=[f"{pyproject}:version"], + check_consistency=True, + encoding="utf-8", + ) + + msg = str(excinfo.value) + assert "2.5.7" in msg + assert "2.5.2" in msg + # The original file must NOT have been rewritten when consistency fails. + # (We re-read it to confirm the bump didn't proceed.) + assert '"2.5.2"' in pyproject.read_text(encoding="utf-8") + + +def test_update_version_in_files_check_consistency_off_keeps_legacy_behaviour( + tmp_path, +): + """Without ``check_consistency``, the old behaviour is preserved: only + the lines that contain the current version are updated, and sibling + versions are left alone (no exception).""" + pyproject = tmp_path / "pyproject.toml" + pyproject.write_text( + '[tool.poetry]\nversion = "2.5.7"\n\n[tool.commitizen]\nversion = "2.5.2"\n', + encoding="utf-8", + ) + + bump.update_version_in_files( + current_version="2.5.2", + new_version="2.6.0", + version_files=[f"{pyproject}:version"], + check_consistency=False, + encoding="utf-8", + ) + + rewritten = pyproject.read_text(encoding="utf-8") + assert '[tool.poetry]\nversion = "2.5.7"' in rewritten + assert '[tool.commitizen]\nversion = "2.6.0"' in rewritten + + @pytest.mark.parametrize( ("encoding", "filename"), [ From ceeefb012b173ed619a9b80c16d44273a3a65eb4 Mon Sep 17 00:00:00 2001 From: Tim Hsiung <26526132+bearomorphism@users.noreply.github.com> Date: Sat, 9 May 2026 22:26:22 +0800 Subject: [PATCH 2/2] fix(bump): guard sibling-mismatch detection behind check_consistency, 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> --- commitizen/bump.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/commitizen/bump.py b/commitizen/bump.py index 6d2fca780..b9e4784ab 100644 --- a/commitizen/bump.py +++ b/commitizen/bump.py @@ -98,8 +98,8 @@ def update_version_in_files( # certainly an inconsistent source we'd otherwise miss # silently (#595). bumped_line = line - if _LIKELY_VERSION_VALUE_RE.search(line): - inconsistent_lines.append((lineno, line.rstrip())) + if check_consistency and _LIKELY_VERSION_VALUE_RE.search(line): + inconsistent_lines.append((lineno, line.rstrip("\r\n"))) else: bumped_line = line