diff --git a/commitizen/config/__init__.py b/commitizen/config/__init__.py index ae980af9c0..b9a530e5fd 100644 --- a/commitizen/config/__init__.py +++ b/commitizen/config/__init__.py @@ -13,23 +13,17 @@ def _resolve_config_candidates() -> list[BaseConfig]: git_project_root = git.find_git_project_root() cfg_search_paths = [Path(".")] - if git_project_root and not cfg_search_paths[0].samefile(git_project_root): + if git_project_root and cfg_search_paths[0].resolve() != git_project_root.resolve(): cfg_search_paths.append(git_project_root) - # 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[BaseConfig] = [] for dir in cfg_search_paths: for filename in defaults.CONFIG_FILES: out_path = dir / Path(filename) - 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) + if out_path.is_file(): + conf = _create_config_from_path(out_path) + if conf.contains_commitizen_section(): + candidates.append(conf) return candidates @@ -43,10 +37,10 @@ def _create_config_from_path(path: Path) -> BaseConfig: def read_cfg(filepath: str | None = None) -> BaseConfig: if filepath is not None: conf_path = Path(filepath) - if not conf_path.exists(): + if not conf_path.is_file(): raise ConfigFileNotFound() conf = _create_config_from_path(conf_path) - if conf.is_empty_config: + if not conf.contains_commitizen_section(): raise ConfigFileIsEmpty() return conf diff --git a/commitizen/config/base_config.py b/commitizen/config/base_config.py index 98270915d8..f100cf9953 100644 --- a/commitizen/config/base_config.py +++ b/commitizen/config/base_config.py @@ -17,10 +17,16 @@ class BaseConfig: def __init__(self) -> None: - self.is_empty_config = False self._settings: Settings = DEFAULT_SETTINGS.copy() self._path: Path | None = None + def contains_commitizen_section(self) -> bool: + """Check if the config file contains a commitizen section. + + The implementation is different for each config file type. + """ + raise NotImplementedError() + @property def settings(self) -> Settings: return self._settings diff --git a/commitizen/config/json_config.py b/commitizen/config/json_config.py index 860ca8ed5a..3951c52854 100644 --- a/commitizen/config/json_config.py +++ b/commitizen/config/json_config.py @@ -25,6 +25,11 @@ def __init__(self, *, data: bytes | str, path: Path) -> None: self.path = path self._parse_setting(data) + def contains_commitizen_section(self) -> bool: + with self.path.open("rb") as json_file: + config_doc = json.load(json_file) + return config_doc.get("commitizen") is not None + def init_empty_config_content(self) -> None: with smart_open( self.path, "a", encoding=self._settings["encoding"] @@ -32,7 +37,7 @@ def init_empty_config_content(self) -> None: json.dump({"commitizen": {}}, json_file) def set_key(self, key: str, value: object) -> Self: - with open(self.path, "rb") as f: + with self.path.open("rb") as f: config_doc = json.load(f) config_doc["commitizen"][key] = value @@ -59,4 +64,4 @@ def _parse_setting(self, data: bytes | str) -> None: try: self.settings.update(doc["commitizen"]) except KeyError: - self.is_empty_config = True + pass diff --git a/commitizen/config/toml_config.py b/commitizen/config/toml_config.py index b10cf9bd3e..d91fcbdfcb 100644 --- a/commitizen/config/toml_config.py +++ b/commitizen/config/toml_config.py @@ -26,6 +26,15 @@ def __init__(self, *, data: bytes | str, path: Path) -> None: self.path = path self._parse_setting(data) + def contains_commitizen_section(self) -> bool: + with self.path.open("rb") as f: + config_doc = parse(f.read()) + tool = config_doc.get("tool") + if tool is None: + return False + commitizen = tool.get("commitizen") + return commitizen is not None + def init_empty_config_content(self) -> None: config_doc = TOMLDocument() if os.path.isfile(self.path): @@ -67,4 +76,4 @@ def _parse_setting(self, data: bytes | str) -> None: try: self.settings.update(doc["tool"]["commitizen"]) # type: ignore[index,typeddict-item] # TODO: fix this except exceptions.NonExistentKey: - self.is_empty_config = True + pass diff --git a/commitizen/config/yaml_config.py b/commitizen/config/yaml_config.py index 58722d0f60..dd1c2c6308 100644 --- a/commitizen/config/yaml_config.py +++ b/commitizen/config/yaml_config.py @@ -32,6 +32,11 @@ def init_empty_config_content(self) -> None: ) as json_file: yaml.dump({"commitizen": {}}, json_file, explicit_start=True) + def contains_commitizen_section(self) -> bool: + with self.path.open("rb") as yaml_file: + config_doc = yaml.load(yaml_file, Loader=yaml.FullLoader) + return config_doc.get("commitizen") is not None + def _parse_setting(self, data: bytes | str) -> None: """We expect to have a section in cz.yaml looking like @@ -40,8 +45,6 @@ def _parse_setting(self, data: bytes | str) -> None: name: cz_conventional_commits ``` """ - import yaml.scanner - try: doc = yaml.safe_load(data) except yaml.YAMLError as e: @@ -50,7 +53,7 @@ def _parse_setting(self, data: bytes | str) -> None: try: self.settings.update(doc["commitizen"]) except (KeyError, TypeError): - self.is_empty_config = True + pass def set_key(self, key: str, value: object) -> Self: with open(self.path, "rb") as yaml_file: