From 0f0813ca61b63a8189436a1aedcb4802dd826ed2 Mon Sep 17 00:00:00 2001 From: santiago fraire Date: Wed, 17 Apr 2019 22:03:10 +0200 Subject: [PATCH 1/6] refactor(commit): moved most of the commit logic to the commit command --- commitizen/commands/commit.py | 28 +++++++++++++- commitizen/cz/base.py | 72 +++++------------------------------ tests/test_commands.py | 24 ++++++++---- tests/test_cz_base.py | 13 ------- 4 files changed, 51 insertions(+), 86 deletions(-) diff --git a/commitizen/commands/commit.py b/commitizen/commands/commit.py index 3b4c7195ca..b84a24707d 100644 --- a/commitizen/commands/commit.py +++ b/commitizen/commands/commit.py @@ -1,4 +1,9 @@ -from commitizen import factory +import questionary +from commitizen import factory, out, git + + +NO_ANSWERS = 5 +COMMIT_ERROR = 6 class Commit: @@ -9,4 +14,23 @@ def __init__(self, config: dict, *args): self.cz = factory.commiter_factory(self.config) def __call__(self): - self.cz.run() + cz = self.cz + questions = cz.questions() + answers = questionary.prompt(questions) + if not answers: + raise SystemExit(NO_ANSWERS) + m = cz.message(answers) + + c = git.commit(m) + + if c.err: + out.error(c.err) + raise SystemExit(COMMIT_ERROR) + + if "nothing added" in c.out or "no changes added to commit" in c.out: + out.error(c.out) + elif c.err: + out.error(c.err) + else: + out.write(c.out) + out.success("Commit successful!") diff --git a/commitizen/cz/base.py b/commitizen/cz/base.py index 3cc00e5752..63ec062cd5 100644 --- a/commitizen/cz/base.py +++ b/commitizen/cz/base.py @@ -1,11 +1,5 @@ -import sys -import logging - -from commitizen import out, git +from commitizen import out from abc import ABCMeta, abstractmethod -from questionary import prompt - -logger = logging.getLogger(__name__) class BaseCommitizen(metaclass=ABCMeta): @@ -23,44 +17,19 @@ def questions(self): """ @abstractmethod - def message(self, answers): - """Format your git message. - - :param answers: Use answers - :type answers: dict - - :rtype: string - """ + def message(self, answers: dict) -> dict: + """Format your git message.""" - def commit(self, message: str): - c = git.commit(message) - # f = NamedTemporaryFile("wb", delete=False) - # f.write(message.encode("utf-8")) - # f.close() - - # c = cmd.run(f"git commit -F {f.name}") - # os.unlink(f.name) - return c - - def example(self): - """Example of the commit message. - - :rtype: string - """ + def example(self) -> str: + """Example of the commit message.""" raise NotImplementedError("Not Implemented yet") - def schema(self): - """Schema definition of the commit message. - - :rtype: string - """ + def schema(self) -> str: + """Schema definition of the commit message.""" raise NotImplementedError("Not Implemented yet") - def info(self): - """Information about the standardized commit message. - - :rtype: string - """ + def info(self) -> str: + """Information about the standardized commit message.""" raise NotImplementedError("Not Implemented yet") def show_example(self, *args, **kwargs): @@ -71,26 +40,3 @@ def show_schema(self, *args, **kwargs): def show_info(self, *args, **kwargs): out.write(self.info()) - - def run(self, *args, **kwargs): - questions = self.questions() - answers = prompt(questions) - logger.debug("Answers:\n %s", answers) - m = self.message(answers) - logger.debug("Commit message generated:\n %s", m) - - c = self.commit(m) - - if c.err: - logger.warning(c.err) - sys.exit(1) - - if "nothing added" in c.out or "no changes added to commit" in c.out: - out.error(c.out) - elif c.err: - out.error(c.err) - else: - out.write(c.out) - out.success("Commit successful!") - - sys.exit(0) diff --git a/tests/test_commands.py b/tests/test_commands.py index 1d925a14a1..32a19ca54f 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -1,18 +1,26 @@ # import pytest from unittest import mock -from commitizen import defaults, commands +from commitizen import defaults, commands, cmd config = {"name": defaults.name} -def test_commit(): - with mock.patch("commitizen.factory.commiter_factory") as mocked_factory: - mock_cz = mock.Mock() - mocked_factory.return_value = mock_cz - commands.Commit(config)() +def test_commit(mocker): + prompt_mock = mocker.patch("questionary.prompt") + prompt_mock.return_value = { + "prefix": "feat", + "subject": "user created", + "scope": "", + "body": "", + "footer": "", + } - mocked_factory.assert_called_once() - mock_cz.run.assert_called_once() + commit_mock = mocker.patch("commitizen.git.commit") + commit_mock.return_value = cmd.Command("success", "", "", "") + success_mock = mocker.patch("commitizen.out.success") + + commands.Commit(config)() + success_mock.assert_called_once() def test_example(): diff --git a/tests/test_cz_base.py b/tests/test_cz_base.py index 801a4033ab..14f982d114 100644 --- a/tests/test_cz_base.py +++ b/tests/test_cz_base.py @@ -62,16 +62,3 @@ def test_show_info(): cz = DummyCz(config) with pytest.raises(NotImplementedError): cz.show_info() - - -def test_commit(mocker): - process_mock = mocker.Mock() - attrs = {"communicate.return_value": (b"commit done", b"")} - process_mock.configure_mock(**attrs) - - m = mocker.patch("subprocess.Popen") - m.return_value = process_mock - - cz = DummyCz(config) - c = cz.commit("test: run test") - assert c.out == "commit done" From f1f584194fc43600650192e62fbf899b49ca0e0b Mon Sep 17 00:00:00 2001 From: santiago fraire Date: Wed, 17 Apr 2019 22:06:59 +0200 Subject: [PATCH 2/6] refactor(example): command logic removed from commitizen base --- commitizen/commands/example.py | 4 ++-- commitizen/cz/base.py | 3 --- tests/test_commands.py | 11 ++++++----- tests/test_cz_base.py | 6 ------ 4 files changed, 8 insertions(+), 16 deletions(-) diff --git a/commitizen/commands/example.py b/commitizen/commands/example.py index 61cdb41bef..66246d4f55 100644 --- a/commitizen/commands/example.py +++ b/commitizen/commands/example.py @@ -1,4 +1,4 @@ -from commitizen import factory +from commitizen import factory, out class Example: @@ -9,4 +9,4 @@ def __init__(self, config: dict, *args): self.cz = factory.commiter_factory(self.config) def __call__(self): - self.cz.show_example() + out.write(self.cz.example()) diff --git a/commitizen/cz/base.py b/commitizen/cz/base.py index 63ec062cd5..19a374c6ee 100644 --- a/commitizen/cz/base.py +++ b/commitizen/cz/base.py @@ -32,9 +32,6 @@ def info(self) -> str: """Information about the standardized commit message.""" raise NotImplementedError("Not Implemented yet") - def show_example(self, *args, **kwargs): - out.write(self.example()) - def show_schema(self, *args, **kwargs): out.write(self.schema()) diff --git a/tests/test_commands.py b/tests/test_commands.py index 32a19ca54f..0b51e26b85 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -24,13 +24,14 @@ def test_commit(mocker): def test_example(): - with mock.patch("commitizen.factory.commiter_factory") as mocked_factory: - mock_cz = mock.Mock() - mocked_factory.return_value = mock_cz + with mock.patch("commitizen.out.write") as write_mock: + # mock_cz = mock.Mock() + # mocked_factory.return_value = mock_cz commands.Example(config)() + write_mock.assert_called_once() - mocked_factory.assert_called_once() - mock_cz.show_example.assert_called_once() + # mocked_factory.assert_called_once() + # mock_cz.show_example.assert_called_once() def test_info(): diff --git a/tests/test_cz_base.py b/tests/test_cz_base.py index 14f982d114..97f67bca04 100644 --- a/tests/test_cz_base.py +++ b/tests/test_cz_base.py @@ -46,12 +46,6 @@ def test_info(): cz.info() -def test_show_example(): - cz = DummyCz(config) - with pytest.raises(NotImplementedError): - cz.show_example() - - def test_show_schema(): cz = DummyCz(config) with pytest.raises(NotImplementedError): From 1e9a8272c49c6352e4e527dd277297b2ca408947 Mon Sep 17 00:00:00 2001 From: santiago fraire Date: Wed, 17 Apr 2019 22:09:38 +0200 Subject: [PATCH 3/6] refactor(info): command logic removed from commitizen base --- commitizen/commands/info.py | 4 ++-- commitizen/cz/base.py | 3 --- tests/test_commands.py | 13 ++----------- tests/test_cz_base.py | 6 ------ 4 files changed, 4 insertions(+), 22 deletions(-) diff --git a/commitizen/commands/info.py b/commitizen/commands/info.py index bd11c45278..f8450e6b0d 100644 --- a/commitizen/commands/info.py +++ b/commitizen/commands/info.py @@ -1,4 +1,4 @@ -from commitizen import factory +from commitizen import factory, out class Info: @@ -9,4 +9,4 @@ def __init__(self, config: dict, *args): self.cz = factory.commiter_factory(self.config) def __call__(self): - self.cz.show_info() + out.write(self.cz.info()) diff --git a/commitizen/cz/base.py b/commitizen/cz/base.py index 19a374c6ee..297b557783 100644 --- a/commitizen/cz/base.py +++ b/commitizen/cz/base.py @@ -34,6 +34,3 @@ def info(self) -> str: def show_schema(self, *args, **kwargs): out.write(self.schema()) - - def show_info(self, *args, **kwargs): - out.write(self.info()) diff --git a/tests/test_commands.py b/tests/test_commands.py index 0b51e26b85..f223ba6cdc 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -25,23 +25,14 @@ def test_commit(mocker): def test_example(): with mock.patch("commitizen.out.write") as write_mock: - # mock_cz = mock.Mock() - # mocked_factory.return_value = mock_cz commands.Example(config)() write_mock.assert_called_once() - # mocked_factory.assert_called_once() - # mock_cz.show_example.assert_called_once() - def test_info(): - with mock.patch("commitizen.factory.commiter_factory") as mocked_factory: - mock_cz = mock.Mock() - mocked_factory.return_value = mock_cz + with mock.patch("commitizen.out.write") as write_mock: commands.Info(config)() - - mocked_factory.assert_called_once() - mock_cz.show_info.assert_called_once() + write_mock.assert_called_once() def test_schema(): diff --git a/tests/test_cz_base.py b/tests/test_cz_base.py index 97f67bca04..a07b5210fc 100644 --- a/tests/test_cz_base.py +++ b/tests/test_cz_base.py @@ -50,9 +50,3 @@ def test_show_schema(): cz = DummyCz(config) with pytest.raises(NotImplementedError): cz.show_schema() - - -def test_show_info(): - cz = DummyCz(config) - with pytest.raises(NotImplementedError): - cz.show_info() From 8425c7f2cc5a725359e4927b583cb024a1c78a02 Mon Sep 17 00:00:00 2001 From: santiago fraire Date: Wed, 17 Apr 2019 22:13:02 +0200 Subject: [PATCH 4/6] refactor(schema): command logic removed from commitizen base --- commitizen/commands/schema.py | 4 ++-- commitizen/cz/base.py | 14 ++------------ tests/test_commands.py | 8 ++------ tests/test_cz_base.py | 6 ------ 4 files changed, 6 insertions(+), 26 deletions(-) diff --git a/commitizen/commands/schema.py b/commitizen/commands/schema.py index d4bbbd0a4c..fdb488295b 100644 --- a/commitizen/commands/schema.py +++ b/commitizen/commands/schema.py @@ -1,4 +1,4 @@ -from commitizen import factory +from commitizen import factory, out class Schema: @@ -9,4 +9,4 @@ def __init__(self, config: dict, *args): self.cz = factory.commiter_factory(self.config) def __call__(self): - self.cz.show_schema() + out.write(self.cz.schema()) diff --git a/commitizen/cz/base.py b/commitizen/cz/base.py index 297b557783..8c56ccf98f 100644 --- a/commitizen/cz/base.py +++ b/commitizen/cz/base.py @@ -1,4 +1,3 @@ -from commitizen import out from abc import ABCMeta, abstractmethod @@ -7,14 +6,8 @@ def __init__(self, config: dict): self.config = config @abstractmethod - def questions(self): - """Questions regarding the commit message. - - Must have 'whaaaaat' format. - More info: https://github.com/finklabs/whaaaaat/ - - :rtype: list - """ + def questions(self) -> list: + """Questions regarding the commit message.""" @abstractmethod def message(self, answers: dict) -> dict: @@ -31,6 +24,3 @@ def schema(self) -> str: def info(self) -> str: """Information about the standardized commit message.""" raise NotImplementedError("Not Implemented yet") - - def show_schema(self, *args, **kwargs): - out.write(self.schema()) diff --git a/tests/test_commands.py b/tests/test_commands.py index f223ba6cdc..0fc62fd007 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -36,13 +36,9 @@ def test_info(): def test_schema(): - with mock.patch("commitizen.factory.commiter_factory") as mocked_factory: - mock_cz = mock.Mock() - mocked_factory.return_value = mock_cz + with mock.patch("commitizen.out.write") as write_mock: commands.Schema(config)() - - mocked_factory.assert_called_once() - mock_cz.show_schema.assert_called_once() + write_mock.assert_called_once() def test_list_cz(): diff --git a/tests/test_cz_base.py b/tests/test_cz_base.py index a07b5210fc..7aab7a6a59 100644 --- a/tests/test_cz_base.py +++ b/tests/test_cz_base.py @@ -44,9 +44,3 @@ def test_info(): cz = DummyCz(config) with pytest.raises(NotImplementedError): cz.info() - - -def test_show_schema(): - cz = DummyCz(config) - with pytest.raises(NotImplementedError): - cz.show_schema() From c8ae205090e4bd58a0fce1a8af9508f3662a3532 Mon Sep 17 00:00:00 2001 From: santiago fraire Date: Wed, 17 Apr 2019 22:57:12 +0200 Subject: [PATCH 5/6] fix: conventional commit 'breaking change' in body instead of title closes #16 --- commitizen/cz/base.py | 2 +- .../conventional_commits.py | 57 +++--- .../conventional_commits_info.txt | 39 ++-- commitizen/git.py | 6 +- docs/images/commit.yml | 187 ++++++++++++++++++ docs/images/demo.gif | Bin 300528 -> 699893 bytes docs/index.md | 2 +- tests/test_commands.py | 1 + tests/test_cz_conventional_commits.py | 4 +- 9 files changed, 249 insertions(+), 49 deletions(-) create mode 100644 docs/images/commit.yml diff --git a/commitizen/cz/base.py b/commitizen/cz/base.py index 8c56ccf98f..4dbaa21ab3 100644 --- a/commitizen/cz/base.py +++ b/commitizen/cz/base.py @@ -10,7 +10,7 @@ def questions(self) -> list: """Questions regarding the commit message.""" @abstractmethod - def message(self, answers: dict) -> dict: + def message(self, answers: dict) -> str: """Format your git message.""" def example(self) -> str: diff --git a/commitizen/cz/conventional_commits/conventional_commits.py b/commitizen/cz/conventional_commits/conventional_commits.py index 6b79ffa061..6fe9ff4672 100644 --- a/commitizen/cz/conventional_commits/conventional_commits.py +++ b/commitizen/cz/conventional_commits/conventional_commits.py @@ -30,7 +30,7 @@ def parse_subject(text): class ConventionalCommitsCz(BaseCommitizen): - def questions(self): + def questions(self) -> list: questions = [ { "type": "list", @@ -45,13 +45,6 @@ def questions(self): "value": "feat", "name": "feat: A new feature. Correlates with MINOR in SemVer", }, - { - "value": "BREAKING CHANGE", - "name": ( - "BREAKING CHANGE: introduces a breaking API change. " - "Correlates with MAJOR in SemVer" - ), - }, {"value": "docs", "name": "docs: Documentation only changes"}, { "value": "style", @@ -112,6 +105,12 @@ def questions(self): "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", @@ -131,46 +130,46 @@ def questions(self): ] return questions - def message(self, answers): + def message(self, answers: dict) -> str: prefix = answers["prefix"] scope = answers["scope"] subject = answers["subject"] body = answers["body"] footer = answers["footer"] - message = "" - - if prefix: - message += "{0}".format(prefix) - if scope: - message += "({0})".format(scope) - message += ": " - if subject: - message += "{0}".format(subject) + is_breaking_change = answers["is_breaking_change"] + + if scope: + scope = f"({scope})" + if is_breaking_change: + body = f"BREAKING CHANGE: {body}" if body: - message += "\n\n{0}".format(body) + body = f"\n\n{body}" if footer: - message += "\n\n{0}".format(footer) + footer = f"\n\n{footer}" + + message = f"{prefix}{scope}: {subject}{body}{footer}" + return message - def example(self): + def example(self) -> str: return ( - "feat($injector): ability to load new modules after bootstrapping\n" - "\nThe new method `$injector.loadNewModules(modules)` will add " - "each of the\ninjectables to the injector and execute all of the " - "config and run blocks\nfor each module passed to the method.\n" - "\nCloses #324" + "fix: correct minor typos in code\n" + "\n" + "see the issue for details on the typos fixed\n" + "\n" + "closes issue #12" ) - def schema(self): + def schema(self) -> str: return ( "(): \n" "\n" - "\n" + "(BREAKING CHANGE: )\n" "\n" "