Hi,
I’ve created a git hook to check compliance with Commit messages guidelines.
I’m sharing it here in case anyone will find it useful.
#!/usr/bin/env python3
# Usage:
# In the .git/hooks directory, create a file named commit-msg and paste the script.
# Make the file executable by running chmod +x commit-msg.
# Requires Python 3
import sys
# [X] = Check created, [-] = Check cannot be created, [?] = Check not created
# [X] The commit subject and body must be separated by a blank line.
# [X] The commit subject must start with a capital letter.
# [X] The commit subject must not be longer than 72 characters.
# [X] The commit subject must not end with a period.
# [X] The commit body must not contain more than 72 characters per line.
# [?] The commit subject or body must not contain Emojis.
# [-] Commits that change 30 or more lines across at least 3 files should describe these changes in the commit body.
# [?] Use issues, milestones, and merge requests’ full URLs instead of short references,
# as they are displayed as plain text outside of GitLab.
# [-] The merge request should not contain more than 10 commit messages.
# [X] The commit subject should contain at least 3 words.
# List of the rules as described in
# https://docs.gitlab.com/ee/development/contributing/merge_request_workflow.html#commit-messages-guidelines
# Rules that don't have a check function should be marked as True otherwise the script will always fail.
rules = [
{"description": "The commit subject and body must be separated by a blank line.", "status": False},
{"description": "The commit subject must start with a capital letter.", "status": False},
{"description": "The commit subject must not be longer than 72 characters.", "status": False},
{"description": "The commit subject must not end with a period.", "status": False},
{"description": "The commit body must not contain more than 72 characters per line.", "status": False},
{"description": "The commit subject or body must not contain Emojis.", "status": True},
{"description": "Commits that change 30 or more lines across at least 3 files should describe these changes in "
"the commit body.", "status": True},
{"description": "Use issues, milestones, and merge requests’ full URLs instead of short references, as they are "
"displayed as plain text outside of GitLab.", "status": True},
{"description": "The merge request should not contain more than 10 commit messages.", "status": True},
{"description": "The commit subject should contain at least 3 words.", "status": False},
]
def check_commit_message(message):
# Split the message into lines
message_lines = message.splitlines()
# First line is the subject, second line is the separator, the rest is the body
subject = message_lines[0]
body = message_lines[2:]
# Check that the second line is a blank line as described in the rule #1
rules[0]['status'] = message_lines[1] == ''
# Subject rules:
# Check that the subject starts with a capital letter as described in the rule #2
rules[1]['status'] = subject[0].isupper()
# Check that the subject is not longer than 72 characters as described in the rule #3
rules[2]['status'] = len(subject) <= 72
# Check that the subject does not end with a period as described in the rule #4
rules[3]['status'] = not subject.endswith('.')
# Check that the subject contains at least 3 words as described in the rule #9
# This is done by splitting the subject using space as the delimiter and counting the words.
# A word is defined (here) as a string that contains at least one letter or number.
word_counter = 0
for word in subject.split():
has_letter = any(char.isalpha() for char in word)
has_number = any(char.isdigit() for char in word)
if has_letter or has_number:
word_counter += 1
rules[9]['status'] = word_counter >= 3
# Check that the body does not contain more than 72 characters per line as described in the rule #5
for line in body:
if len(line) > 72:
break
rules[4]['status'] = True
# Print the results
fail = False
for rule in rules:
if rule['status']:
# Will print met rules in white
print(f"\033[97m[X] {rule['description']}\033[0m")
else:
# Will print unmet rules in red
print(f"\033[91m[ ] {rule['description']}\033[0m")
fail = True
return fail
if __name__ == '__main__':
# Read the commit message from the file
commit_message_file = sys.argv[1]
with open(commit_message_file, "r") as f:
commit_message = f.read().strip()
# Will exit with 0 if the commit message is correct, otherwise will exit with 1
exit(check_commit_message(commit_message))