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 4 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": "specify file path if config file is not in root folder",
},
{"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
24 changes: 23 additions & 1 deletion commitizen/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,39 @@
from pathlib import Path

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

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()

if filepath is not None:
given_cfg_path = Path(filepath)

if not given_cfg_path.exists():
raise ConfigFileNotFound()

with open(given_cfg_path, "rb") as f:
given_cfg_data: bytes = f.read()

given_cfg: TomlConfig | JsonConfig | YAMLConfig

if "toml" in given_cfg_path.suffix:
given_cfg = TomlConfig(data=given_cfg_data, path=given_cfg_path)
elif "json" in given_cfg_path.suffix:
given_cfg = JsonConfig(data=given_cfg_data, path=given_cfg_path)
elif "yaml" in given_cfg_path.suffix:
given_cfg = YAMLConfig(data=given_cfg_data, path=given_cfg_path)

return given_cfg

cfg_search_paths = [Path(".")]
if git_project_root:
cfg_search_paths.append(git_project_root)
Expand Down
6 changes: 6 additions & 0 deletions commitizen/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class ExitCode(enum.IntEnum):
VERSION_PROVIDER_UNKNOWN = 27
VERSION_SCHEME_UNKNOWN = 28
CHANGELOG_FORMAT_UNKNOWN = 29
CONFIG_FILE_NOT_FOUND = 30


class CommitizenException(Exception):
Expand Down Expand Up @@ -189,3 +190,8 @@ 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."
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 specify file path if config file is not in root folder
--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
47 changes: 47 additions & 0 deletions tests/test_conf.py
Original file line number Diff line number Diff line change
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,32 @@ 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_not_in_root_folder(_, 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_in_root_folder(_, 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_in_root_folder(_, 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=JSON_STR, path=_not_root_path)
assert cfg.settings == yaml_cfg_by_class._settings


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