Skip to content
This repository has been archived by the owner on Jan 31, 2018. It is now read-only.

[bug 1108755] Add a git commit message linter #422

Merged
merged 1 commit into from Dec 18, 2014
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
47 changes: 47 additions & 0 deletions bin/hooks/lint.commit-msg
@@ -0,0 +1,47 @@
#!/bin/bash
#
# Run the commit message linters
#
# If this file is not in the `.git/hooks` directory, executing it will
# prompt to install it.

DIR=$(dirname $0)
COMMIT_MSG_FILE=$1

function lint() {
echo 'Linting the commit message...'
./bin/lint_commit_msg.py $COMMIT_MSG_FILE
LINT_STATUS=$?
if [[ $LINT_STATUS -ne 0 ]]; then
echo
echo "Lint errors found. Please fix the above and retry."
echo "Alternatively, run 'git commit --no-verify' to ignore lint errors."
exit 1
fi
}

function install() {
echo -ne "Would you like to install the commit message linter? "
while true; do
read yn
case $yn in
[Yy]* ) break;;
[Nn]* ) exit 1;;
* ) echo "Please enter 'y' or 'n'."
esac
done

GITDIR=$(git rev-parse --git-dir)
if [[ -e $GITDIR/hooks/commit-msg ]]; then
echo "You already have a git commit message hook. Bailing."
exit 1
fi

ln -s ../../bin/hooks/lint.commit-msg $GITDIR/hooks/commit-msg
}

if echo $DIR | grep -E ".git/hooks$" > /dev/null; then
lint
else
install
fi
79 changes: 79 additions & 0 deletions bin/lint_commit_msg.py
@@ -0,0 +1,79 @@
#!/usr/bin/env python

import sys
import re

import requests
from requests.exceptions import (
ConnectionError,
Timeout
)

BUG_PREFIX_REGEX = r'\[bug (\d+)\]'


def are_lines_not_more_than_79_chars(contents):
if filter(lambda x: len(x) > 79, contents):
print('Error:')
print('The commit message should not have more than '
'79 characters per line')
return False
return True


def is_bug_number_in_right_format(contents):
summary = contents[0]
if (re.compile(r'\bbug\b', flags=re.IGNORECASE).search(summary) and
re.search(r'\b#{0,1}\d+\b', summary)):
bug_format_regex = re.compile(BUG_PREFIX_REGEX,
flags=re.IGNORECASE)
valid_bug_format = bug_format_regex.match(summary)
if not valid_bug_format:
print('Specify the bug number in the format '
'[bug xxxxxxx] at the beginning of the commit summary')
return False
return True


def print_bug_info(contents):
summary = contents[0]
bug_id_regex = re.compile(BUG_PREFIX_REGEX,
flags=re.IGNORECASE)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pretty sure this regex could be pulled into a "constant" because it's used in multiple places with slightly different forms.

is_a_bug = bug_id_regex.match(summary)
if is_a_bug:
bug_id = is_a_bug.group(1)
url = 'https://bugzilla.mozilla.org/rest/bug/{}'.format(bug_id)
try:
response = requests.get(url, timeout=60.0)
response_dict = response.json()
if ('error' in response_dict and response_dict['error']):
print('Bug with id {} not found.'.format(bug_id))
return False
bug_info = response_dict['bugs'][0]
print('{} - {}'.format(bug_id,
bug_info['summary']))
print('Assigned to: {}'.format(bug_info['assigned_to']))
except ValueError:
print('Error parsing response from Bugzilla REST API')
except (ConnectionError, Timeout):
print('Unable to contact Mozilla Bugzilla')
return True

LINT_FUNCTIONS = [is_bug_number_in_right_format,
are_lines_not_more_than_79_chars,
print_bug_info]


def lint_commit_msg(commit_msg_file):
with open(commit_msg_file) as commit_contents:
commit_msg = commit_contents.readlines()
return_values = map(lambda x: x(commit_msg), LINT_FUNCTIONS)
if not all(return_values):
return 1
return 0

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Need an extra blank line here. It's probably worth running flake8 on this file.

if __name__ == '__main__':
commit_msg_file = sys.argv[1]
errors_found = lint_commit_msg(commit_msg_file)
if errors_found:
sys.exit(1)