From cec5d321988253919bd4c643993c15b0368dc708 Mon Sep 17 00:00:00 2001 From: Kurt von Laven Date: Wed, 4 May 2022 23:46:13 -0700 Subject: [PATCH 1/3] style(commitizen/cz/__init__.py): Mark vars unused Explicitly mark some loop variables unused as recommended by flake8-bugbear. --- commitizen/cz/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commitizen/cz/__init__.py b/commitizen/cz/__init__.py index f141e1c256..e14cb9f7c9 100644 --- a/commitizen/cz/__init__.py +++ b/commitizen/cz/__init__.py @@ -20,7 +20,7 @@ def discover_plugins(path: Iterable[str] = None) -> Dict[str, Type[BaseCommitize Dict[str, Type[BaseCommitizen]]: Registry with found plugins """ plugins = {} - for finder, name, ispkg in pkgutil.iter_modules(path): + for _finder, name, _ispkg in pkgutil.iter_modules(path): try: if name.startswith("cz_"): plugins[name] = importlib.import_module(name).discover_this From f9ab31a7e36249360bbdc8f4ac1a90625ea13516 Mon Sep 17 00:00:00 2001 From: Kurt von Laven Date: Wed, 4 May 2022 23:39:33 -0700 Subject: [PATCH 2/3] style(test_check_command): Correct "argments" typo --- tests/commands/test_check_command.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/commands/test_check_command.py b/tests/commands/test_check_command.py index 26db697b50..b9da13520a 100644 --- a/tests/commands/test_check_command.py +++ b/tests/commands/test_check_command.py @@ -200,7 +200,7 @@ def test_check_a_range_of_git_commits_and_failed(config, mocker): error_mock.assert_called_once() -def test_check_command_with_invalid_argment(config): +def test_check_command_with_invalid_argument(config): with pytest.raises(InvalidCommandArgumentError) as excinfo: commands.Check( config=config, From aa25391096039afdc6941a3cc8dfe0e1b3a37369 Mon Sep 17 00:00:00 2001 From: Kurt von Laven Date: Wed, 4 May 2022 23:22:07 -0700 Subject: [PATCH 3/3] feat(check): Add --allow-abort option Empty commit messages indicate to Git that the commit should be aborted. Displaying an error message when the commit is already being aborted typically only creates confusion. Add an --allow-abort argument to the check command and allow_abort config variable, both defaulting to false for backwards compatibility. When the commit message is empty, determine the outcome based on the allow_abort setting alone, ignoring the pattern. --- commitizen/cli.py | 6 ++++ commitizen/commands/check.py | 27 ++++++++++------- commitizen/defaults.py | 2 ++ docs/check.md | 15 +++++++++- docs/config.md | 2 ++ tests/commands/test_check_command.py | 45 ++++++++++++++++++++++++++-- tests/test_conf.py | 2 ++ 7 files changed, 86 insertions(+), 13 deletions(-) diff --git a/commitizen/cli.py b/commitizen/cli.py index bf751b44a4..d9f1025ba2 100644 --- a/commitizen/cli.py +++ b/commitizen/cli.py @@ -240,6 +240,12 @@ "help": "commit message that needs to be checked", "exclusive_group": "group1", }, + { + "name": ["--allow-abort"], + "action": "store_true", + "default": False, + "help": "allow empty commit messages, which typically abort a commit", + }, ], }, { diff --git a/commitizen/commands/check.py b/commitizen/commands/check.py index a32613e77f..fb79e805dd 100644 --- a/commitizen/commands/check.py +++ b/commitizen/commands/check.py @@ -26,6 +26,9 @@ def __init__(self, config: BaseConfig, arguments: Dict[str, str], cwd=os.getcwd( self.commit_msg_file: Optional[str] = arguments.get("commit_msg_file") self.commit_msg: Optional[str] = arguments.get("message") self.rev_range: Optional[str] = arguments.get("rev_range") + self.allow_abort: bool = bool( + arguments.get("allow_abort", config.settings["allow_abort"]) + ) self._valid_command_argument() @@ -33,15 +36,16 @@ def __init__(self, config: BaseConfig, arguments: Dict[str, str], cwd=os.getcwd( self.cz = factory.commiter_factory(self.config) def _valid_command_argument(self): - number_args_provided = ( - bool(self.commit_msg_file) + bool(self.commit_msg) + bool(self.rev_range) + num_exclusive_args_provided = sum( + arg is not None + for arg in (self.commit_msg_file, self.commit_msg, self.rev_range) ) - if number_args_provided == 0 and not os.isatty(0): + if num_exclusive_args_provided == 0 and not os.isatty(0): self.commit_msg: Optional[str] = sys.stdin.read() - elif number_args_provided != 1: + elif num_exclusive_args_provided != 1: raise InvalidCommandArgumentError( ( - "One and only one argument is required for check command! " + "Only one of --rev-range, --message, and --commit-msg-file is permitted by check command! " "See 'cz check -h' for more information" ) ) @@ -60,7 +64,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( [ @@ -79,7 +83,8 @@ def __call__(self): def _get_commits(self): # Get commit message from file (--commit-msg-file) - if self.commit_msg_file: + if self.commit_msg_file is not None: + # Enter this branch if commit_msg_file is "". with open(self.commit_msg_file, "r", encoding="utf-8") as commit_file: msg = commit_file.read() msg = self._filter_comments(msg) @@ -87,7 +92,8 @@ def _get_commits(self): commit_title = msg.split("\n")[0] commit_body = "\n".join(msg.split("\n")[1:]) return [git.GitCommit(rev="", title=commit_title, body=commit_body)] - elif self.commit_msg: + elif self.commit_msg is not None: + # Enter this branch if commit_msg is "". self.commit_msg = self._filter_comments(self.commit_msg) return [git.GitCommit(rev="", title="", body=self.commit_msg)] @@ -98,8 +104,9 @@ 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 not commit_msg: + return self.allow_abort if ( commit_msg.startswith("Merge") or commit_msg.startswith("Revert") diff --git a/commitizen/defaults.py b/commitizen/defaults.py index 0b217bf7f8..35a518adc9 100644 --- a/commitizen/defaults.py +++ b/commitizen/defaults.py @@ -31,6 +31,7 @@ class Settings(TypedDict, total=False): version_files: List[str] tag_format: Optional[str] bump_message: Optional[str] + allow_abort: bool changelog_file: str changelog_incremental: bool changelog_start_rev: Optional[str] @@ -56,6 +57,7 @@ class Settings(TypedDict, total=False): "version_files": [], "tag_format": None, # example v$version "bump_message": None, # bumped v$current_version to $new_version + "allow_abort": False, "changelog_file": "CHANGELOG.md", "changelog_incremental": False, "changelog_start_rev": None, diff --git a/docs/check.md b/docs/check.md index e1f0344e16..759d60d915 100644 --- a/docs/check.md +++ b/docs/check.md @@ -7,7 +7,11 @@ If you want to setup an automatic check before every git commit, please refer to [Automatically check message before commit](auto_check.md). ## Usage -There are three arguments that you can use one of them to check commit message. +There are three mutually exclusive ways to use `cz check`: + +- with `--rev-range` to check a range of pre-existing commits +- with `--message` or by piping the message to it to check a given string +- or with `--commit-msg-file` to read the commit message from a file ### Git Rev Range If you'd like to check a commit's message after it has already been created, then you can specify the range of commits to check with `--rev-range REV_RANGE`. @@ -46,3 +50,12 @@ $ cz check --commit-msg-file COMMIT_MSG_FILE In this option, COMMIT_MSG_FILE is the path of the temporal file that contains the commit message. This argument can be useful when cooperating with git hook, please check [Automatically check message before commit](auto_check.md) for more information about how to use this argument with git hook. + +### Allow Abort + +```bash +cz check --message MESSAGE --allow-abort +``` + +Empty commit messages typically instruct Git to abort a commit, so you can pass `--allow-abort` to +permit them. Since `git commit` accepts an `--allow-empty-message` flag (primarily for wrapper scripts), you may wish to disallow such commits in CI. `--allow-abort` may be used in conjunction with any of the other options. diff --git a/docs/config.md b/docs/config.md index 119db6f739..6a7838e245 100644 --- a/docs/config.md +++ b/docs/config.md @@ -131,6 +131,7 @@ commitizen: | `update_changelog_on_bump` | `bool` | `false` | Create changelog when running `cz bump` | | `annotated_tag` | `bool` | `false` | Use annotated tags instead of lightweight tags. [See difference][annotated-tags-vs-lightweight] | | `bump_message` | `str` | `None` | Create custom commit message, useful to skip ci. [See more][bump_message] | +| `allow_abort` | `bool` | `false` | Disallow empty commit messages, useful in ci. [See more][allow_abort] | | `changelog_file` | `str` | `CHANGELOG.md` | filename of exported changelog | | `changelog_incremental` | `bool` | `false` | Update changelog with the missing versions. This is good if you don't want to replace previous versions in the file. Note: when doing `cz bump --changelog` this is automatically set to `true` | | `changelog_start_rev` | `str` | `None` | Start from a given git rev to generate the changelog | @@ -141,6 +142,7 @@ commitizen: [version_files]: bump.md#version_files [tag_format]: bump.md#tag_format [bump_message]: bump.md#bump_message +[allow_abort]: check.md#allow-abort [additional-features]: https://github.com/tmbo/questionary#additional-features [customization]: customization.md [shortcuts]: customization.md#shortcut-keys diff --git a/tests/commands/test_check_command.py b/tests/commands/test_check_command.py index b9da13520a..e5f62d0a82 100644 --- a/tests/commands/test_check_command.py +++ b/tests/commands/test_check_command.py @@ -206,8 +206,9 @@ def test_check_command_with_invalid_argument(config): config=config, arguments={"commit_msg_file": "some_file", "rev_range": "HEAD~10..master"}, ) - assert "One and only one argument is required for check command!" in str( - excinfo.value + assert ( + "Only one of --rev-range, --message, and --commit-msg-file is permitted by check command!" + in str(excinfo.value) ) @@ -257,6 +258,46 @@ def test_check_command_with_invalid_message(config, mocker): error_mock.assert_called_once() +def test_check_command_with_empty_message(config, mocker): + error_mock = mocker.patch("commitizen.out.error") + check_cmd = commands.Check(config=config, arguments={"message": ""}) + + with pytest.raises(InvalidCommitMessageError): + check_cmd() + error_mock.assert_called_once() + + +def test_check_command_with_allow_abort_arg(config, mocker): + success_mock = mocker.patch("commitizen.out.success") + check_cmd = commands.Check( + config=config, arguments={"message": "", "allow_abort": True} + ) + + check_cmd() + success_mock.assert_called_once() + + +def test_check_command_with_allow_abort_config(config, mocker): + success_mock = mocker.patch("commitizen.out.success") + config.settings["allow_abort"] = True + check_cmd = commands.Check(config=config, arguments={"message": ""}) + + check_cmd() + success_mock.assert_called_once() + + +def test_check_command_override_allow_abort_config(config, mocker): + error_mock = mocker.patch("commitizen.out.error") + config.settings["allow_abort"] = True + check_cmd = commands.Check( + config=config, arguments={"message": "", "allow_abort": False} + ) + + with pytest.raises(InvalidCommitMessageError): + check_cmd() + error_mock.assert_called_once() + + def test_check_command_with_pipe_message(mocker, capsys): testargs = ["cz", "check"] mocker.patch.object(sys, "argv", testargs) diff --git a/tests/test_conf.py b/tests/test_conf.py index 786af25756..f051a1f56f 100644 --- a/tests/test_conf.py +++ b/tests/test_conf.py @@ -40,6 +40,7 @@ "version": "1.0.0", "tag_format": None, "bump_message": None, + "allow_abort": False, "version_files": ["commitizen/__version__.py", "pyproject.toml"], "style": [["pointer", "reverse"], ["question", "underline"]], "changelog_file": "CHANGELOG.md", @@ -54,6 +55,7 @@ "version": "2.0.0", "tag_format": None, "bump_message": None, + "allow_abort": False, "version_files": ["commitizen/__version__.py", "pyproject.toml"], "style": [["pointer", "reverse"], ["question", "underline"]], "changelog_file": "CHANGELOG.md",