Skip to content

Commit c03d500

Browse files
committed
feat(config): add warning for multiple configuration files
Detects when multiple configuration files exist (excluding pyproject.toml) and displays a warning message identifying which file is being used. Closes #1771
1 parent 7327ae1 commit c03d500

File tree

3 files changed

+134
-1
lines changed

3 files changed

+134
-1
lines changed

commitizen/config/__init__.py

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from pathlib import Path
44
from typing import TYPE_CHECKING
55

6-
from commitizen import defaults, git
6+
from commitizen import defaults, git, out
77
from commitizen.config.factory import create_config
88
from commitizen.exceptions import ConfigFileIsEmpty, ConfigFileNotFound
99

@@ -34,7 +34,53 @@ def _resolve_config_paths(filepath: str | None = None) -> Generator[Path, None,
3434
yield out_path
3535

3636

37+
def _check_and_warn_multiple_configs(filepath: str | None = None) -> None:
38+
"""Check if multiple config files exist and warn the user."""
39+
if filepath is not None:
40+
# If user explicitly specified a config file, no need to warn
41+
return
42+
43+
git_project_root = git.find_git_project_root()
44+
cfg_search_paths = [Path(".")]
45+
if git_project_root:
46+
cfg_search_paths.append(git_project_root)
47+
48+
for path in cfg_search_paths:
49+
# Find all existing config files (excluding pyproject.toml for clearer warning)
50+
existing_files = [
51+
filename
52+
for filename in defaults.CONFIG_FILES
53+
if filename != "pyproject.toml" and (path / filename).exists()
54+
]
55+
56+
# If more than one config file exists, warn the user
57+
if len(existing_files) > 1:
58+
# Find which one will be used (first non-empty one in the priority order)
59+
used_config = None
60+
for filename in defaults.CONFIG_FILES:
61+
config_path = path / filename
62+
if config_path.exists():
63+
try:
64+
with open(config_path, "rb") as f:
65+
data = f.read()
66+
conf = create_config(data=data, path=config_path)
67+
if not conf.is_empty_config:
68+
used_config = filename
69+
break
70+
except Exception:
71+
continue
72+
73+
if used_config:
74+
out.warn(
75+
f"Multiple config files detected: {', '.join(existing_files)}. "
76+
f"Using {used_config}."
77+
)
78+
break
79+
80+
3781
def read_cfg(filepath: str | None = None) -> BaseConfig:
82+
_check_and_warn_multiple_configs(filepath)
83+
3884
for filename in _resolve_config_paths(filepath):
3985
with open(filename, "rb") as f:
4086
data: bytes = f.read()

docs/config/configuration_file.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ The first valid configuration file found will be used. If no configuration file
2323
!!! tip
2424
For Python projects, it's recommended to add your Commitizen configuration to `pyproject.toml` to keep all project configuration in one place.
2525

26+
!!! warning "Multiple Configuration Files"
27+
If Commitizen detects more than one configuration file in your project directory (excluding `pyproject.toml`), it will display a warning message and identify which file is being used. To avoid confusion, ensure you have only one Commitizen configuration file in your project.
28+
2629
## Supported Formats
2730

2831
Commitizen supports three configuration file formats:

tests/test_conf.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,90 @@ def test_load_empty_pyproject_toml_from_config_argument(_, tmpdir):
239239
with pytest.raises(ConfigFileIsEmpty):
240240
config.read_cfg(filepath="./not_in_root/pyproject.toml")
241241

242+
def test_warn_multiple_config_files(_, tmpdir, capsys):
243+
"""Test that a warning is issued when multiple config files exist."""
244+
with tmpdir.as_cwd():
245+
# Create multiple config files
246+
tmpdir.join(".cz.toml").write(PYPROJECT)
247+
tmpdir.join(".cz.json").write(JSON_STR)
248+
249+
# Read config
250+
cfg = config.read_cfg()
251+
252+
# Check that the warning was issued
253+
captured = capsys.readouterr()
254+
assert "Multiple config files detected" in captured.err
255+
assert ".cz.toml" in captured.err
256+
assert ".cz.json" in captured.err
257+
assert "Using" in captured.err
258+
259+
# Verify the correct config is loaded (first in priority order)
260+
assert cfg.settings == _settings
261+
262+
def test_warn_multiple_config_files_with_pyproject(_, tmpdir, capsys):
263+
"""Test warning excludes pyproject.toml from the warning message."""
264+
with tmpdir.as_cwd():
265+
# Create multiple config files including pyproject.toml
266+
tmpdir.join("pyproject.toml").write(PYPROJECT)
267+
tmpdir.join(".cz.json").write(JSON_STR)
268+
269+
# Read config - should use pyproject.toml (first in priority)
270+
cfg = config.read_cfg()
271+
272+
# No warning should be issued as only one non-pyproject config exists
273+
captured = capsys.readouterr()
274+
assert "Multiple config files detected" not in captured.err
275+
276+
# Verify the correct config is loaded
277+
assert cfg.settings == _settings
278+
279+
def test_warn_multiple_config_files_uses_correct_one(_, tmpdir, capsys):
280+
"""Test that the correct config file is used when multiple exist."""
281+
with tmpdir.as_cwd():
282+
# Create .cz.json with different settings
283+
json_different = """
284+
{
285+
"commitizen": {
286+
"name": "cz_conventional_commits",
287+
"version": "2.0.0"
288+
}
289+
}
290+
"""
291+
tmpdir.join(".cz.json").write(json_different)
292+
tmpdir.join(".cz.toml").write(PYPROJECT)
293+
294+
# Read config - should use pyproject.toml (first in defaults.CONFIG_FILES)
295+
# But since pyproject.toml doesn't exist, .cz.toml is second in priority
296+
cfg = config.read_cfg()
297+
298+
# Check that warning mentions both files
299+
captured = capsys.readouterr()
300+
assert "Multiple config files detected" in captured.err
301+
assert ".cz.toml" in captured.err
302+
assert ".cz.json" in captured.err
303+
304+
# Verify .cz.toml was used (second in priority after pyproject.toml)
305+
assert cfg.settings["name"] == "cz_jira" # from PYPROJECT
306+
assert cfg.settings["version"] == "1.0.0"
307+
308+
def test_no_warn_with_explicit_config_path(_, tmpdir, capsys):
309+
"""Test that no warning is issued when user explicitly specifies config."""
310+
with tmpdir.as_cwd():
311+
# Create multiple config files
312+
tmpdir.join(".cz.toml").write(PYPROJECT)
313+
tmpdir.join(".cz.json").write(JSON_STR)
314+
315+
# Read config with explicit path
316+
cfg = config.read_cfg(filepath=".cz.json")
317+
318+
# No warning should be issued
319+
captured = capsys.readouterr()
320+
assert "Multiple config files detected" not in captured.err
321+
322+
# Verify the explicitly specified config is loaded (compare to expected JSON config)
323+
json_cfg_expected = JsonConfig(data=JSON_STR, path=Path(".cz.json"))
324+
assert cfg.settings == json_cfg_expected.settings
325+
242326

243327
@pytest.mark.parametrize(
244328
"config_file, exception_string",

0 commit comments

Comments
 (0)