Skip to content
Merged
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
17 changes: 15 additions & 2 deletions commitizen/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from pathlib import Path
from typing import TYPE_CHECKING

from commitizen import defaults, git
from commitizen import defaults, git, out
from commitizen.config.factory import create_config
from commitizen.exceptions import ConfigFileIsEmpty, ConfigFileNotFound

Expand Down Expand Up @@ -35,7 +35,20 @@ def _resolve_config_paths(filepath: str | None = None) -> Generator[Path, None,


def read_cfg(filepath: str | None = None) -> BaseConfig:
for filename in _resolve_config_paths(filepath):
config_candidates = list(_resolve_config_paths(filepath))

# Check for multiple config files and warn the user
config_candidates_exclude_pyproject = [
path for path in config_candidates if path.name != "pyproject.toml"
]
if len(config_candidates_exclude_pyproject) > 1:
filenames = [path.name for path in config_candidates_exclude_pyproject]
out.warn(
f"Multiple config files detected: {', '.join(filenames)}. "
f"Using config file: '{filenames[0]}'."
)

for filename in config_candidates:
with open(filename, "rb") as f:
data: bytes = f.read()

Expand Down
3 changes: 3 additions & 0 deletions docs/config/configuration_file.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ The first valid configuration file found will be used. If no configuration file
!!! tip
For Python projects, it's recommended to add your Commitizen configuration to `pyproject.toml` to keep all project configuration in one place.

!!! warning "Multiple Configuration Files"
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.

## Supported Formats

Commitizen supports three configuration file formats:
Expand Down
84 changes: 84 additions & 0 deletions tests/test_conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,90 @@ def test_load_empty_pyproject_toml_from_config_argument(_, tmpdir):
with pytest.raises(ConfigFileIsEmpty):
config.read_cfg(filepath="./not_in_root/pyproject.toml")

def test_warn_multiple_config_files(_, tmpdir, capsys):
"""Test that a warning is issued when multiple config files exist."""
with tmpdir.as_cwd():
# Create multiple config files
tmpdir.join(".cz.toml").write(PYPROJECT)
tmpdir.join(".cz.json").write(JSON_STR)

# Read config
cfg = config.read_cfg()

# Check that the warning was issued
captured = capsys.readouterr()
assert "Multiple config files detected" in captured.err
assert ".cz.toml" in captured.err
assert ".cz.json" in captured.err
assert "Using" in captured.err

# Verify the correct config is loaded (first in priority order)
assert cfg.settings == _settings

def test_warn_multiple_config_files_with_pyproject(_, tmpdir, capsys):
"""Test warning excludes pyproject.toml from the warning message."""
with tmpdir.as_cwd():
# Create multiple config files including pyproject.toml
tmpdir.join("pyproject.toml").write(PYPROJECT)
tmpdir.join(".cz.json").write(JSON_STR)

# Read config - should use pyproject.toml (first in priority)
cfg = config.read_cfg()

# No warning should be issued as only one non-pyproject config exists
captured = capsys.readouterr()
assert "Multiple config files detected" not in captured.err

# Verify the correct config is loaded
assert cfg.settings == _settings

def test_warn_multiple_config_files_uses_correct_one(_, tmpdir, capsys):
"""Test that the correct config file is used when multiple exist."""
with tmpdir.as_cwd():
# Create .cz.json with different settings
json_different = """
{
"commitizen": {
"name": "cz_conventional_commits",
"version": "2.0.0"
}
}
"""
tmpdir.join(".cz.json").write(json_different)
tmpdir.join(".cz.toml").write(PYPROJECT)

# Read config - should use pyproject.toml (first in defaults.CONFIG_FILES)
# But since pyproject.toml doesn't exist, .cz.toml is second in priority
cfg = config.read_cfg()

# Check that warning mentions both files
captured = capsys.readouterr()
assert "Multiple config files detected" in captured.err
assert ".cz.toml" in captured.err
assert ".cz.json" in captured.err

# Verify .cz.toml was used (second in priority after pyproject.toml)
assert cfg.settings["name"] == "cz_jira" # from PYPROJECT
assert cfg.settings["version"] == "1.0.0"

def test_no_warn_with_explicit_config_path(_, tmpdir, capsys):
"""Test that no warning is issued when user explicitly specifies config."""
with tmpdir.as_cwd():
# Create multiple config files
tmpdir.join(".cz.toml").write(PYPROJECT)
tmpdir.join(".cz.json").write(JSON_STR)

# Read config with explicit path
cfg = config.read_cfg(filepath=".cz.json")

# No warning should be issued
captured = capsys.readouterr()
assert "Multiple config files detected" not in captured.err

# Verify the explicitly specified config is loaded (compare to expected JSON config)
json_cfg_expected = JsonConfig(data=JSON_STR, path=Path(".cz.json"))
assert cfg.settings == json_cfg_expected.settings


@pytest.mark.parametrize(
"config_file, exception_string",
Expand Down