Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions commitizen/config/base_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from typing import TYPE_CHECKING

from commitizen.defaults import DEFAULT_SETTINGS, Settings
from commitizen.exceptions import InvalidConfigurationError

if TYPE_CHECKING:
import sys
Expand All @@ -14,6 +15,8 @@
else:
from typing import Self

_VALID_CONFIG_KEYS: frozenset[str] = frozenset(Settings.__annotations__)


class BaseConfig:
def __init__(self) -> None:
Expand Down Expand Up @@ -47,6 +50,12 @@ def set_key(self, key: str, value: object) -> Self:
raise NotImplementedError()

def update(self, data: Settings) -> None:
if self._settings.get("strict_config") or data.get("strict_config"):
unknown = sorted(set(data) - _VALID_CONFIG_KEYS)
if unknown:
raise InvalidConfigurationError(
f"Unknown commitizen config keys: {', '.join(unknown)}"
)
self._settings.update(data)

def _parse_setting(self, data: bytes | str) -> None:
Expand Down
2 changes: 1 addition & 1 deletion commitizen/config/json_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,6 @@ def _parse_setting(self, data: bytes | str) -> None:
raise InvalidConfigurationError(f"Failed to parse {self.path}: {e}")

try:
self.settings.update(doc["commitizen"])
self.update(doc["commitizen"])
except KeyError:
pass
2 changes: 1 addition & 1 deletion commitizen/config/toml_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,6 @@ def _parse_setting(self, data: bytes | str) -> None:
raise InvalidConfigurationError(f"Failed to parse {self.path}: {e}")

try:
self.settings.update(doc["tool"]["commitizen"]) # type: ignore[index,typeddict-item] # TODO: fix this
self.update(doc["tool"]["commitizen"]) # type: ignore[index,arg-type]
except exceptions.NonExistentKey:
pass
2 changes: 1 addition & 1 deletion commitizen/config/yaml_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def _parse_setting(self, data: bytes | str) -> None:
raise InvalidConfigurationError(f"Failed to parse {self.path}: {e}")

try:
self.settings.update(doc["commitizen"])
self.update(doc["commitizen"])
except (KeyError, TypeError):
pass

Expand Down
2 changes: 2 additions & 0 deletions commitizen/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ class Settings(TypedDict, total=False):
version_type: str | None
version: str | None
breaking_change_exclamation_in_title: bool
strict_config: bool


CONFIG_FILES: tuple[str, ...] = (
Expand Down Expand Up @@ -115,6 +116,7 @@ class Settings(TypedDict, total=False):
"extras": {},
"breaking_change_exclamation_in_title": False,
"message_length_limit": 0, # 0 for no limit
"strict_config": False,
}

MAJOR = "MAJOR"
Expand Down
64 changes: 64 additions & 0 deletions tests/test_conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@
"extras": {},
"breaking_change_exclamation_in_title": False,
"message_length_limit": 0,
"strict_config": False,
}

_new_settings: dict[str, Any] = {
Expand Down Expand Up @@ -152,6 +153,7 @@
"extras": {},
"breaking_change_exclamation_in_title": False,
"message_length_limit": 0,
"strict_config": False,
}


Expand Down Expand Up @@ -497,3 +499,65 @@ def test_init_with_invalid_content(self, tmp_path, config_file):
with pytest.raises(InvalidConfigurationError) as excinfo:
YAMLConfig(data=existing_content, path=path)
assert config_file in str(excinfo.value)


class TestStrictConfig:
"""Tests for the strict_config option that rejects unknown config keys."""

def test_toml_strict_config_raises_on_unknown_key(self, tmp_path):
data = """
[tool.commitizen]
strict_config = true
tga_format = "v$version"
"""
path = tmp_path / "pyproject.toml"
with pytest.raises(InvalidConfigurationError, match="tga_format"):
TomlConfig(data=data, path=path)

def test_json_strict_config_raises_on_unknown_key(self, tmp_path):
data = json.dumps(
{"commitizen": {"strict_config": True, "tga_format": "v$version"}}
)
path = tmp_path / ".cz.json"
with pytest.raises(InvalidConfigurationError, match="tga_format"):
JsonConfig(data=data, path=path)

def test_yaml_strict_config_raises_on_unknown_key(self, tmp_path):
data = "commitizen:\n strict_config: true\n tga_format: v$version\n"
path = tmp_path / ".cz.yaml"
with pytest.raises(InvalidConfigurationError, match="tga_format"):
YAMLConfig(data=data, path=path)

def test_strict_config_off_by_default_ignores_unknown_key(self, tmp_path):
data = """
[tool.commitizen]
tga_format = "v$version"
"""
path = tmp_path / "pyproject.toml"
cfg = TomlConfig(data=data, path=path)
assert cfg.settings.get("strict_config") is False

def test_strict_config_allows_all_known_keys(self, tmp_path):
data = """
[tool.commitizen]
strict_config = true
name = "cz_conventional_commits"
tag_format = "v$version"
"""
path = tmp_path / "pyproject.toml"
cfg = TomlConfig(data=data, path=path)
assert cfg.settings["strict_config"] is True
assert cfg.settings["tag_format"] == "v$version"

def test_strict_config_error_lists_all_unknown_keys(self, tmp_path):
data = """
[tool.commitizen]
strict_config = true
typo_one = "foo"
typo_two = "bar"
"""
path = tmp_path / "pyproject.toml"
with pytest.raises(InvalidConfigurationError) as excinfo:
TomlConfig(data=data, path=path)
assert "typo_one" in str(excinfo.value)
assert "typo_two" in str(excinfo.value)
Loading