From c0ca27dd0f6c5c069d8a1cf80d4dcb2750c5802a Mon Sep 17 00:00:00 2001 From: vemilano <230173219+vemilano@users.noreply.github.com> Date: Mon, 15 Jun 2026 16:38:14 +0200 Subject: [PATCH 1/2] fix: workspace member with fixed version key --- commitizen/providers/cargo_provider.py | 13 ++--- tests/providers/test_cargo_provider.py | 71 ++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 6 deletions(-) diff --git a/commitizen/providers/cargo_provider.py b/commitizen/providers/cargo_provider.py index 235d4a110..4b7892320 100644 --- a/commitizen/providers/cargo_provider.py +++ b/commitizen/providers/cargo_provider.py @@ -83,12 +83,13 @@ def set_lock_version(self, version: str) -> None: 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) + if not isinstance(package_content["version"], str): + 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 diff --git a/tests/providers/test_cargo_provider.py b/tests/providers/test_cargo_provider.py index 63f143b29..298727c83 100644 --- a/tests/providers/test_cargo_provider.py +++ b/tests/providers/test_cargo_provider.py @@ -378,6 +378,77 @@ def test_cargo_provider_workspace_member_without_version_key( assert lock_file.read_text() == dedent(expected_lock_content) +def test_cargo_provider_workspace_member_with_fixed_version_key( + config: BaseConfig, + chdir: Path, +): + """Test workspace member that has fixed version key (should not crash).""" + workspace_toml = """\ +[workspace] +members = ["member_with_fixed_version"] + +[workspace.package] +version = "0.1.0" +""" + + # Create a member that has fixed version key + member_content = """\ +[package] +name = "member_with_fixed_version" +version = "1.0.0" +""" + + lock_content = """\ +[[package]] +name = "member_with_fixed_version" +version = "1.0.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" +""" + + expected_lock_content = """\ +[[package]] +name = "member_with_fixed_version" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "123abc" +""" + + # Create the workspace file + filename = CargoProvider.filename + file = chdir / filename + file.write_text(dedent(workspace_toml)) + + # Create the member directory and file + os.mkdir(chdir / "member_with_fixed_version") + member_file = chdir / "member_with_fixed_version" / "Cargo.toml" + member_file.write_text(dedent(member_content)) + + # Create the lock file + 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" + + # This should not crash even though the member has fixed version key + provider.set_version("42.1") + assert file.read_text() == dedent(expected_workspace_toml) + assert lock_file.read_text() == dedent(expected_lock_content) + + def test_cargo_provider_workspace_member_without_workspace_key( config: BaseConfig, chdir: Path, From 9277972720a95f1b9bb3945fe9986487e271f47b Mon Sep 17 00:00:00 2001 From: vemilano <230173219+vemilano@users.noreply.github.com> Date: Fri, 19 Jun 2026 15:09:54 +0200 Subject: [PATCH 2/2] feat: inheriting workspace members with path, package and version attributes --- commitizen/providers/cargo_provider.py | 85 ++++++---- tests/providers/test_cargo_provider.py | 223 +++++++++++++++++++++++++ 2 files changed, 277 insertions(+), 31 deletions(-) diff --git a/commitizen/providers/cargo_provider.py b/commitizen/providers/cargo_provider.py index 4b7892320..98485e82c 100644 --- a/commitizen/providers/cargo_provider.py +++ b/commitizen/providers/cargo_provider.py @@ -37,6 +37,24 @@ def get(self, document: TOMLDocument) -> str: def set(self, document: TOMLDocument, version: str) -> None: _try_get_workspace(document)["package"]["version"] = version + if document.get("workspace"): + # get all workspace members that have a version.workspace = true + members_inheriting = _get_workspace_members(document, self._get_encoding()) + + # get all workspace dependencies that have a version specified and match a member inheriting + workspace_deps = document["workspace"].get("dependencies", {}) + for dep_name, dep_value in workspace_deps.items(): + if isinstance(dep_value, str) and (dep_name in members_inheriting): + workspace_deps[dep_name] = version + elif (isinstance(dep_value, dict) and dep_value.get("version", [])): + if dep_value.get("path", "") and dep_value.get("package", ""): + crate_name = dep_value["package"] + else: + crate_name = dep_name + + if crate_name in members_inheriting: + dep_value["version"] = version + def set_version(self, version: str) -> None: super().set_version(version) if self.lock_file.is_file(): @@ -61,37 +79,7 @@ def set_lock_version(self, version: str) -> None: cargo_lock_content["package"][i]["version"] = version # type: ignore[index] break except NonExistentKey: - workspace = cargo_toml_content.get("workspace", {}) - if TYPE_CHECKING: - assert isinstance(workspace, dict) - workspace_members = workspace.get("members", []) - excluded_workspace_members = workspace.get("exclude", []) - members_inheriting: list[str] = [] - - for member in workspace_members: - for path in glob.glob(member, recursive=True): - if any( - fnmatch.fnmatch(path, pattern) - for pattern in excluded_workspace_members - ): - continue - - cargo_file = Path(path) / "Cargo.toml" - package_content = parse( - cargo_file.read_text(encoding=self._get_encoding()) - ).get("package", {}) - if TYPE_CHECKING: - assert isinstance(package_content, dict) - try: - if not isinstance(package_content["version"], str): - 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 + members_inheriting = _get_workspace_members(cargo_toml_content, self._get_encoding()) for i, package in enumerate(packages): if package["name"] in members_inheriting: @@ -110,3 +98,38 @@ def _try_get_workspace(document: TOMLDocument) -> dict: return workspace except NonExistentKey: return document + +def _get_workspace_members(cargo_toml_content: TOMLDocument, encoding: str | None) -> list[str]: + workspace = cargo_toml_content.get("workspace", {}) + if TYPE_CHECKING: + assert isinstance(workspace, dict) + workspace_members = workspace.get("members", []) + excluded_workspace_members = workspace.get("exclude", []) + members_inheriting: list[str] = [] + + for member in workspace_members: + for path in glob.glob(member, recursive=True): + if any( + fnmatch.fnmatch(path, pattern) + for pattern in excluded_workspace_members + ): + continue + + cargo_file = Path(path) / "Cargo.toml" + package_content = parse( + cargo_file.read_text(encoding=encoding) + ).get("package", {}) + if TYPE_CHECKING: + assert isinstance(package_content, dict) + try: + if not isinstance(package_content["version"], str): + 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 + + return members_inheriting \ No newline at end of file diff --git a/tests/providers/test_cargo_provider.py b/tests/providers/test_cargo_provider.py index 298727c83..5df5d7dc2 100644 --- a/tests/providers/test_cargo_provider.py +++ b/tests/providers/test_cargo_provider.py @@ -522,3 +522,226 @@ 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_inheriting_workspace_member_with_version_in_toml( + config: BaseConfig, + chdir: Path, +): + """Test workspace member that has version key but no workspace subkey.""" + workspace_toml = """\ +[workspace] +members = ["inheriting_member"] + +[workspace.package] +version = "0.1.0" + +[workspace.dependencies] +inheriting_member = "0.1.0" +""" + + member_content = """\ +[package] +name = "inheriting_member" +version.workspace = true +""" + + lock_content = """\ +[[package]] +name = "inheriting_member" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "123abc" +""" + + expected_workspace_toml = """\ +[workspace] +members = ["inheriting_member"] + +[workspace.package] +version = "42.1" + +[workspace.dependencies] +inheriting_member = "42.1" +""" + + expected_lock_content = """\ +[[package]] +name = "inheriting_member" +version = "42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "123abc" +""" + + # Create the workspace file + filename = CargoProvider.filename + file = chdir / filename + file.write_text(dedent(workspace_toml)) + + # Create the member directory and file + os.mkdir(chdir / "inheriting_member") + member_file = chdir / "inheriting_member" / "Cargo.toml" + member_file.write_text(dedent(member_content)) + + # Create the lock file + 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" + + provider.set_version("42.1") + assert file.read_text() == dedent(expected_workspace_toml) + assert lock_file.read_text() == dedent(expected_lock_content) + +def test_cargo_provider_inheriting_workspace_member_with_version_dict_in_toml( + config: BaseConfig, + chdir: Path, +): + """Test workspace member that has version key but no workspace subkey.""" + workspace_toml = """\ +[workspace] +members = ["inheriting_member"] + +[workspace.package] +version = "0.1.0" + +[workspace.dependencies] +inheriting_member = { version = "0.1.0" } +""" + + member_content = """\ +[package] +name = "inheriting_member" +version.workspace = true +""" + + lock_content = """\ +[[package]] +name = "inheriting_member" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "123abc" +""" + + expected_workspace_toml = """\ +[workspace] +members = ["inheriting_member"] + +[workspace.package] +version = "42.1" + +[workspace.dependencies] +inheriting_member = { version = "42.1" } +""" + + expected_lock_content = """\ +[[package]] +name = "inheriting_member" +version = "42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "123abc" +""" + + # Create the workspace file + filename = CargoProvider.filename + file = chdir / filename + file.write_text(dedent(workspace_toml)) + + # Create the member directory and file + os.mkdir(chdir / "inheriting_member") + member_file = chdir / "inheriting_member" / "Cargo.toml" + member_file.write_text(dedent(member_content)) + + # Create the lock file + 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" + + provider.set_version("42.1") + assert file.read_text() == dedent(expected_workspace_toml) + assert lock_file.read_text() == dedent(expected_lock_content) + +def test_cargo_provider_inheriting_workspace_member_with_version_and_package_in_toml( + config: BaseConfig, + chdir: Path, +): + """Test workspace member that has version key but no workspace subkey.""" + workspace_toml = """\ +[workspace] +members = ["mypath/original_name"] + +[workspace.package] +version = "0.1.0" + +[workspace.dependencies] +renamed_inheriting_member = { version = "0.1.0", path = "mypath/original_name", package = "original_name" } +""" + + member_content = """\ +[package] +name = "original_name" +version.workspace = true +""" + + lock_content = """\ +[[package]] +name = "original_name" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "123abc" +""" + + expected_workspace_toml = """\ +[workspace] +members = ["mypath/original_name"] + +[workspace.package] +version = "42.1" + +[workspace.dependencies] +renamed_inheriting_member = { version = "42.1", path = "mypath/original_name", package = "original_name" } +""" + + expected_lock_content = """\ +[[package]] +name = "original_name" +version = "42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "123abc" +""" + + # Create the workspace file + filename = CargoProvider.filename + file = chdir / filename + file.write_text(dedent(workspace_toml)) + + # Create the member directory and file + os.makedirs(chdir / "mypath/original_name") + member_file = chdir / "mypath/original_name" / "Cargo.toml" + member_file.write_text(dedent(member_content)) + + # Create the lock file + 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" + + provider.set_version("42.1") + assert file.read_text() == dedent(expected_workspace_toml) + assert lock_file.read_text() == dedent(expected_lock_content) \ No newline at end of file