Rafidaslam has uploaded a new change for review. (
https://gerrit.wikimedia.org/r/404155 )
Change subject: Make MessageValidator an abstract class
......................................................................
Make MessageValidator an abstract class
Make MessageValidator an abstract class so we can create
another MessageValidator for other remote origin outside
gerrit.
Change-Id: I862bf42561eb436258951947c8c4a1233a3416c5
---
M commit_message_validator/__init__.py
A commit_message_validator/message_validator.py
A commit_message_validator/tests/__init__.py
A commit_message_validator/tests/data/bug_in_header.msg
A commit_message_validator/tests/data/bug_in_header.out
A commit_message_validator/tests/data/cherry_pick_not_on_the_last_line.msg
A commit_message_validator/tests/data/cherry_pick_not_on_the_last_line.out
A commit_message_validator/tests/data/unexpected_line_in_footers.msg
A commit_message_validator/tests/data/unexpected_line_in_footers.out
A commit_message_validator/tests/test_GerritMessageValidator.py
A commit_message_validator/tests/test_message_validator.py
A commit_message_validator/validators/GerritMessageValidator.py
A commit_message_validator/validators/GlobalMessageValidator.py
A commit_message_validator/validators/__init__.py
14 files changed, 496 insertions(+), 189 deletions(-)
git pull
ssh://gerrit.wikimedia.org:29418/integration/commit-message-validator
refs/changes/55/404155/1
diff --git a/commit_message_validator/__init__.py
b/commit_message_validator/__init__.py
index cf15256..56d12bf 100644
--- a/commit_message_validator/__init__.py
+++ b/commit_message_validator/__init__.py
@@ -24,198 +24,12 @@
from __future__ import print_function
import os
-import re
import subprocess
import sys
+from .validators.GerritMessageValidator import GerritMessageValidator
+
__version__ = '0.5.2'
-
-RE_BUGID = re.compile('^T[0-9]+$')
-RE_CHANGEID = re.compile('^I[a-f0-9]{40}$')
-RE_SUBJECT_BUG_OR_TASK = re.compile(r'^(bug|T?\d+)', re.IGNORECASE)
-RE_URL = re.compile(r'^<?https?://\S+>?$', re.IGNORECASE)
-RE_FOOTER = re.compile(
- r'^(?P<name>[a-z]\S+):(?P<ws>\s*)(?P<value>.*)$', re.IGNORECASE)
-RE_CHERRYPICK = re.compile(r'^\(cherry picked from commit [0-9a-fA-F]{40}\)$')
-
-# Header-like lines that we are interested in validating
-CORRECT_FOOTERS = [
- 'Acked-by',
- 'Bug',
- 'Cc',
- 'Change-Id',
- 'Co-Authored-by',
- 'Depends-On',
- 'Requested-by',
- 'Reported-by',
- 'Reviewed-by',
- 'Signed-off-by',
- 'Suggested-by',
- 'Tested-by',
- 'Thanks',
-]
-FOOTERS = dict((footer.lower(), footer) for footer in CORRECT_FOOTERS)
-
-BEFORE_CHANGE_ID = [
- 'bug',
- 'closes',
- 'depends-on',
- 'fixes',
- 'task',
-]
-
-# Invalid footer name to expected name mapping
-BAD_FOOTERS = {
- 'closes': 'bug',
- 'fixes': 'bug',
- 'task': 'bug',
-}
-
-
-def is_valid_bug_id(s):
- return RE_BUGID.match(s)
-
-
-def is_valid_change_id(s):
- """A Gerrit change id is a 40 character hex string prefixed with 'I'."""
- return RE_CHANGEID.match(s)
-
-
-class MessageValidator(object):
-
- """Iterator to check a commit message line for errors.
-
- Checks:
- - First line <=80 characters
- - Second line blank
- - No line >100 characters (unless it is only a URL)
- - Footer lines ("Foo: ...") are capitalized and have a space after the ':'
- - "Bug: " is followed by one task id ("Tnnnn")
- - "Depends-On:" is followed by one change id ("I...")
- - "Change-Id:" is followed one change id ("I...")
- - No "Task: ", "Fixes: ", "Closes: " lines
- """
-
- def __init__(self, lines):
- self._lines = lines
- self._first_changeid = False
- self._in_footers = False
-
- self._generator = self._check_generator()
-
- def _check_line(self, lineno):
- line = self._lines[lineno]
- # First line <=80
- if lineno == 0:
- if len(line) > 80:
- yield "First line should be <=80 characters"
- m = RE_SUBJECT_BUG_OR_TASK.match(line)
- if m:
- yield "Do not define bug in the header"
-
- # Second line blank
- elif lineno == 1:
- if line:
- yield "Second line should be empty"
-
- # No line >100 unless it is all a URL
- elif len(line) > 100 and not RE_URL.match(line):
- yield "Line should be <=100 characters"
-
- if not line:
- if self._in_footers:
- yield "Unexpected blank line"
- return
-
- # Look for and validate footer lines
- m = RE_FOOTER.match(line)
- if m:
- name = m.group('name')
- normalized_name = name.lower()
- ws = m.group('ws')
- value = m.group('value')
-
- if normalized_name in BAD_FOOTERS:
- # Treat as the correct name for the rest of the rules
- normalized_name = BAD_FOOTERS[normalized_name]
-
- if normalized_name not in FOOTERS:
- if self._in_footers:
- yield "Unexpected line in footers"
- else:
- # Meh. Not a name we care about
- return
- else:
- if lineno > 0 and not self._lines[lineno - 1]:
- self._in_footers = True
- elif not self._in_footers:
- yield "Expected '{0}:' to be in footer".format(name)
-
- correct_name = FOOTERS[normalized_name]
- if correct_name != name:
- yield "Use '{0}:' not '{1}:'".format(correct_name, name)
-
- if normalized_name == 'bug':
- if not is_valid_bug_id(value):
- yield "Bug: value must be a single phabricator task ID"
-
- elif normalized_name == 'depends-on':
- if not is_valid_change_id(value):
- yield "Depends-On: value must be a single Gerrit change id"
-
- elif normalized_name == 'change-id':
- if not is_valid_change_id(value):
- yield "Change-Id: value must be a single Gerrit change id"
- if self._first_changeid is not False:
- yield ("Extra Change-Id found, first at "
- "{0}".format(self._first_changeid))
- else:
- self._first_changeid = lineno + 1
-
- if (normalized_name in BEFORE_CHANGE_ID and
- self._first_changeid is not False):
- yield ("Expected '{0}:' to come before Change-Id on line "
- "{1}").format(name, self._first_changeid)
-
- if ws != ' ':
- yield "Expected one space after '%s:'" % name
-
- elif self._in_footers:
- # if it wasn't a footer (not a match) but it is in the footers
- cherry_pick = RE_CHERRYPICK.match(line)
- if cherry_pick:
- if lineno < len(self._lines) - 1:
- yield "Cherry pick line is not the last line"
- else:
- yield "Expected footer line to follow format of 'Name: ...'"
-
- def _check_global(self):
- """All checks that are done after the line checks."""
- if len(self._lines) < 3:
- yield "Expected at least 3 lines"
-
- if self._first_changeid is False:
- yield "Expected Change-Id"
-
- def _check_generator(self):
- """A generator returning each error and line number."""
- for lineno in range(len(self._lines)):
- for e in self._check_line(lineno):
- yield lineno + 1, e
-
- for e in self._check_global():
- yield len(self._lines), e
-
- def __iter__(self):
- return self
-
- def __next__(self):
- """Return the next error of the generator."""
- return next(self._generator)
-
- def next(self):
- # For Python 2 support
- return self.__next__()
def check_message(lines):
@@ -230,7 +44,7 @@
- Any "Bug:" and "Depends-On:" lines come before "Change-Id:"
- "(cherry picked from commit ...)" is last line in footer if present
"""
- validator = MessageValidator(lines)
+ validator = GerritMessageValidator(lines)
errors = ["Line {0}: {1}".format(lineno, error)
for lineno, error in validator]
diff --git a/commit_message_validator/message_validator.py
b/commit_message_validator/message_validator.py
new file mode 100644
index 0000000..f31218d
--- /dev/null
+++ b/commit_message_validator/message_validator.py
@@ -0,0 +1,83 @@
+
+
+class MessageValidator(object):
+ """
+ IterableMessageValidator is supposed to make a MessageValidator that
+ yields a line number and the error message.
+
+ A class that implements this class, should implement:
+ - `check_line()`, that yields the error message of the checked line,
+ and optional (can be implemented or not):
+ - `check_global()` that yields the line number and the error message.
+
+ See `HelperMessageValdator` and `GerritMessageValidator` for the
+ implementation of `check_line()`, and `check_global()`
+ methods.
+
+ Example usage:
+ >>> lines = ['Title', 'This should be empty', 'Body']
+ >>> for lineno, msg in GerritMessageValidator(lines):
+ ... print('{0} {1}'.format(lineno, msg))
+ ...
+ 2 Second line should be empty
+ 3 Expected Change-Id
+
+ """
+
+ def __init__(self, lines):
+ """
+ Constructor for MessageValidator.
+
+ :param lines: list of lines from a commit message that will be checked.
+ """
+ self._lines = lines
+ self._generator = self._check_generator()
+
+ def check_line(self, lineno):
+ """
+ A function that will be called to check the commit message.
+ This function should yields a string that contain description
+ of what error that occured on `lineno`.
+
+ :param lineno: int, line number that's being checked right now.
+ """
+ raise NotImplementedError(
+ '`check_line()` should be implemented in {0}'.format(
+ type(self).__name__))
+
+ def check_global(self):
+ """
+ All checks that are done after the line checks.
+ This function should yields a string that contain description
+ of what error that is occured.
+ """
+ raise NotImplementedError(
+ '`check_global()` isn\'t implemented in {0} but called'.format(
+ type(self).__name__))
+
+ def _check_generator(self):
+ """
+ A generator returning each error and line number
+ """
+ for lineno in range(len(self._lines)):
+ for e in self.check_line(lineno):
+ yield lineno + 1, e
+
+ try:
+ for e in self.check_global():
+ yield len(self._lines), e
+ except NotImplementedError:
+ pass
+
+ def __iter__(self):
+ return self
+
+ def __next__(self):
+ """
+ Return the next error of the generator
+ """
+ return next(self._generator)
+
+ def next(self):
+ # For Python 2 support
+ return self.__next__()
diff --git a/commit_message_validator/tests/__init__.py
b/commit_message_validator/tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/commit_message_validator/tests/__init__.py
diff --git a/commit_message_validator/tests/data/bug_in_header.msg
b/commit_message_validator/tests/data/bug_in_header.msg
new file mode 100644
index 0000000..c551bda
--- /dev/null
+++ b/commit_message_validator/tests/data/bug_in_header.msg
@@ -0,0 +1,5 @@
+T1234 Fix
+
+Should fail because a bug id is written in the header
+
+Change-Id: I395fd614bfa8bcfc46a35f955fb77ec6f03f7a01
diff --git a/commit_message_validator/tests/data/bug_in_header.out
b/commit_message_validator/tests/data/bug_in_header.out
new file mode 100644
index 0000000..d0f1d67
--- /dev/null
+++ b/commit_message_validator/tests/data/bug_in_header.out
@@ -0,0 +1,5 @@
+commit-message-validator v%version%
+The following errors were found:
+Line 1: Do not define bug in the header
+Please review <https://www.mediawiki.org/wiki/Gerrit/Commit_message_guidelines>
+and update your commit message accordingly
diff --git
a/commit_message_validator/tests/data/cherry_pick_not_on_the_last_line.msg
b/commit_message_validator/tests/data/cherry_pick_not_on_the_last_line.msg
new file mode 100644
index 0000000..4bd1e4a
--- /dev/null
+++ b/commit_message_validator/tests/data/cherry_pick_not_on_the_last_line.msg
@@ -0,0 +1,6 @@
+This is a commit header
+
+This is a commit body
+
+(cherry picked from commit a24ca11e277afd8b5259d44b2d645d4dbb99502f)
+Change-Id: If89d24838e326fe25fe867d02181eebcfbb0e196
diff --git
a/commit_message_validator/tests/data/cherry_pick_not_on_the_last_line.out
b/commit_message_validator/tests/data/cherry_pick_not_on_the_last_line.out
new file mode 100644
index 0000000..ecbfa6a
--- /dev/null
+++ b/commit_message_validator/tests/data/cherry_pick_not_on_the_last_line.out
@@ -0,0 +1,5 @@
+commit-message-validator v%version%
+The following errors were found:
+Line 5: Cherry pick line is not the last line
+Please review <https://www.mediawiki.org/wiki/Gerrit/Commit_message_guidelines>
+and update your commit message accordingly
diff --git a/commit_message_validator/tests/data/unexpected_line_in_footers.msg
b/commit_message_validator/tests/data/unexpected_line_in_footers.msg
new file mode 100644
index 0000000..fd7e694
--- /dev/null
+++ b/commit_message_validator/tests/data/unexpected_line_in_footers.msg
@@ -0,0 +1,7 @@
+This is a commit header
+
+Commit body
+
+Bug: T1234
+Err: Unexpected line
+Change-Id: If89d24838e326fe25fe867d02181eebcfbb0e196
diff --git a/commit_message_validator/tests/data/unexpected_line_in_footers.out
b/commit_message_validator/tests/data/unexpected_line_in_footers.out
new file mode 100644
index 0000000..60f94c3
--- /dev/null
+++ b/commit_message_validator/tests/data/unexpected_line_in_footers.out
@@ -0,0 +1,5 @@
+commit-message-validator v%version%
+The following errors were found:
+Line 6: Unexpected line in footers
+Please review <https://www.mediawiki.org/wiki/Gerrit/Commit_message_guidelines>
+and update your commit message accordingly
diff --git a/commit_message_validator/tests/test_GerritMessageValidator.py
b/commit_message_validator/tests/test_GerritMessageValidator.py
new file mode 100644
index 0000000..df376a4
--- /dev/null
+++ b/commit_message_validator/tests/test_GerritMessageValidator.py
@@ -0,0 +1,31 @@
+import unittest
+
+from commit_message_validator.validators.GerritMessageValidator import (
+ GerritMessageValidator, CommitMessageContext
+)
+
+
+class GerritMessageValidatorTest(unittest.TestCase):
+
+ def test_context_handler(self):
+ lines = [
+ 'Commit header',
+ '',
+ 'Commit body message',
+ '',
+ 'Change-Id: I00d0f7c3b294c3ddc656f9a5447df89c63142203'
+ ]
+
+ gerrit_mv = GerritMessageValidator(lines)
+
+ expected_result = [
+ CommitMessageContext.HEADER,
+ CommitMessageContext.BODY,
+ CommitMessageContext.BODY,
+ CommitMessageContext.BODY,
+ CommitMessageContext.FOOTER
+ ]
+
+ result = [gerrit_mv.get_context(lineno)
+ for lineno in range(len(lines))]
+ self.assertEqual(expected_result, result)
diff --git a/commit_message_validator/tests/test_message_validator.py
b/commit_message_validator/tests/test_message_validator.py
new file mode 100644
index 0000000..5defaa9
--- /dev/null
+++ b/commit_message_validator/tests/test_message_validator.py
@@ -0,0 +1,98 @@
+import re
+import unittest
+
+
+from commit_message_validator.message_validator import MessageValidator
+
+
+class NoCheckLineMessageValidator(MessageValidator):
+
+ def __init__(self, lines):
+ super(NoCheckLineMessageValidator, self).__init__(lines)
+
+
+class JustTestMessageValidator(MessageValidator):
+
+ def __init__(self, lines):
+ super(JustTestMessageValidator, self).__init__(lines)
+
+ def check_line(self, lineno):
+ if lineno == 1:
+ yield 'Error on line 2'
+ elif lineno == 3:
+ yield 'Error on line 4'
+
+ def check_global(self):
+ yield 'From global check'
+
+
+class NoCheckGlobalMessageValidator(MessageValidator):
+
+ def __init__(self, lines):
+ super(NoCheckGlobalMessageValidator, self).__init__(lines)
+
+ def check_line(self, lineno):
+ if lineno == 1:
+ yield 'Error on line 2'
+ elif lineno == 3:
+ yield 'Error on line 4'
+
+
+class MessageValidatorTest(unittest.TestCase):
+
+ def test_check_line_not_implemented(self):
+ # If 'check_line()` is not implemented, the method should raise
+ # NotImplementedError.
+ lines = ['This is a line']
+ no_check_line_mv = NoCheckLineMessageValidator(lines)
+
+ with self.assertRaisesRegex(
+ NotImplementedError,
+ re.escape('`check_line()` should be '
+ 'implemented in NoCheckLineMessageValidator')):
+ no_check_line_mv.check_line(0)
+
+ def test_check_global_not_implemented_but_called(self):
+ # If 'check_global()` is not implemented, but called,
+ # the method should raise NotImplementedError.
+ lines = ['This is a line']
+ no_check_line_mv = NoCheckLineMessageValidator(lines)
+
+ with self.assertRaisesRegex(
+ NotImplementedError,
+ re.escape('`check_global()` isn\'t '
+ 'implemented in NoCheckLineMessageValidator but '
+ 'called'
+ )):
+ no_check_line_mv.check_global()
+
+ def test_iterate_iterable_message_validator(self):
+ lines = ['This is a line', '2nd line', '3rd line', '4th line', '5th']
+
+ expected_result = [(lineno, msg)
+ for lineno, msg in
+ JustTestMessageValidator(lines)]
+ self.assertEqual(
+ expected_result,
+ [(2, 'Error on line 2'),
+ (4, 'Error on line 4'),
+ (5, 'From global check')])
+
+ def test_iterate_iterable_message_validator_no_check_global(self):
+ lines = ['This is a line', '2nd line', '3rd line', '4th line', '5th']
+
+ expected_result = [(lineno, msg)
+ for lineno, msg in
+ NoCheckGlobalMessageValidator(lines)]
+ self.assertEqual(
+ expected_result,
+ [(2, 'Error on line 2'),
+ (4, 'Error on line 4')])
+
+ def test_iterate_with_next_method(self):
+ lines = ['This is a line', '2nd line', '3rd line', '4th line', '5th']
+
+ just_test_mv = JustTestMessageValidator(lines)
+ self.assertEqual(
+ (2, 'Error on line 2'),
+ just_test_mv.next())
diff --git a/commit_message_validator/validators/GerritMessageValidator.py
b/commit_message_validator/validators/GerritMessageValidator.py
new file mode 100644
index 0000000..63672dc
--- /dev/null
+++ b/commit_message_validator/validators/GerritMessageValidator.py
@@ -0,0 +1,200 @@
+import re
+
+from enum import Enum
+
+from commit_message_validator.message_validator import MessageValidator
+from .GlobalMessageValidator import GlobalMessageValidator
+
+RE_CHERRYPICK = re.compile(r'^\(cherry picked from commit [0-9a-fA-F]{40}\)$')
+RE_GERRIT_CHANGEID = re.compile('^I[a-f0-9]{40}$')
+RE_GERRIT_FOOTER = re.compile(
+ r'^(?P<name>[a-z]\S+):(?P<ws>\s*)(?P<value>.*)$', re.IGNORECASE)
+RE_PHABRICATOR_BUGID = re.compile('^T[0-9]+$')
+RE_SUBJECT_BUG_OR_TASK = re.compile(r'^(bug|T?\d+)', re.IGNORECASE)
+
+# Header-like lines that we are interested in validating
+CORRECT_FOOTERS = [
+ 'Acked-by',
+ 'Bug',
+ 'Cc',
+ 'Change-Id',
+ 'Co-Authored-by',
+ 'Depends-On',
+ 'Requested-by',
+ 'Reported-by',
+ 'Reviewed-by',
+ 'Signed-off-by',
+ 'Suggested-by',
+ 'Tested-by',
+ 'Thanks',
+]
+FOOTERS = dict((footer.lower(), footer) for footer in CORRECT_FOOTERS)
+
+BEFORE_CHANGE_ID = [
+ 'bug',
+ 'closes',
+ 'depends-on',
+ 'fixes',
+ 'task',
+]
+
+# Invalid footer name to expected name mapping
+BAD_FOOTERS = {
+ 'closes': 'bug',
+ 'fixes': 'bug',
+ 'task': 'bug',
+}
+
+
+def is_valid_bug_id(s):
+ return RE_PHABRICATOR_BUGID.match(s)
+
+
+def is_valid_change_id(s):
+ """A Gerrit change id is a 40 character hex string prefixed with 'I'."""
+ return RE_GERRIT_CHANGEID.match(s)
+
+
+class CommitMessageContext(Enum):
+ HEADER = 1
+ BODY = 2
+ FOOTER = 3
+
+
+class GerritMessageValidator(MessageValidator):
+ """
+ An iterator to validate Gerrit remote repo commit message.
+
+ Checks:
+ - First line <=80 characters
+ - Second line blank
+ - No line >100 characters (unless it is only a URL)
+ - Footer lines ("Foo: ...") are capitalized and have a space after the ':'
+ - "Bug: " is followed by one task id ("Tnnnn")
+ - "Depends-On:" is followed by one change id ("I...")
+ - "Change-Id:" is followed one change id ("I...")
+ - No "Task: ", "Fixes: ", "Closes: " lines
+ """
+
+ def __init__(self, lines):
+ """
+ MessageValidator for Gerrit remote origin.
+
+ :param lines: list of lines from the commit message that will be
+ checked.
+ """
+ super(GerritMessageValidator, self).__init__(lines)
+ self._global_mv = GlobalMessageValidator(lines)
+
+ self._commit_message_context = None
+ self._first_changeid = False
+
+ def check_line(self, lineno):
+ yield from self._global_mv.check_line(lineno)
+
+ line_context = self.get_context(lineno)
+ line = self._lines[lineno]
+
+ if (line_context is CommitMessageContext.HEADER and
+ RE_SUBJECT_BUG_OR_TASK.match(line)):
+ yield "Do not define bug in the header"
+
+ if (not line and
+ line_context is CommitMessageContext.FOOTER):
+ yield "Unexpected blank line"
+
+ gerrit_footer_match = RE_GERRIT_FOOTER.match(line)
+ if gerrit_footer_match:
+ name = gerrit_footer_match.group('name')
+ normalized_name = name.lower()
+ ws = gerrit_footer_match.group('ws')
+ value = gerrit_footer_match.group('value')
+
+ if normalized_name in BAD_FOOTERS:
+ # Treat as the correct name for the rest of the rules
+ normalized_name = BAD_FOOTERS[normalized_name]
+
+ if normalized_name not in FOOTERS:
+ if line_context is CommitMessageContext.FOOTER:
+ yield "Unexpected line in footers"
+ else:
+ # Meh. Not a name we care about
+ return
+ elif (line_context is not
+ CommitMessageContext.FOOTER):
+ yield "Expected '{0}:' to be in footer".format(name)
+
+ correct_name = FOOTERS.get(normalized_name)
+ if correct_name and correct_name != name:
+ yield "Use '{0}:' not '{1}:'".format(correct_name, name)
+
+ if normalized_name == 'bug':
+ if not is_valid_bug_id(value):
+ yield "Bug: value must be a single phabricator task ID"
+
+ elif normalized_name == 'depends-on':
+ if not is_valid_change_id(value):
+ yield "Depends-On: value must be a single Gerrit change id"
+
+ elif normalized_name == 'change-id':
+ if not is_valid_change_id(value):
+ yield "Change-Id: value must be a single Gerrit change id"
+ if self._first_changeid is not False:
+ yield ("Extra Change-Id found, first at "
+ "{0}".format(self._first_changeid))
+ else:
+ self._first_changeid = lineno + 1
+
+ if (normalized_name in BEFORE_CHANGE_ID and
+ self._first_changeid is not False):
+ yield ("Expected '{0}:' to come before Change-Id on line "
+ "{1}").format(name, self._first_changeid)
+
+ if ws != ' ':
+ yield "Expected one space after '%s:'" % name
+
+ elif (line and
+ line_context is CommitMessageContext.FOOTER):
+ # if it wasn't a footer (not a match) but it is in the footers
+ cherry_pick = RE_CHERRYPICK.match(line)
+ if cherry_pick:
+ if lineno < len(self._lines) - 1:
+ yield "Cherry pick line is not the last line"
+ else:
+ yield "Expected footer line to follow format of 'Name: ...'"
+
+ def check_global(self):
+ yield from self._global_mv.check_global()
+
+ if self._first_changeid is False:
+ yield "Expected Change-Id"
+
+ def get_context(self, lineno):
+ """
+ Get the context of the current line.
+
+ :param lineno: Line number that the context will be checked.
+ :return: A `CommitMessageContext` enum.
+ """
+ if lineno == 0:
+ # First line in the commit message is HEADER.
+ self._commit_message_context = CommitMessageContext.HEADER
+ elif self._commit_message_context is not CommitMessageContext.FOOTER:
+ line = self._lines[lineno]
+ footer_match = RE_GERRIT_FOOTER.match(line)
+ cherrypick_match = RE_CHERRYPICK.match(line)
+
+ if (((footer_match and
+ footer_match.group('name').lower() in FOOTERS)
+ or cherrypick_match) and
+ not self._lines[lineno - 1]):
+ # If the current line is a footer ("Name: ..." formatted)
+ # and it's indeed a footer (the "Name" listed in the FOOTERS)
+ # or it's a cherry pick
+ # and the previous line is a blank line.
+ # Mark the current line until the end as FOOTER.
+ self._commit_message_context = CommitMessageContext.FOOTER
+ else:
+ self._commit_message_context = CommitMessageContext.BODY
+
+ return self._commit_message_context
diff --git a/commit_message_validator/validators/GlobalMessageValidator.py
b/commit_message_validator/validators/GlobalMessageValidator.py
new file mode 100644
index 0000000..8bcb0b6
--- /dev/null
+++ b/commit_message_validator/validators/GlobalMessageValidator.py
@@ -0,0 +1,48 @@
+import re
+
+from commit_message_validator.message_validator import MessageValidator
+
+RE_URL = re.compile(r'^<?https?://\S+>?$', re.IGNORECASE)
+
+
+class GlobalMessageValidator(MessageValidator):
+ """
+ An iterator to validate all remote repo commit message.
+
+ Checks:
+ - First line <=80 characters
+ - Second line blank
+ - No line >100 characters (unless it is only a URL)
+
+ Global checks:
+ - At least 3 lines in a commit message
+ """
+
+ def __init__(self, lines):
+ """
+ MessageValidator for all remote origin.
+
+ :param lines: list of lines from the commit message that will be
+ checked.
+ """
+ super(GlobalMessageValidator, self).__init__(lines)
+
+ def check_line(self, lineno):
+ line = self._lines[lineno]
+
+ # First line <=80
+ if lineno == 0 and len(line) > 80:
+ yield "First line should be <=80 characters"
+
+ # Second line blank
+ elif lineno == 1 and line:
+ yield "Second line should be empty"
+
+ # No line >100 characters (unless it is only a URL)
+ elif len(line) > 100 and not RE_URL.match(line):
+ yield "Line should be <=100 characters"
+
+ def check_global(self):
+ # At least 3 lines in a commit message
+ if len(self._lines) < 3:
+ yield "Expected at least 3 lines"
diff --git a/commit_message_validator/validators/__init__.py
b/commit_message_validator/validators/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/commit_message_validator/validators/__init__.py
--
To view, visit https://gerrit.wikimedia.org/r/404155
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I862bf42561eb436258951947c8c4a1233a3416c5
Gerrit-PatchSet: 1
Gerrit-Project: integration/commit-message-validator
Gerrit-Branch: master
Gerrit-Owner: Rafidaslam <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits