jenkins-bot has submitted this change and it was merged. ( 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 M setup.py 15 files changed, 500 insertions(+), 190 deletions(-) Approvals: John Vandenberg: Looks good to me, approved jenkins-bot: Verified diff --git a/commit_message_validator/__init__.py b/commit_message_validator/__init__.py index 8166fee..3f1bc34 100644 --- a/commit_message_validator/__init__.py +++ b/commit_message_validator/__init__.py @@ -24,198 +24,13 @@ from __future__ import print_function import os -import re import subprocess import sys +from commit_message_validator.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 +45,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..2a9a557 --- /dev/null +++ b/commit_message_validator/message_validator.py @@ -0,0 +1,98 @@ +"""A module that contains `MessageValidator` class.""" + + +class MessageValidator(object): + """ + MessageValidator is an iterable class that will yields a tuple + that contains line number and a string that describes the + error. + + A class that implements this class, may implement: + + - `check_line()`, that yields the error message of the checked line. + - `check_global()` (optional) that yields the error message of the + checked lines, will be called after `check_line()` done the checking. + + See `GerritMessageValidator` for an implementation of + `check_line()`, and `check_global()` methods. + + Example usage: + + >>> lines = ['Title', 'This should be empty', 'Body'] + >>> + >>> class AMessageValidator(MessageValidator): + ... def check_line(self, lineno): + ... line = self._lines[lineno] + ... if lineno == 1 and line: + ... yield 'Line should be empty' + ... def check_global(self): + ... yield 'Message commit is {0} lines long'.format( + ... len(self._lines)) + ... + >>> for lineno, msg in AMessageValidator(lines): + ... print('{0} {1}'.format(lineno, msg)) + ... + 2 Line should be empty + 3 Message commit is 3 lines long + + """ + + 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}'.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..994ed3a --- /dev/null +++ b/commit_message_validator/tests/test_GerritMessageValidator.py @@ -0,0 +1,32 @@ +import unittest + +from commit_message_validator.validators.GerritMessageValidator import ( + CommitMessageContext, + GerritMessageValidator, +) + + +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..481aeac --- /dev/null +++ b/commit_message_validator/tests/test_message_validator.py @@ -0,0 +1,92 @@ +import re +import unittest + + +from commit_message_validator.message_validator import MessageValidator + + +class NoCheckLineMessageValidator(MessageValidator): + """ + A MessageValidator that doesn't implement `check_line()` and + `check_global()`. + """ + + +class JustTestMessageValidator(MessageValidator): + + 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 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.assertRaisesRegexp( + 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.assertRaisesRegexp( + NotImplementedError, + re.escape('`check_global()` isn\'t ' + 'implemented in NoCheckLineMessageValidator' + )): + 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..e78ff9a --- /dev/null +++ b/commit_message_validator/validators/GerritMessageValidator.py @@ -0,0 +1,198 @@ +import re + +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(object): + HEADER = 1 + BODY = 2 + FOOTER = 3 + + +class GerritMessageValidator(GlobalMessageValidator): + """ + 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._commit_message_context = None + self._first_changeid = False + + def check_line(self, lineno): + for error in super(GerritMessageValidator, self).check_line(lineno): + yield error + + 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): + for error in super(GerritMessageValidator, self).check_global(): + yield error + + 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..51ec26d --- /dev/null +++ b/commit_message_validator/validators/GlobalMessageValidator.py @@ -0,0 +1,39 @@ +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 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 diff --git a/setup.py b/setup.py index f8388ac..da42588 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,10 @@ license='GPL-2.0+', description='Validate the format of a commit message to Wikimedia Gerrit standards', long_description=open('README').read(), - packages=['commit_message_validator'], + packages=[ + 'commit_message_validator', + 'commit_message_validator.validators', + ], install_requires=[], test_suite='nose.collector', tests_require=['nose'], -- To view, visit https://gerrit.wikimedia.org/r/404155 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: merged Gerrit-Change-Id: I862bf42561eb436258951947c8c4a1233a3416c5 Gerrit-PatchSet: 11 Gerrit-Project: integration/commit-message-validator Gerrit-Branch: master Gerrit-Owner: Rafidaslam <rafidt...@gmail.com> Gerrit-Reviewer: Hashar <has...@free.fr> Gerrit-Reviewer: John Vandenberg <jay...@gmail.com> Gerrit-Reviewer: Legoktm <lego...@member.fsf.org> Gerrit-Reviewer: Paladox <thomasmulhall...@yahoo.com> Gerrit-Reviewer: jenkins-bot <> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits