Skip to content
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

First take at ConventionalCommitsAppenderCz #486

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
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
20 changes: 16 additions & 4 deletions commitizen/commands/check.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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(
[
Expand Down Expand Up @@ -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
2 changes: 2 additions & 0 deletions commitizen/cz/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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,
}
Expand Down
1 change: 1 addition & 0 deletions commitizen/cz/conventional_commits_appender/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .conventional_commits_appender import ConventionalCommitsCz # noqa
Original file line number Diff line number Diff line change
@@ -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()
Original file line number Diff line number Diff line change
@@ -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` ===
5 changes: 5 additions & 0 deletions commitizen/out.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 <[email protected]>"]
license = "MIT"
Expand Down
21 changes: 21 additions & 0 deletions tests/data/sample_conventional_commits_appender.toml
Original file line number Diff line number Diff line change
@@ -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",
]