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

feat: Validate multi-line messages #68

Merged
merged 1 commit into from
Nov 17, 2023
Merged
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
22 changes: 16 additions & 6 deletions conventional_pre_commit/format.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,13 @@ def r_delim():


def r_subject():
"""Regex str for subject line, body, footer."""
return r" .+"
"""Regex str for subject line."""
return r" .+$"


def r_body():
"""Regex str for the body"""
return r"(?P<multi>\r?\n(?P<sep>^$\r?\n)?.+)?"


def r_autosquash_prefixes():
Expand All @@ -56,18 +61,23 @@ def conventional_types(types=[]):
return types


def is_conventional(input, types=DEFAULT_TYPES, optional_scope=True):
def is_conventional(input, types=DEFAULT_TYPES, optional_scope=True, is_strict=False):
"""
Returns True if input matches Conventional Commits formatting
https://www.conventionalcommits.org

Optionally provide a list of additional custom types.
"""
types = conventional_types(types)
pattern = f"^({r_types(types)}){r_scope(optional_scope)}{r_delim()}{r_subject()}$"
regex = re.compile(pattern, re.DOTALL)
pattern = f"^({r_types(types)}){r_scope(optional_scope)}{r_delim()}{r_subject()}{r_body()}"
regex = re.compile(pattern, re.MULTILINE)

return bool(regex.match(input))
result = regex.match(input)
is_valid_subject = bool(result)
if is_valid_subject and is_strict and result.group("multi") and not result.group("sep"):
is_valid_subject = False

return is_valid_subject


def has_autosquash_prefix(input):
Expand Down
13 changes: 10 additions & 3 deletions conventional_pre_commit/hook.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def main(argv=[]):
if format.has_autosquash_prefix(message):
return RESULT_SUCCESS

if format.is_conventional(message, args.types, args.optional_scope):
if format.is_conventional(message, args.types, args.optional_scope, args.strict):
return RESULT_SUCCESS
else:
print(
Expand All @@ -64,7 +64,7 @@ def main(argv=[]):
{Colors.LBLUE}https://www.conventionalcommits.org/{Colors.YELLOW}
Conventional Commits start with one of the below types, followed by a colon,
followed by the commit message:{Colors.RESTORE}
followed by the commit subject and an optional body seperated by a blank line:{Colors.RESTORE}
{" ".join(format.conventional_types(args.types))}
Expand All @@ -78,7 +78,14 @@ def main(argv=[]):
{Colors.YELLOW}Example commit with scope in parentheses after the type for more context:{Colors.RESTORE}
fix(account): remove infinite loop"""
fix(account): remove infinite loop
{Colors.YELLOW}Example commit with a body
fix: remove infinite loop
Additional information on the issue caused by the infinite loop
"""
)
return RESULT_FAIL

Expand Down
10 changes: 10 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,13 @@ def conventional_gbk_commit_path():
@pytest.fixture
def fixup_commit_path():
return get_message_path("fixup_commit")


@pytest.fixture
def conventional_commit_bad_multi_line_path():
return get_message_path("conventional_commit_bad_multi_line")


@pytest.fixture
def conventional_commit_multi_line_path():
return get_message_path("conventional_commit_multi_line")
2 changes: 2 additions & 0 deletions tests/messages/conventional_commit_bad_multi_line
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
fix: message
no blank line
3 changes: 3 additions & 0 deletions tests/messages/conventional_commit_multi_line
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
fix: message

A blank line is there
55 changes: 53 additions & 2 deletions tests/test_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,13 +162,64 @@ def test_is_conventional__with_scope():
assert format.is_conventional(input)


def test_is_conventional__body_multiline():
def test_is_conventional__body_multiline_not_strict():
input = """feat(scope): message

more message
"""

assert format.is_conventional(input)
assert format.is_conventional(input, is_strict=False)


def test_is_conventional__body_multiline_no_body_not_strict():
input = """feat(scope): message

"""
assert format.is_conventional(input, is_strict=False)


def test_is_conventional__body_multiline_body_bad_type_strict():
input = """wrong: message

more_message
"""

assert not format.is_conventional(input, is_strict=True)


def test_is_conventional__bad_body_multiline_not_strict():
input = """feat(scope): message
more message
"""

assert format.is_conventional(input, is_strict=False)


def test_is_conventional__bad_body_multiline_strict():
input = """feat(scope): message
more message
"""

assert not format.is_conventional(input, is_strict=True)


def test_is_conventional__body_multiline_strict():
input = """feat(scope): message

more message
"""

assert format.is_conventional(input, is_strict=True)


def test_is_conventional__bad_body_multiline_paragraphs_strict():
input = """feat(scope): message
more message

more body message
"""

assert not format.is_conventional(input, is_strict=True)


@pytest.mark.parametrize("char", ['"', "'", "`", "#", "&"])
Expand Down
18 changes: 18 additions & 0 deletions tests/test_hook.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,3 +116,21 @@ def test_main_success__fail_commit(cmd, fixup_commit_path):
result = subprocess.call((cmd, "--strict", fixup_commit_path))

assert result == RESULT_FAIL


def test_main_success__conventional_commit_multi_line(cmd, conventional_commit_multi_line_path):
result = subprocess.call((cmd, conventional_commit_multi_line_path))

assert result == RESULT_SUCCESS


def test_main_success__conventional_commit_bad_multi_line(cmd, conventional_commit_bad_multi_line_path):
result = subprocess.call((cmd, conventional_commit_bad_multi_line_path))

assert result == RESULT_SUCCESS


def test_main_success__conventional_commit_bad_multi_line_strict(cmd, conventional_commit_bad_multi_line_path):
result = subprocess.call((cmd, "--strict", conventional_commit_bad_multi_line_path))

assert result == RESULT_FAIL