Skip to content

feat(cli): add config option to specify config file path #1047

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Apr 11, 2024
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
11 changes: 9 additions & 2 deletions commitizen/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ def __call__(
),
"formatter_class": argparse.RawDescriptionHelpFormatter,
"arguments": [
{
"name": "--config",
"help": "the path of configuration file",
},
{"name": "--debug", "action": "store_true", "help": "use debug mode"},
{
"name": ["-n", "--name"],
Expand Down Expand Up @@ -534,9 +538,7 @@ def parse_no_raise(comma_separated_no_raise: str) -> list[int]:


def main():
conf = config.read_cfg()
parser = cli(data)

argcomplete.autocomplete(parser)
# Show help if no arg provided
if len(sys.argv) == 1:
Expand Down Expand Up @@ -576,6 +578,11 @@ def main():
extra_args = " ".join(unknown_args[1:])
arguments["extra_cli_args"] = extra_args

if args.config:
conf = config.read_cfg(args.config)
else:
conf = config.read_cfg()

if args.name:
conf.update({"name": args.name})
elif not args.name and not conf.path:
Expand Down
32 changes: 21 additions & 11 deletions commitizen/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,34 @@
from pathlib import Path

from commitizen import defaults, git
from commitizen.exceptions import ConfigFileNotFound, ConfigFileIsEmpty

from .base_config import BaseConfig
from .json_config import JsonConfig
from .toml_config import TomlConfig
from .yaml_config import YAMLConfig


def read_cfg() -> BaseConfig:
def read_cfg(filepath: str | None = None) -> BaseConfig:
conf = BaseConfig()

git_project_root = git.find_git_project_root()
cfg_search_paths = [Path(".")]
if git_project_root:
cfg_search_paths.append(git_project_root)
if filepath is not None:
if not Path(filepath).exists():
raise ConfigFileNotFound()

cfg_paths = (path for path in (Path(filepath),))
else:
git_project_root = git.find_git_project_root()
cfg_search_paths = [Path(".")]
if git_project_root:
cfg_search_paths.append(git_project_root)

cfg_paths = (
path / Path(filename)
for path in cfg_search_paths
for filename in defaults.config_files
)

cfg_paths = (
path / Path(filename)
for path in cfg_search_paths
for filename in defaults.config_files
)
for filename in cfg_paths:
if not filename.exists():
continue
Expand All @@ -39,7 +47,9 @@ def read_cfg() -> BaseConfig:
elif "yaml" in filename.suffix:
_conf = YAMLConfig(data=data, path=filename)

if _conf.is_empty_config:
if filepath is not None and _conf.is_empty_config:
raise ConfigFileIsEmpty()
elif _conf.is_empty_config:
continue
else:
conf = _conf
Expand Down
12 changes: 12 additions & 0 deletions commitizen/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ class ExitCode(enum.IntEnum):
VERSION_PROVIDER_UNKNOWN = 27
VERSION_SCHEME_UNKNOWN = 28
CHANGELOG_FORMAT_UNKNOWN = 29
CONFIG_FILE_NOT_FOUND = 30
CONFIG_FILE_IS_EMPTY = 31


class CommitizenException(Exception):
Expand Down Expand Up @@ -189,3 +191,13 @@ class VersionSchemeUnknown(CommitizenException):
class ChangelogFormatUnknown(CommitizenException):
exit_code = ExitCode.CHANGELOG_FORMAT_UNKNOWN
message = "Unknown changelog format identifier"


class ConfigFileNotFound(CommitizenException):
exit_code = ExitCode.CONFIG_FILE_NOT_FOUND
message = "Cannot found the config file, please check your file path again."


class ConfigFileIsEmpty(CommitizenException):
exit_code = ExitCode.CONFIG_FILE_IS_EMPTY
message = "Config file is empty, please check your file path again."
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ For more information about the topic go to https://conventionalcommits.org/

optional arguments:
-h, --help show this help message and exit
--config the path of configuration file
--debug use debug mode
-n NAME, --name NAME use the given commitizen (default: cz_conventional_commits)
-nr NO_RAISE, --no-raise NO_RAISE
Expand Down
10 changes: 10 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
NoCommandFoundError,
NotAGitProjectError,
InvalidCommandArgumentError,
ConfigFileNotFound,
)


Expand All @@ -25,6 +26,15 @@ def test_sysexit_no_argv(mocker: MockFixture, capsys):
assert out.startswith("usage")


def test_cz_config_file_without_correct_file_path(mocker: MockFixture, capsys):
testargs = ["cz", "--config", "./config/pyproject.toml", "example"]
mocker.patch.object(sys, "argv", testargs)

with pytest.raises(ConfigFileNotFound) as excinfo:
cli.main()
assert "Cannot found the config file" in str(excinfo.value)


def test_cz_with_arg_but_without_command(mocker: MockFixture):
testargs = ["cz", "--name", "cz_jira"]
mocker.patch.object(sys, "argv", testargs)
Expand Down
57 changes: 56 additions & 1 deletion tests/test_conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import yaml

from commitizen import config, defaults, git
from commitizen.exceptions import InvalidConfigurationError
from commitizen.exceptions import InvalidConfigurationError, ConfigFileIsEmpty

PYPROJECT = """
[tool.commitizen]
Expand Down Expand Up @@ -44,6 +44,27 @@
}
}

JSON_STR = r"""
{
"commitizen": {
"name": "cz_jira",
"version": "1.0.0",
"version_files": [
"commitizen/__version__.py",
"pyproject.toml"
]
}
}
"""

YAML_STR = """
commitizen:
name: cz_jira
version: 1.0.0
version_files:
- commitizen/__version__.py
- pyproject.toml
"""

_settings: dict[str, Any] = {
"name": "cz_jira",
Expand Down Expand Up @@ -158,6 +179,40 @@ def test_load_empty_pyproject_toml_and_cz_toml_with_config(_, tmpdir):
cfg = config.read_cfg()
assert cfg.settings == _settings

def test_load_pyproject_toml_from_config_argument(_, tmpdir):
with tmpdir.as_cwd():
_not_root_path = tmpdir.mkdir("not_in_root").join("pyproject.toml")
_not_root_path.write(PYPROJECT)

cfg = config.read_cfg(filepath="./not_in_root/pyproject.toml")
assert cfg.settings == _settings

def test_load_cz_json_not_from_config_argument(_, tmpdir):
with tmpdir.as_cwd():
_not_root_path = tmpdir.mkdir("not_in_root").join(".cz.json")
_not_root_path.write(JSON_STR)

cfg = config.read_cfg(filepath="./not_in_root/.cz.json")
json_cfg_by_class = config.JsonConfig(data=JSON_STR, path=_not_root_path)
assert cfg.settings == json_cfg_by_class.settings

def test_load_cz_yaml_not_from_config_argument(_, tmpdir):
with tmpdir.as_cwd():
_not_root_path = tmpdir.mkdir("not_in_root").join(".cz.yaml")
_not_root_path.write(YAML_STR)

cfg = config.read_cfg(filepath="./not_in_root/.cz.yaml")
yaml_cfg_by_class = config.YAMLConfig(data=YAML_STR, path=_not_root_path)
assert cfg.settings == yaml_cfg_by_class._settings

def test_load_empty_pyproject_toml_from_config_argument(_, tmpdir):
with tmpdir.as_cwd():
_not_root_path = tmpdir.mkdir("not_in_root").join("pyproject.toml")
_not_root_path.write("")

with pytest.raises(ConfigFileIsEmpty):
config.read_cfg(filepath="./not_in_root/pyproject.toml")


class TestTomlConfig:
def test_init_empty_config_content(self, tmpdir):
Expand Down