Skip to content
Open
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
38 changes: 17 additions & 21 deletions commitizen/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from .base_config import BaseConfig


def _resolve_config_paths() -> list[Path]:
def _resolve_config_candidates() -> list[BaseConfig]:
git_project_root = git.find_git_project_root()
cfg_search_paths = [Path(".")]

Expand All @@ -18,12 +18,18 @@ def _resolve_config_paths() -> list[Path]:

# The following algorithm is ugly, but we need to ensure that the order of the candidates are preserved before v5.
# Also, the number of possible config files is limited, so the complexity is not a problem.
candidates: list[Path] = []
candidates: list[BaseConfig] = []
for dir in cfg_search_paths:
for filename in defaults.CONFIG_FILES:
out_path = dir / Path(filename)
if out_path.exists() and all(not out_path.samefile(p) for p in candidates):
candidates.append(out_path)
if (
out_path.exists()
and not any(
out_path.samefile(candidate.path) for candidate in candidates
)
and not (conf := _create_config_from_path(out_path)).is_empty_config
):
candidates.append(conf)
return candidates


Expand All @@ -44,21 +50,11 @@ def read_cfg(filepath: str | None = None) -> BaseConfig:
raise ConfigFileIsEmpty()
return conf

config_candidate_paths = _resolve_config_paths()

# Check for multiple config files and warn the user
config_candidates_exclude_pyproject = [
path for path in config_candidate_paths if path.name != "pyproject.toml"
]

for config_candidate_path in config_candidate_paths:
conf = _create_config_from_path(config_candidate_path)
if not conf.is_empty_config:
if len(config_candidates_exclude_pyproject) > 1:
out.warn(
f"Multiple config files detected: {', '.join(map(str, config_candidates_exclude_pyproject))}. "
f"Using config file: '{config_candidate_path}'."
)
return conf
config_candidates = _resolve_config_candidates()
if len(config_candidates) > 1:
out.warn(
f"Multiple config files detected: {', '.join(str(conf.path) for conf in config_candidates)}. "
f"Using config file: '{config_candidates[0].path}'."
)

return BaseConfig()
return config_candidates[0] if config_candidates else BaseConfig()
61 changes: 35 additions & 26 deletions tests/test_conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from commitizen.config.yaml_config import YAMLConfig
from commitizen.exceptions import ConfigFileIsEmpty, InvalidConfigurationError

PYPROJECT = """
TOML_STR = """
[tool.commitizen]
name = "cz_jira"
version = "1.0.0"
Expand All @@ -30,11 +30,17 @@
"scripts/generate_documentation.sh"
]
post_bump_hooks = ["scripts/slack_notification.sh"]
"""

PYPROJECT = (
TOML_STR
+ """
[tool.black]
line-length = 88
target-version = ['py36', 'py37', 'py38']
"""
)


DICT_CONFIG = {
"commitizen": {
Expand Down Expand Up @@ -198,7 +204,7 @@ def test_load_empty_pyproject_toml_and_cz_toml_with_config(_, tmpdir):
p = tmpdir.join("pyproject.toml")
p.write("")
p = tmpdir.join(".cz.toml")
p.write(PYPROJECT)
p.write(TOML_STR)

cfg = config.read_cfg()
assert cfg.settings == _settings
Expand Down Expand Up @@ -240,27 +246,25 @@ def test_load_empty_pyproject_toml_from_config_argument(_, tmpdir):

class TestWarnMultipleConfigFiles:
@pytest.mark.parametrize(
"files,expected_path,should_warn",
"files,expected_path",
[
# Same directory, different file types
([(".cz.toml", PYPROJECT), (".cz.json", JSON_STR)], ".cz.toml", True),
([(".cz.json", JSON_STR), (".cz.yaml", YAML_STR)], ".cz.json", True),
([(".cz.toml", PYPROJECT), (".cz.yaml", YAML_STR)], ".cz.toml", True),
# With pyproject.toml (excluded from warning)
([(".cz.toml", TOML_STR), (".cz.json", JSON_STR)], ".cz.toml"),
([(".cz.json", JSON_STR), (".cz.yaml", YAML_STR)], ".cz.json"),
([(".cz.toml", TOML_STR), (".cz.yaml", YAML_STR)], ".cz.toml"),
# With pyproject.toml
(
[("pyproject.toml", PYPROJECT), (".cz.json", JSON_STR)],
".cz.json",
False,
),
(
[("pyproject.toml", PYPROJECT), (".cz.toml", PYPROJECT)],
[("pyproject.toml", PYPROJECT), (".cz.toml", TOML_STR)],
".cz.toml",
False,
),
],
)
def test_warn_multiple_config_files_same_dir(
_, tmpdir, capsys, files, expected_path, should_warn
_, tmpdir, capsys, files, expected_path
):
"""Test warning when multiple config files exist in same directory."""
with tmpdir.as_cwd():
Expand All @@ -270,27 +274,20 @@ def test_warn_multiple_config_files_same_dir(
cfg = config.read_cfg()
captured = capsys.readouterr()

if should_warn:
assert "Multiple config files detected" in captured.err
assert "Using" in captured.err
for filename, _ in files:
if filename != "pyproject.toml":
assert filename in captured.err
else:
assert "Multiple config files detected" not in captured.err
assert "Multiple config files detected" in captured.err
for filename, _ in files:
assert filename in captured.err
assert f"Using config file: '{expected_path}'" in captured.err

assert cfg.path == Path(expected_path)
# Verify config loaded correctly (name and version match expected)
assert cfg.settings["name"] == "cz_jira"
assert cfg.settings["version"] == "1.0.0"

@pytest.mark.parametrize(
"config_file,content",
[
(".cz.json", JSON_STR),
(".cz.toml", PYPROJECT),
(".cz.toml", TOML_STR),
(".cz.yaml", YAML_STR),
("cz.toml", PYPROJECT),
("cz.toml", TOML_STR),
("cz.json", JSON_STR),
("cz.yaml", YAML_STR),
],
Expand Down Expand Up @@ -340,11 +337,11 @@ def test_no_warn_with_explicit_config_path(_, tmpdir, capsys):
[
(file, content, with_git)
for file, content in [
(".cz.toml", PYPROJECT),
(".cz.toml", TOML_STR),
(".cz.json", JSON_STR),
(".cz.yaml", YAML_STR),
("pyproject.toml", PYPROJECT),
("cz.toml", PYPROJECT),
("cz.toml", TOML_STR),
("cz.json", JSON_STR),
("cz.yaml", YAML_STR),
]
Expand All @@ -368,6 +365,18 @@ def test_no_warn_with_single_config_file(
assert "Multiple config files detected" not in captured.err
assert cfg.path == Path(config_file)

def test_no_warn_with_no_commitizen_section_in_pyproject_toml_and_cz_toml(
_, tmpdir, capsys
):
with tmpdir.as_cwd():
tmpdir.join("pyproject.toml").write("[tool.foo]\nbar = 'baz'")
tmpdir.join(".cz.toml").write(TOML_STR)

cfg = config.read_cfg()
captured = capsys.readouterr()
assert "Multiple config files detected" not in captured.err
assert cfg.path == Path(".cz.toml")


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