Skip to content
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
66 changes: 39 additions & 27 deletions commit_check/author.py
Original file line number Diff line number Diff line change
@@ -1,34 +1,46 @@
"""Check git author name and email"""
import re
from commit_check import YELLOW, RESET_COLOR, PASS, FAIL
from commit_check.util import get_commit_info, has_commits, print_error_header, print_error_message, print_suggestion
from commit_check.util import (
get_commit_info,
has_commits,
_find_check,
_print_failure,
)


_AUTHOR_FORMAT_MAP = {
"author_name": "an",
"author_email": "ae",
}


def _get_author_value(check_type: str) -> str:
"""Fetch the author value from git for the given check type."""
format_str = _AUTHOR_FORMAT_MAP.get(check_type, "")
return str(get_commit_info(format_str))


def check_author(checks: list, check_type: str) -> int:
"""Validate author name or email according to configured regex."""
if has_commits() is False:
return PASS # pragma: no cover

for check in checks:
if check['check'] == check_type:
if check['regex'] == "":
print(
f"{YELLOW}Not found regex for {check_type}. skip checking.{RESET_COLOR}",
)
return PASS
if check_type == "author_name":
format_str = "an"
if check_type == 'author_email':
format_str = "ae"
config_value = str(get_commit_info(format_str))
result = re.match(check['regex'], config_value)
if result is None:
if not print_error_header.has_been_called:
print_error_header()
print_error_message(
check['check'], check['regex'],
check['error'], config_value,
)
if check['suggest']:
print_suggestion(check['suggest'])
return FAIL
return PASS
return PASS # pragma: no cover

check = _find_check(checks, check_type)
if not check:
return PASS

# If regex is empty, skip without fetching author info
regex = check.get("regex", "")
if regex == "":
print(f"{YELLOW}Not found regex for {check_type}. skip checking.{RESET_COLOR}")
return PASS

value = _get_author_value(check_type)

if re.match(regex, value):
return PASS

_print_failure(check, regex, value)

return FAIL
80 changes: 38 additions & 42 deletions commit_check/branch.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,27 @@
"""Check git branch naming convention."""
import re
from commit_check import YELLOW, RESET_COLOR, PASS, FAIL
from commit_check.util import get_branch_name, git_merge_base, print_error_header, print_error_message, print_suggestion, has_commits
from commit_check.util import _find_check, _print_failure, get_branch_name, git_merge_base, has_commits


def check_branch(checks: list) -> int:
for check in checks:
if check['check'] == 'branch':
if check['regex'] == "":
print(
f"{YELLOW}Not found regex for branch naming. skip checking.{RESET_COLOR}",
)
return PASS
branch_name = get_branch_name()
result = re.match(check['regex'], branch_name)
if result is None:
if not print_error_header.has_been_called:
print_error_header() # pragma: no cover
print_error_message(
check['check'], check['regex'],
check['error'], branch_name,
)
if check['suggest']:
print_suggestion(check['suggest'])
return FAIL
return PASS
check = _find_check(checks, 'branch')
if not check:
return PASS

regex = check.get('regex', "")
if regex == "":
print(
f"{YELLOW}Not found regex for branch naming. skip checking.{RESET_COLOR}",
)
return PASS

branch_name = get_branch_name()
if re.match(regex, branch_name):
return PASS

_print_failure(check, regex, branch_name)
return FAIL


def check_merge_base(checks: list) -> int:
Expand All @@ -36,24 +33,23 @@ def check_merge_base(checks: list) -> int:
if has_commits() is False:
return PASS # pragma: no cover

for check in checks:
if check['check'] == 'merge_base':
if check['regex'] == "":
print(
f"{YELLOW}Not found target branch for checking merge base. skip checking.{RESET_COLOR}",
)
return PASS
target_branch = check['regex'] if "origin/" in check['regex'] else f"origin/{check['regex']}"
current_branch = get_branch_name()
result = git_merge_base(target_branch, current_branch)
if result != 0:
if not print_error_header.has_been_called:
print_error_header() # pragma: no cover
print_error_message(
check['check'], check['regex'],
check['error'], current_branch,
)
if check['suggest']:
print_suggestion(check['suggest'])
return FAIL
return PASS
# locate merge_base rule, if any
check = _find_check(checks, 'merge_base')
if not check:
return PASS

regex = check.get('regex', "")
if regex == "":
print(
f"{YELLOW}Not found target branch for checking merge base. skip checking.{RESET_COLOR}",
)
return PASS

target_branch = regex if "origin/" in regex else f"origin/{regex}"
current_branch = get_branch_name()
result = git_merge_base(target_branch, current_branch)
if result == 0:
return PASS

_print_failure(check, regex, current_branch)
return FAIL
155 changes: 66 additions & 89 deletions commit_check/commit.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,20 @@
import re
from pathlib import PurePath
from commit_check import YELLOW, RESET_COLOR, PASS, FAIL
from commit_check.util import cmd_output, get_commit_info, print_error_header, print_error_message, print_suggestion, has_commits
from commit_check.util import _find_check, _print_failure, cmd_output, get_commit_info, has_commits
from commit_check.imperatives import IMPERATIVES


def _load_imperatives() -> set:
"""Load imperative verbs from imperatives module."""
return IMPERATIVES

def _ensure_msg_file(commit_msg_file: str | None) -> str:
"""Return a commit message file path, falling back to the default when empty."""
if not commit_msg_file:
return get_default_commit_msg_file()
return commit_msg_file


def get_default_commit_msg_file() -> str:
"""Get the default commit message file."""
Expand All @@ -26,118 +32,89 @@ def read_commit_msg(commit_msg_file) -> str:
# Commit message is composed by subject and body
return str(get_commit_info("s") + "\n\n" + get_commit_info("b"))


def check_commit_msg(checks: list, commit_msg_file: str = "") -> int:
"""Check commit message against the provided checks."""
if has_commits() is False:
return PASS # pragma: no cover
return PASS # pragma: no cover

if commit_msg_file is None or commit_msg_file == "":
commit_msg_file = get_default_commit_msg_file()
check = _find_check(checks, 'message')
if not check:
return PASS # pragma: no cover

commit_msg = read_commit_msg(commit_msg_file)
regex = check.get('regex', "")
if regex == "":
print(f"{YELLOW}Not found regex for commit message. skip checking.{RESET_COLOR}")
return PASS

for check in checks:
if check['check'] == 'message':
if check['regex'] == "":
print(
f"{YELLOW}Not found regex for commit message. skip checking.{RESET_COLOR}",
)
return PASS
path = _ensure_msg_file(commit_msg_file)
commit_msg = read_commit_msg(path)

result = re.match(check['regex'], commit_msg)
if result is None:
if not print_error_header.has_been_called:
print_error_header() # pragma: no cover
print_error_message(
check['check'], check['regex'],
check['error'], commit_msg,
)
if check['suggest']:
print_suggestion(check['suggest'])
return FAIL
if re.match(regex, commit_msg):
return PASS

return PASS
_print_failure(check, regex, commit_msg)
return FAIL


def check_commit_signoff(checks: list, commit_msg_file: str = "") -> int:
if has_commits() is False:
return PASS # pragma: no cover
return PASS # pragma: no cover

if commit_msg_file is None or commit_msg_file == "":
commit_msg_file = get_default_commit_msg_file()
check = _find_check(checks, 'commit_signoff')
if not check:
return PASS # pragma: no cover

for check in checks:
if check['check'] == 'commit_signoff':
if check['regex'] == "":
print(
f"{YELLOW}Not found regex for commit signoff. skip checking.{RESET_COLOR}",
)
return PASS
regex = check.get('regex', "")
if regex == "":
print(f"{YELLOW}Not found regex for commit signoff. skip checking.{RESET_COLOR}")
return PASS

commit_msg = read_commit_msg(commit_msg_file)
path = _ensure_msg_file(commit_msg_file)
commit_msg = read_commit_msg(path)

# Extract the subject line (first line of commit message)
subject = commit_msg.split('\n')[0].strip()
# Extract the subject line (first line of commit message)
subject = commit_msg.split('\n')[0].strip()

# Skip if merge commit
if subject.startswith('Merge'):
return PASS
# Skip if merge commit
if subject.startswith('Merge'):
return PASS

commit_hash = get_commit_info("H")
result = re.search(check['regex'], commit_msg)
if result is None:
if not print_error_header.has_been_called:
print_error_header() # pragma: no cover
print_error_message(
check['check'], check['regex'],
check['error'], commit_hash,
)
if check['suggest']:
print_suggestion(check['suggest'])
return FAIL
commit_hash = get_commit_info("H")
if re.search(regex, commit_msg):
return PASS

return PASS
_print_failure(check, regex, commit_hash)
return FAIL


def check_imperative(checks: list, commit_msg_file: str = "") -> int:
"""Check if commit message uses imperative mood."""
if has_commits() is False:
return PASS # pragma: no cover

if commit_msg_file is None or commit_msg_file == "":
commit_msg_file = get_default_commit_msg_file()

for check in checks:
if check['check'] == 'imperative':
commit_msg = read_commit_msg(commit_msg_file)

# Extract the subject line (first line of commit message)
subject = commit_msg.split('\n')[0].strip()

# Skip if empty or merge commit
if not subject or subject.startswith('Merge'):
return PASS

# For conventional commits, extract description after the colon
if ':' in subject:
description = subject.split(':', 1)[1].strip()
else:
description = subject

# Check if the description uses imperative mood
if not _is_imperative(description):
if not print_error_header.has_been_called:
print_error_header() # pragma: no cover
print_error_message(
check['check'], 'imperative mood pattern',
check['error'], subject,
)
if check['suggest']:
print_suggestion(check['suggest'])
return FAIL

return PASS
return PASS # pragma: no cover

check = _find_check(checks, 'imperative')
if not check:
return PASS

path = _ensure_msg_file(commit_msg_file)
commit_msg = read_commit_msg(path)

# Extract the subject line (first line of commit message)
subject = commit_msg.split('\n')[0].strip()

# Skip if empty or merge commit
if not subject or subject.startswith('Merge'):
return PASS

# For conventional commits, extract description after the colon
description = subject.split(':', 1)[1].strip() if ':' in subject else subject

# Check if the description uses imperative mood
if _is_imperative(description):
return PASS

_print_failure(check, 'imperative mood pattern', subject)
return FAIL


def _is_imperative(description: str) -> bool:
Expand Down
Loading
Loading