From 4ff25e30ba4344c84fdff2e3304fdd4fa3048536 Mon Sep 17 00:00:00 2001 From: Tim Hsiung <26526132+bearomorphism@users.noreply.github.com> Date: Mon, 25 May 2026 21:38:14 +0800 Subject: [PATCH] fix(providers/cargo): don't crash on workspace member with fixed version When iterating workspace members in `CargoProvider.set_lock_version`, the code subscripted `package["version"]["workspace"]` inside a try/except block that only caught `NonExistentKey`. If a member's `Cargo.toml` declared a hardcoded version (`version = "x.y.z"`) instead of `version.workspace = true`, `package["version"]` was a tomlkit `String` and the subscript raised `TypeError: string indices must be integers, not 'str'`, which was not caught and propagated as a crash. Replace the exception-driven check with an `isinstance` type guard so the inheritance lookup only runs when `version` is an actual table. Fixes #2001 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- commitizen/providers/cargo_provider.py | 18 +++--- tests/providers/test_cargo_provider.py | 77 ++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 9 deletions(-) diff --git a/commitizen/providers/cargo_provider.py b/commitizen/providers/cargo_provider.py index 235d4a110..5152bc74c 100644 --- a/commitizen/providers/cargo_provider.py +++ b/commitizen/providers/cargo_provider.py @@ -82,15 +82,15 @@ def set_lock_version(self, version: str) -> None: ).get("package", {}) if TYPE_CHECKING: assert isinstance(package_content, dict) - try: - version_workspace = package_content["version"]["workspace"] - if version_workspace is True: - package_name = package_content["name"] - if TYPE_CHECKING: - assert isinstance(package_name, str) - members_inheriting.append(package_name) - except NonExistentKey: - pass + version_field = package_content.get("version") + if ( + isinstance(version_field, dict) + and version_field.get("workspace") is True + ): + package_name = package_content["name"] + if TYPE_CHECKING: + assert isinstance(package_name, str) + members_inheriting.append(package_name) for i, package in enumerate(packages): if package["name"] in members_inheriting: diff --git a/tests/providers/test_cargo_provider.py b/tests/providers/test_cargo_provider.py index 63f143b29..023e34b21 100644 --- a/tests/providers/test_cargo_provider.py +++ b/tests/providers/test_cargo_provider.py @@ -451,3 +451,80 @@ def test_cargo_provider_workspace_member_without_workspace_key( assert file.read_text() == dedent(expected_workspace_toml) # The lock file should remain unchanged since the member doesn't inherit workspace version assert lock_file.read_text() == dedent(expected_lock_content) + + +def test_cargo_provider_workspace_member_with_fixed_version( + config: BaseConfig, + chdir: Path, +): + """Regression test for https://github.com/commitizen-tools/commitizen/issues/2001. + + A workspace member with a hardcoded ``version = "x.y.z"`` string (rather + than ``version.workspace = true``) used to crash ``set_lock_version`` with + ``TypeError: string indices must be integers, not 'str'`` because the code + unconditionally subscripted ``package["version"]["workspace"]``. + """ + workspace_toml = """\ +[workspace] +members = ["member_with_fixed_version"] + +[workspace.package] +version = "0.1.0" +""" + + # Real Cargo syntax for a workspace member that opts out of the + # workspace-inherited version by pinning its own. + member_content = """\ +[package] +name = "member_with_fixed_version" +version = "0.9.0" +""" + + lock_content = """\ +[[package]] +name = "member_with_fixed_version" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "123abc" +""" + + expected_workspace_toml = """\ +[workspace] +members = ["member_with_fixed_version"] + +[workspace.package] +version = "42.1" +""" + + # The pinned member must not be bumped because it does not inherit the + # workspace version. + expected_lock_content = """\ +[[package]] +name = "member_with_fixed_version" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "123abc" +""" + + filename = CargoProvider.filename + file = chdir / filename + file.write_text(dedent(workspace_toml)) + + os.mkdir(chdir / "member_with_fixed_version") + member_file = chdir / "member_with_fixed_version" / "Cargo.toml" + member_file.write_text(dedent(member_content)) + + lock_filename = CargoProvider.lock_filename + lock_file = chdir / lock_filename + lock_file.write_text(dedent(lock_content)) + + config.settings["version_provider"] = "cargo" + + provider = get_provider(config) + assert isinstance(provider, CargoProvider) + assert provider.get_version() == "0.1.0" + + # Must not raise TypeError: string indices must be integers, not 'str'. + provider.set_version("42.1") + assert file.read_text() == dedent(expected_workspace_toml) + assert lock_file.read_text() == dedent(expected_lock_content)