From cc12aa07d653da3d8ab22fc56ac5dec9a66373b7 Mon Sep 17 00:00:00 2001 From: Michael Kuc Date: Fri, 20 Mar 2020 19:49:17 +0000 Subject: [PATCH 1/2] feat(cz/conventional_jira): conventional commits with enforced JIRA scope Allows for better enforcement of JIRA issue-driven projects. --- commitizen/cz/__init__.py | 2 + commitizen/cz/conventional_jira/__init__.py | 1 + .../cz/conventional_jira/conventional_jira.py | 189 ++++++++++++++++++ .../conventional_jira_info.txt | 31 +++ commitizen/cz/exceptions.py | 4 + 5 files changed, 227 insertions(+) create mode 100644 commitizen/cz/conventional_jira/__init__.py create mode 100644 commitizen/cz/conventional_jira/conventional_jira.py create mode 100644 commitizen/cz/conventional_jira/conventional_jira_info.txt diff --git a/commitizen/cz/__init__.py b/commitizen/cz/__init__.py index 3c379f3c6b..e57f0ef2e2 100644 --- a/commitizen/cz/__init__.py +++ b/commitizen/cz/__init__.py @@ -2,11 +2,13 @@ import pkgutil from commitizen.cz.conventional_commits import ConventionalCommitsCz +from commitizen.cz.conventional_jira import ConventionalJiraCz from commitizen.cz.customize import CustomizeCommitsCz from commitizen.cz.jira import JiraSmartCz registry = { "cz_conventional_commits": ConventionalCommitsCz, + "cz_conventional_jira": ConventionalJiraCz, "cz_jira": JiraSmartCz, "cz_customize": CustomizeCommitsCz, } diff --git a/commitizen/cz/conventional_jira/__init__.py b/commitizen/cz/conventional_jira/__init__.py new file mode 100644 index 0000000000..c081fb6608 --- /dev/null +++ b/commitizen/cz/conventional_jira/__init__.py @@ -0,0 +1 @@ +from .conventional_jira import ConventionalJiraCz # noqa diff --git a/commitizen/cz/conventional_jira/conventional_jira.py b/commitizen/cz/conventional_jira/conventional_jira.py new file mode 100644 index 0000000000..b6ef5bfa6c --- /dev/null +++ b/commitizen/cz/conventional_jira/conventional_jira.py @@ -0,0 +1,189 @@ +import os +import re + +from commitizen import defaults +from commitizen.cz import exceptions +from commitizen.cz.base import BaseCommitizen +from commitizen.cz.utils import multiple_line_breaker, required_validator + +__all__ = ["ConventionalJiraCz"] + + +def parse_jira_issues(text): + if not text: + return "" + + issues = text.strip().split(", ") + issueRE = re.compile(r"\w+-\d+") + + for issue in issues: + if not issueRE.fullmatch(issue): + raise exceptions.InvalidAnswerError(f"JIRA scope of '{issue}' is invalid") + + if len(issues) == 1: + return issues[0] + + return required_validator(", ".join(issues), msg="JIRA scope is required") + + +def parse_subject(text): + if isinstance(text, str): + text = text.strip(".").strip() + + return required_validator(text, msg="Subject is required.") + + +class ConventionalJiraCz(BaseCommitizen): + bump_pattern = defaults.bump_pattern + bump_map = defaults.bump_map + + def questions(self) -> list: + questions = [ + { + "type": "list", + "name": "prefix", + "message": "Select the type of change you are committing", + "choices": [ + { + "value": "fix", + "name": "fix: A bug fix. Correlates with PATCH in SemVer", + }, + { + "value": "feat", + "name": "feat: A new feature. Correlates with MINOR in SemVer", + }, + {"value": "docs", "name": "docs: Documentation only changes"}, + { + "value": "style", + "name": ( + "style: Changes that do not affect the " + "meaning of the code (white-space, formatting," + " missing semi-colons, etc)" + ), + }, + { + "value": "refactor", + "name": ( + "refactor: A code change that neither fixes " + "a bug nor adds a feature" + ), + }, + { + "value": "perf", + "name": "perf: A code change that improves performance", + }, + { + "value": "test", + "name": ( + "test: Adding missing or correcting " "existing tests" + ), + }, + { + "value": "build", + "name": ( + "build: Changes that affect the build system or " + "external dependencies (example scopes: pip, docker, npm)" + ), + }, + { + "value": "ci", + "name": ( + "ci: Changes to our CI configuration files and " + "scripts (example scopes: GitLabCI)" + ), + }, + ], + }, + { + "type": "input", + "name": "jira_issue", + "message": ("JIRA issue. Of form $JIRA_Project$-$Issue_Number$:"), + "filter": parse_jira_issues, + }, + { + "type": "input", + "name": "subject", + "filter": parse_subject, + "message": ( + "Subject. Concise description of the changes. " + "Imperative, lower case and no final dot:\n" + ), + }, + { + "type": "confirm", + "message": "Is this a BREAKING CHANGE? Correlates with MAJOR in SemVer", + "name": "is_breaking_change", + "default": False, + }, + { + "type": "input", + "name": "body", + "message": ( + "Body. Motivation for the change and contrast this " + "with previous behavior:\n" + ), + "filter": multiple_line_breaker, + }, + { + "type": "input", + "name": "footer", + "message": ( + "Footer. Information about Breaking Changes and " + "reference issues that this commit impacts:\n" + ), + }, + ] + return questions + + def message(self, answers: dict) -> str: + prefix = answers["prefix"] + jira_issue = answers["jira_issue"] + subject = answers["subject"] + body = answers["body"] + footer = answers["footer"] + is_breaking_change = answers["is_breaking_change"] + + if jira_issue: + jira_issue = f"({jira_issue})" + if is_breaking_change: + body = f"BREAKING CHANGE: {body}" + if body: + body = f"\n\n{body}" + if footer: + footer = f"\n\n{footer}" + + message = f"{prefix}{jira_issue}: {subject}{body}{footer}" + + return message + + def example(self) -> str: + return ( + "fix(JIRA-1): correct minor typos in code\n" + "\n" + "see the issue for details on the typos fixed\n" + "\n" + "closes issue #12" + ) + + def schema(self) -> str: + return ( + "(): \n" + "\n" + "(BREAKING CHANGE: )\n" + "\n" + "