Skip to content

Commit e0f9fe5

Browse files
Kurt-von-LavenLee-W
authored andcommitted
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.
1 parent b62ecfa commit e0f9fe5

File tree

7 files changed

+86
-13
lines changed

7 files changed

+86
-13
lines changed

commitizen/cli.py

+6
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,12 @@
240240
"help": "commit message that needs to be checked",
241241
"exclusive_group": "group1",
242242
},
243+
{
244+
"name": ["--allow-abort"],
245+
"action": "store_true",
246+
"default": False,
247+
"help": "allow empty commit messages, which typically abort a commit",
248+
},
243249
],
244250
},
245251
{

commitizen/commands/check.py

+17-10
Original file line numberDiff line numberDiff line change
@@ -26,22 +26,26 @@ def __init__(self, config: BaseConfig, arguments: Dict[str, str], cwd=os.getcwd(
2626
self.commit_msg_file: Optional[str] = arguments.get("commit_msg_file")
2727
self.commit_msg: Optional[str] = arguments.get("message")
2828
self.rev_range: Optional[str] = arguments.get("rev_range")
29+
self.allow_abort: bool = bool(
30+
arguments.get("allow_abort", config.settings["allow_abort"])
31+
)
2932

3033
self._valid_command_argument()
3134

3235
self.config: BaseConfig = config
3336
self.cz = factory.commiter_factory(self.config)
3437

3538
def _valid_command_argument(self):
36-
number_args_provided = (
37-
bool(self.commit_msg_file) + bool(self.commit_msg) + bool(self.rev_range)
39+
num_exclusive_args_provided = sum(
40+
arg is not None
41+
for arg in (self.commit_msg_file, self.commit_msg, self.rev_range)
3842
)
39-
if number_args_provided == 0 and not os.isatty(0):
43+
if num_exclusive_args_provided == 0 and not os.isatty(0):
4044
self.commit_msg: Optional[str] = sys.stdin.read()
41-
elif number_args_provided != 1:
45+
elif num_exclusive_args_provided != 1:
4246
raise InvalidCommandArgumentError(
4347
(
44-
"One and only one argument is required for check command! "
48+
"Only one of --rev-range, --message, and --commit-msg-file is permitted by check command! "
4549
"See 'cz check -h' for more information"
4650
)
4751
)
@@ -60,7 +64,7 @@ def __call__(self):
6064
ill_formated_commits = [
6165
commit
6266
for commit in commits
63-
if not Check.validate_commit_message(commit.message, pattern)
67+
if not self.validate_commit_message(commit.message, pattern)
6468
]
6569
displayed_msgs_content = "\n".join(
6670
[
@@ -79,15 +83,17 @@ def __call__(self):
7983

8084
def _get_commits(self):
8185
# Get commit message from file (--commit-msg-file)
82-
if self.commit_msg_file:
86+
if self.commit_msg_file is not None:
87+
# Enter this branch if commit_msg_file is "".
8388
with open(self.commit_msg_file, "r", encoding="utf-8") as commit_file:
8489
msg = commit_file.read()
8590
msg = self._filter_comments(msg)
8691
msg = msg.lstrip("\n")
8792
commit_title = msg.split("\n")[0]
8893
commit_body = "\n".join(msg.split("\n")[1:])
8994
return [git.GitCommit(rev="", title=commit_title, body=commit_body)]
90-
elif self.commit_msg:
95+
elif self.commit_msg is not None:
96+
# Enter this branch if commit_msg is "".
9197
self.commit_msg = self._filter_comments(self.commit_msg)
9298
return [git.GitCommit(rev="", title="", body=self.commit_msg)]
9399

@@ -98,8 +104,9 @@ def _filter_comments(self, msg: str) -> str:
98104
lines = [line for line in msg.split("\n") if not line.startswith("#")]
99105
return "\n".join(lines)
100106

101-
@staticmethod
102-
def validate_commit_message(commit_msg: str, pattern: str) -> bool:
107+
def validate_commit_message(self, commit_msg: str, pattern: str) -> bool:
108+
if not commit_msg:
109+
return self.allow_abort
103110
if (
104111
commit_msg.startswith("Merge")
105112
or commit_msg.startswith("Revert")

commitizen/defaults.py

+2
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ class Settings(TypedDict, total=False):
3131
version_files: List[str]
3232
tag_format: Optional[str]
3333
bump_message: Optional[str]
34+
allow_abort: bool
3435
changelog_file: str
3536
changelog_incremental: bool
3637
changelog_start_rev: Optional[str]
@@ -56,6 +57,7 @@ class Settings(TypedDict, total=False):
5657
"version_files": [],
5758
"tag_format": None, # example v$version
5859
"bump_message": None, # bumped v$current_version to $new_version
60+
"allow_abort": False,
5961
"changelog_file": "CHANGELOG.md",
6062
"changelog_incremental": False,
6163
"changelog_start_rev": None,

docs/check.md

+14-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@ If you want to setup an automatic check before every git commit, please refer to
77
[Automatically check message before commit](auto_check.md).
88

99
## Usage
10-
There are three arguments that you can use one of them to check commit message.
10+
There are three mutually exclusive ways to use `cz check`:
11+
12+
- with `--rev-range` to check a range of pre-existing commits
13+
- with `--message` or by piping the message to it to check a given string
14+
- or with `--commit-msg-file` to read the commit message from a file
1115

1216
### Git Rev Range
1317
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
4650

4751
In this option, COMMIT_MSG_FILE is the path of the temporal file that contains the commit message.
4852
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.
53+
54+
### Allow Abort
55+
56+
```bash
57+
cz check --message MESSAGE --allow-abort
58+
```
59+
60+
Empty commit messages typically instruct Git to abort a commit, so you can pass `--allow-abort` to
61+
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.

docs/config.md

+2
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ commitizen:
131131
| `update_changelog_on_bump` | `bool` | `false` | Create changelog when running `cz bump` |
132132
| `annotated_tag` | `bool` | `false` | Use annotated tags instead of lightweight tags. [See difference][annotated-tags-vs-lightweight] |
133133
| `bump_message` | `str` | `None` | Create custom commit message, useful to skip ci. [See more][bump_message] |
134+
| `allow_abort` | `bool` | `false` | Disallow empty commit messages, useful in ci. [See more][allow_abort] |
134135
| `changelog_file` | `str` | `CHANGELOG.md` | filename of exported changelog |
135136
| `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` |
136137
| `changelog_start_rev` | `str` | `None` | Start from a given git rev to generate the changelog |
@@ -141,6 +142,7 @@ commitizen:
141142
[version_files]: bump.md#version_files
142143
[tag_format]: bump.md#tag_format
143144
[bump_message]: bump.md#bump_message
145+
[allow_abort]: check.md#allow-abort
144146
[additional-features]: https://github.com/tmbo/questionary#additional-features
145147
[customization]: customization.md
146148
[shortcuts]: customization.md#shortcut-keys

tests/commands/test_check_command.py

+43-2
Original file line numberDiff line numberDiff line change
@@ -206,8 +206,9 @@ def test_check_command_with_invalid_argument(config):
206206
config=config,
207207
arguments={"commit_msg_file": "some_file", "rev_range": "HEAD~10..master"},
208208
)
209-
assert "One and only one argument is required for check command!" in str(
210-
excinfo.value
209+
assert (
210+
"Only one of --rev-range, --message, and --commit-msg-file is permitted by check command!"
211+
in str(excinfo.value)
211212
)
212213

213214

@@ -257,6 +258,46 @@ def test_check_command_with_invalid_message(config, mocker):
257258
error_mock.assert_called_once()
258259

259260

261+
def test_check_command_with_empty_message(config, mocker):
262+
error_mock = mocker.patch("commitizen.out.error")
263+
check_cmd = commands.Check(config=config, arguments={"message": ""})
264+
265+
with pytest.raises(InvalidCommitMessageError):
266+
check_cmd()
267+
error_mock.assert_called_once()
268+
269+
270+
def test_check_command_with_allow_abort_arg(config, mocker):
271+
success_mock = mocker.patch("commitizen.out.success")
272+
check_cmd = commands.Check(
273+
config=config, arguments={"message": "", "allow_abort": True}
274+
)
275+
276+
check_cmd()
277+
success_mock.assert_called_once()
278+
279+
280+
def test_check_command_with_allow_abort_config(config, mocker):
281+
success_mock = mocker.patch("commitizen.out.success")
282+
config.settings["allow_abort"] = True
283+
check_cmd = commands.Check(config=config, arguments={"message": ""})
284+
285+
check_cmd()
286+
success_mock.assert_called_once()
287+
288+
289+
def test_check_command_override_allow_abort_config(config, mocker):
290+
error_mock = mocker.patch("commitizen.out.error")
291+
config.settings["allow_abort"] = True
292+
check_cmd = commands.Check(
293+
config=config, arguments={"message": "", "allow_abort": False}
294+
)
295+
296+
with pytest.raises(InvalidCommitMessageError):
297+
check_cmd()
298+
error_mock.assert_called_once()
299+
300+
260301
def test_check_command_with_pipe_message(mocker, capsys):
261302
testargs = ["cz", "check"]
262303
mocker.patch.object(sys, "argv", testargs)

tests/test_conf.py

+2
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
"version": "1.0.0",
4141
"tag_format": None,
4242
"bump_message": None,
43+
"allow_abort": False,
4344
"version_files": ["commitizen/__version__.py", "pyproject.toml"],
4445
"style": [["pointer", "reverse"], ["question", "underline"]],
4546
"changelog_file": "CHANGELOG.md",
@@ -54,6 +55,7 @@
5455
"version": "2.0.0",
5556
"tag_format": None,
5657
"bump_message": None,
58+
"allow_abort": False,
5759
"version_files": ["commitizen/__version__.py", "pyproject.toml"],
5860
"style": [["pointer", "reverse"], ["question", "underline"]],
5961
"changelog_file": "CHANGELOG.md",

0 commit comments

Comments
 (0)