From 39104517006ae19b76e20c2c716079a95f387864 Mon Sep 17 00:00:00 2001 From: Stavros Ntentos <133706+stdedos@users.noreply.github.com> Date: Fri, 11 Feb 2022 13:21:24 +0200 Subject: [PATCH] First take at `ConventionalCommitsAppenderCz` --- commitizen/commands/check.py | 20 +++- commitizen/cz/__init__.py | 2 + .../conventional_commits_appender/__init__.py | 1 + .../conventional_commits_appender.py | 112 ++++++++++++++++++ .../conventional_commits_appender_info.txt | 5 + commitizen/out.py | 5 + pyproject.toml | 2 +- .../sample_conventional_commits_appender.toml | 21 ++++ 8 files changed, 163 insertions(+), 5 deletions(-) create mode 100644 commitizen/cz/conventional_commits_appender/__init__.py create mode 100644 commitizen/cz/conventional_commits_appender/conventional_commits_appender.py create mode 100644 commitizen/cz/conventional_commits_appender/conventional_commits_appender_info.txt create mode 100644 tests/data/sample_conventional_commits_appender.toml diff --git a/commitizen/commands/check.py b/commitizen/commands/check.py index 3c06c9764..d4efd4658 100644 --- a/commitizen/commands/check.py +++ b/commitizen/commands/check.py @@ -5,6 +5,7 @@ from commitizen import factory, git, out from commitizen.config import BaseConfig +from commitizen.cz import ConventionalCommitsAppenderCz from commitizen.exceptions import ( InvalidCommandArgumentError, InvalidCommitMessageError, @@ -60,7 +61,7 @@ def __call__(self): ill_formated_commits = [ commit for commit in commits - if not Check.validate_commit_message(commit.message, pattern) + if not self.validate_commit_message(commit.message, pattern) ] displayed_msgs_content = "\n".join( [ @@ -98,8 +99,19 @@ def _filter_comments(self, msg: str) -> str: lines = [line for line in msg.split("\n") if not line.startswith("#")] return "\n".join(lines) - @staticmethod - def validate_commit_message(commit_msg: str, pattern: str) -> bool: + def validate_commit_message(self, commit_msg: str, pattern: str) -> bool: if commit_msg.startswith("Merge") or commit_msg.startswith("Revert"): return True - return bool(re.match(pattern, commit_msg)) + + validate = bool(re.match(pattern, commit_msg)) + + if not validate and isinstance(self.cz, ConventionalCommitsAppenderCz): + if os.environ.get(self.cz.APPENDER_CZ_LOCAL, None): + pattern = self.cz.schema_pattern(local=True) + + validate = bool(re.match(pattern, commit_msg)) + if validate: + out.warning("Title line is only allowed locally! " + "It will be rejected upstream.") + + return validate diff --git a/commitizen/cz/__init__.py b/commitizen/cz/__init__.py index 05e54673f..727ae1238 100644 --- a/commitizen/cz/__init__.py +++ b/commitizen/cz/__init__.py @@ -5,6 +5,7 @@ from commitizen.cz.base import BaseCommitizen from commitizen.cz.conventional_commits import ConventionalCommitsCz +from commitizen.cz.conventional_commits_appender.conventional_commits_appender import ConventionalCommitsAppenderCz from commitizen.cz.customize import CustomizeCommitsCz from commitizen.cz.jira import JiraSmartCz @@ -32,6 +33,7 @@ def discover_plugins(path: Iterable[str] = None) -> Dict[str, Type[BaseCommitize registry: Dict[str, Type[BaseCommitizen]] = { "cz_conventional_commits": ConventionalCommitsCz, + "cz_conventional_commits_appender": ConventionalCommitsAppenderCz, "cz_jira": JiraSmartCz, "cz_customize": CustomizeCommitsCz, } diff --git a/commitizen/cz/conventional_commits_appender/__init__.py b/commitizen/cz/conventional_commits_appender/__init__.py new file mode 100644 index 000000000..9a83c1f3f --- /dev/null +++ b/commitizen/cz/conventional_commits_appender/__init__.py @@ -0,0 +1 @@ +from .conventional_commits_appender import ConventionalCommitsCz # noqa diff --git a/commitizen/cz/conventional_commits_appender/conventional_commits_appender.py b/commitizen/cz/conventional_commits_appender/conventional_commits_appender.py new file mode 100644 index 000000000..62e2ba149 --- /dev/null +++ b/commitizen/cz/conventional_commits_appender/conventional_commits_appender.py @@ -0,0 +1,112 @@ +import os +import re + +from commitizen import out + +from commitizen.config import BaseConfig +from commitizen.cz import ConventionalCommitsCz +from commitizen.cz.utils import required_validator +from commitizen.defaults import Questions + +__all__ = ["ConventionalCommitsAppenderCz"] + + +def parse_scope(text): + if not text: + return "" + + scope = text.strip().split() + if len(scope) == 1: + return scope[0] + + return "-".join(scope) + + +def parse_subject(text): + if isinstance(text, str): + text = text.strip(".").strip() + + return required_validator(text, msg="Subject is required.") + + +class ConventionalCommitsAppenderCz(ConventionalCommitsCz): + APPENDER_CZ_LOCAL = "ConventionalCommitsAppenderCz_LOCAL" + + DEFAULT_CONFIG = { + "prefix": { + "choices": [ + ] + }, + "schema_allow_locally": { + "patterns": [ + ] + } + } + + def __init__(self, config: BaseConfig): + super().__init__(config) + self.appender_config = dict(config.settings.get(self.__class__.__name__, self.DEFAULT_CONFIG)) + self.q_list = None + + self.questions() + + if self.is_local_run(): + os.environ[self.APPENDER_CZ_LOCAL] = "True" + + def is_local_run(self): + import os + return not os.environ.get('CI', False) + + def append_questions(self): + for element in self.q_list: + if element["name"] != "prefix": + continue + + element["choices"].extend(self.appender_config["prefix"]["choices"]) + + def questions(self) -> Questions: + questions: Questions = super().questions() + self.q_list = questions + self.append_questions() + return questions + + def schema_pattern(self, local=False) -> str: + prefix_choices = [x for x in self.q_list if x["name"] == "prefix"] + assert len(prefix_choices) == 1 + prefix_choices = prefix_choices[0]["choices"] + + PATTERN = ( + fr"({'|'.join([x['value'] for x in prefix_choices])})" + r"(\(\S+\))?!?:(\s.*)" + ) + + if local: + schema_allow_locally = self.appender_config["schema_allow_locally"]["patterns"] + PATTERN = fr"(^{'|'.join(schema_allow_locally)}|{PATTERN})" + + return PATTERN + + def info(self) -> str: + dir_path = os.path.dirname(os.path.realpath(__file__)) + filepath = os.path.join(dir_path, "conventional_commits_appender_info.txt") + content = '' + with open(filepath, "r") as f: + content += f.read() + filepath = os.path.join(dir_path, "..", "conventional_commits", "conventional_commits_info.txt") + with open(filepath, "r") as f: + content += f.read() + return content + + def process_commit(self, commit: str, local=False) -> str: + pat = re.compile(self.schema_pattern()) + m = re.match(pat, commit) + if m is None: + if os.environ.get('CI', None) and not local: + return self.process_commit(commit, True) + + return "" + + if os.environ.get('CI', None) and local: + out.warning("Title line is only allowed locally!\nIt will be rejected upstream.") + + return m.group(3).strip() diff --git a/commitizen/cz/conventional_commits_appender/conventional_commits_appender_info.txt b/commitizen/cz/conventional_commits_appender/conventional_commits_appender_info.txt new file mode 100644 index 000000000..5fd33d39e --- /dev/null +++ b/commitizen/cz/conventional_commits_appender/conventional_commits_appender_info.txt @@ -0,0 +1,5 @@ +This is exactly the same as `conventional_commits`, with the exception that +it will read the first `.conventional_commit_appender_rc` file it encounters, +and append its patterns to the conventional_commits' ones. + +=== Below is the rest of the `conventional_commits_info.txt` === diff --git a/commitizen/out.py b/commitizen/out.py index a25f0f2e3..f1c10021a 100644 --- a/commitizen/out.py +++ b/commitizen/out.py @@ -18,6 +18,11 @@ def error(value: str) -> None: line(message, file=sys.stderr) +def warning(value: str) -> None: + message = colored(value, "yellow") + line(message) + + def success(value: str) -> None: message = colored(value, "green") line(message) diff --git a/pyproject.toml b/pyproject.toml index 5f2e05b81..2c65c3b21 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,7 +29,7 @@ exclude = ''' [tool.poetry] name = "commitizen" -version = "2.20.5" +version = "2.20.6" description = "Python commitizen client tool" authors = ["Santiago Fraire "] license = "MIT" diff --git a/tests/data/sample_conventional_commits_appender.toml b/tests/data/sample_conventional_commits_appender.toml new file mode 100644 index 000000000..f618d321b --- /dev/null +++ b/tests/data/sample_conventional_commits_appender.toml @@ -0,0 +1,21 @@ +[tool.commitizen] +name = "cz_conventional_commits_appender" +version = "2.20.4" +tag_format = "v$version" +version_files = [ + "pyproject.toml:version", + "commitizen/__version__.py" +] + [tool.commitizen.ConventionalCommitsAppenderCz] + [tool.commitizen.ConventionalCommitsAppenderCz.prefix] + [[tool.commitizen.ConventionalCommitsAppenderCz.prefix.choices]] + value = "impr" + name = "improvement: An improvement (but not as big as a feature). Correlates with MINOR in SemVer" + key = "i" + + [tool.commitizen.ConventionalCommitsAppenderCz.schema_allow_locally] + patterns = [ + "fixup!", + "squash!", + "a", + ]