jenkins-bot has submitted this change and it was merged. Change subject: Add commit-message-validator.py tool ......................................................................
Add commit-message-validator.py tool Add a python script that can be used to validate the format of a commit message. When executed, the script will get the commit message of the HEAD commit in the current working directory and validate it. Script will exit with a non-zero status if validation fails. Validation errors are written to stdout. Current validation rules: * First line <=80 characters * Second line blank * No line >100 characters * "Bug:" is capitalized * "Bug:" is followed by a space * Exactly one task id on each Bug: line * No "Task:", "Fixes:", "Closes:" lines * For any "Bug: " line, next line is not blank * For any "Bug: " line, prior line is another Bug: line or empty * Exactly one "Change-Id: " line per commit * For "Change-Id: " line, prior line is empty or "Bug: " * No blank lines between any "Bug: " lines and "Change-Id: " * Only "(cherry picked from commit" can follow "Change-Id: " * Message has at least 3 lines (subject, blank, Change-Id) Bug: T109119 Change-Id: Ib07957562a0f54abc669f91db9b8bddb29d369c2 --- A tests/test_commit-message-validator.py A tests/test_commit-message-validator/check_message_errors.msg A tests/test_commit-message-validator/check_message_errors.out A tests/test_commit-message-validator/check_message_ok.msg A tests/test_commit-message-validator/check_message_ok.out A tools/commit-message-validator.py 6 files changed, 237 insertions(+), 0 deletions(-) Approvals: 20after4: Looks good to me, but someone else must approve JanZerebecki: Looks good to me, approved jenkins-bot: Verified diff --git a/tests/test_commit-message-validator.py b/tests/test_commit-message-validator.py new file mode 100644 index 0000000..c45935c --- /dev/null +++ b/tests/test_commit-message-validator.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python + +import imp +import os +import unittest + +cmv = imp.load_source( + 'cmv', + os.path.dirname(os.path.dirname(__file__)) + + '/tools/commit-message-validator.py') + + +class TestCommitMessageValidator(unittest.TestCase): + def test_check_message_errors(self): + path = os.path.dirname(__file__) + '/test_commit-message-validator/' + with open(path + 'check_message_errors.msg') as msg: + with open(path + 'check_message_errors.out') as out: + self._test_check_message(msg.read(), 1, out.read()) + + def test_check_message_ok(self): + path = os.path.dirname(__file__) + '/test_commit-message-validator/' + with open(path + 'check_message_ok.msg') as msg: + with open(path + 'check_message_ok.out') as out: + self._test_check_message(msg.read(), 0, out.read()) + + def _test_check_message(self, msg, expect_status, expect_out): + import sys + import StringIO + saved_stdout = sys.stdout + try: + out = StringIO.StringIO() + sys.stdout = out + self.assertEqual( + expect_status, + cmv.check_message(msg.splitlines())) + self.assertEqual(expect_out, out.getvalue()) + finally: + sys.stdout = saved_stdout diff --git a/tests/test_commit-message-validator/check_message_errors.msg b/tests/test_commit-message-validator/check_message_errors.msg new file mode 100644 index 0000000..ac0fc3c --- /dev/null +++ b/tests/test_commit-message-validator/check_message_errors.msg @@ -0,0 +1,15 @@ +XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +YYYYYYYYYYY +YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY +Fixes:foo +Closes:bar +Task: T11 +BUG:13 +Bug: T11 T12 + +Bug: T11 +Bug: T12 + +Change-Id: I1234567890abcdef123456 +Foo bar +(cherry picked from commit 7ad8cc24d0672111cff5beddcfc9d778a1a3478e) diff --git a/tests/test_commit-message-validator/check_message_errors.out b/tests/test_commit-message-validator/check_message_errors.out new file mode 100644 index 0000000..d2611ec --- /dev/null +++ b/tests/test_commit-message-validator/check_message_errors.out @@ -0,0 +1,13 @@ +Line 1: First line should be <=80 characters +Line 2: Second line should be empty +Line 3: Line should be <=100 characters +Line 4: Use 'Bug: ' not 'Fixes:' +Line 5: Use 'Bug: ' not 'Closes:' +Line 6: Use 'Bug: ' not 'Task:' +Line 7: Expected 'Bug:' not 'BUG:' +Line 8: Each Bug: should list exactly one task +Line 8: Expected blank line before Bug: +Line 9: Unexpected blank line after Bug: +Line 12: Unexpected blank line after Bug: +Line 12: Unexpected line between Bug: and Change-Id: +Line 14: Unexpected line after Change-Id diff --git a/tests/test_commit-message-validator/check_message_ok.msg b/tests/test_commit-message-validator/check_message_ok.msg new file mode 100644 index 0000000..018adc6 --- /dev/null +++ b/tests/test_commit-message-validator/check_message_ok.msg @@ -0,0 +1,8 @@ +Add mediawiki::iwlink define and use it with role::mobilefrontend + +Add a new Puppet define that can be used to easily add rows to the +interwiki links table. The define is used to add a few interwiki +language links to mobilewiki provisioned by role:mobilefrontend. + +Bug: T106633 +Change-Id: Ifcd397165df1cbf9fa04f2044e1bb33ad7414d8d diff --git a/tests/test_commit-message-validator/check_message_ok.out b/tests/test_commit-message-validator/check_message_ok.out new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/test_commit-message-validator/check_message_ok.out diff --git a/tools/commit-message-validator.py b/tools/commit-message-validator.py new file mode 100755 index 0000000..4852c8e --- /dev/null +++ b/tools/commit-message-validator.py @@ -0,0 +1,163 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# License: GPLv2+ +# Copyright (c) 2015 Bryan Davis and Wikimedia Foundation. +""" +Validate the format of a commit message to WMF Gerrit standards. + +https://www.mediawiki.org/wiki/Gerrit/Commit_message_guidelines +""" + +import re +import subprocess + + +def line_has_errors(lineno, line): + """Check a commit message line to see if it has errors. + + Checks: + - First line <=80 characters + - Second line blank + - No line >100 characters + - "Bug:" is capitalized + - "Bug:" is followed by a space + - Exactly one task id on each Bug: line + - No "Task: ", "Fixes: ", "Closes: " lines + """ + rline = lineno + 1 + + # First line <=80 + if lineno == 0 and len(line) > 80: + return "Line %d: First line should be <=80 characters" % rline + + # Second line blank + if lineno == 1 and line: + return "Line %d: Second line should be empty" % rline + + # No line >100 + if len(line) > 100: + return "Line %d: Line should be <=100 characters" % rline + + m = re.match(r'^(bug:)(.)', line, re.IGNORECASE) + if m: + if m.group(1) != 'Bug:': + return "Line %d: Expected 'Bug:' not '%s'" % (rline, m.group(1)) + + if m.group(2) != ' ': + return "Line %d: Expected space after 'Bug:'" % rline + + # Exactly one task id on each Bug: line + m = re.match(r'^Bug: \w+$', line) + if not m: + return "Line %d: Each Bug: should list exactly one task" % rline + + # No "Task: ", "Fixes: ", "Closes: " lines + m = re.match(r'^(closes|fixes|task):', line, re.IGNORECASE) + if m: + return "Line %d: Use 'Bug: ' not '%s'" % (rline, m.group(0)) + + return False + + +def check_message(lines): + """Check a commit message to see if it has errors. + + Checks: + - All lines ok as checked by line_has_errors() + - For any "^Bug: " line, next line is not blank + - For any "^Bug: " line, prior line is another Bug: line or empty + - Exactly one "Change-Id: " line per commit + - For "Change-Id: " line, prior line is empty or "^Bug: " + - No blank lines between any "Bug: " lines and "Change-Id: " + - Only "(cherry picked from commit" can follow "Change-Id: " + - Message has at least 3 lines (subject, blank, Change-Id) + """ + errors = [] + last_lineno = 0 + last_line = '' + changeid_line = False + last_bug = False + for lineno, line in enumerate(lines): + # Strip leading spaces to remove `git log` indenting + stripped = line.lstrip() + rline = lineno + 1 + e = line_has_errors(lineno, stripped) + if e: + errors.append(e) + + # For any "Bug: " line, next line is not blank + if last_bug == last_lineno: + if not line: + errors.append( + "Line %d: Unexpected blank line after Bug:" % rline) + + # For any "Bug: " line, prior line is another Bug: line or empty + if line.startswith('Bug: '): + last_bug = rline + if last_line and not last_line.startswith('Bug: '): + errors.append( + "Line %d: Expected blank line before Bug:" % rline) + + if line.startswith('Change-Id: I'): + # Only expect one "Change-Id: " line + if changeid_line is not False: + errors.append( + "Line %d: Extra Change-Id found, next at %d" % + (changeid_line, rline)) + + # For "Change-Id: " line, prior line is empty or Bug: + elif last_line and not last_line.startswith('Bug: '): + errors.append( + "Line %d: Expected blank line or Bug: before Change-Id:" % + rline) + + # If we have Bug: lines, Change-Id follows immediately + elif last_bug and last_bug != rline - 1: + for lno in range(last_bug + 1, rline): + errors.append( + "Line %d: Unexpected line between Bug: and Change-Id:" + % lno) + + changeid_line = rline + + last_lineno = rline + last_line = stripped + + if last_lineno < 2: + errors.append("Line %d: Expected at least 3 lines" % last_lineno) + + if changeid_line is False: + errors.append("Line %d: Expected Change-Id" % last_lineno) + + elif changeid_line != last_lineno: + if last_lineno != changeid_line + 1: + for lno in range(changeid_line + 1, last_lineno): + errors.append( + "Line %d: Unexpected line after Change-Id" % lno) + if not last_line.startswith("(cherry picked from commit"): + errors.append( + "Line %d: Unexpected line after Change-Id" % last_lineno) + + if errors: + for e in errors: + print e + return 1 + return 0 + + +def main(): + """Validate the current HEAD commit message.""" + commit = subprocess.check_output('git log --pretty=raw -1', shell=True) + lines = commit.splitlines() + + # Discard until the first blank line + line = lines.pop(0) + while line: + line = lines.pop(0) + + return check_message(lines) + +if __name__ == '__main__': + import sys + sys.exit(main()) -- To view, visit https://gerrit.wikimedia.org/r/237719 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: merged Gerrit-Change-Id: Ib07957562a0f54abc669f91db9b8bddb29d369c2 Gerrit-PatchSet: 3 Gerrit-Project: integration/jenkins Gerrit-Branch: master Gerrit-Owner: BryanDavis <[email protected]> Gerrit-Reviewer: 20after4 <[email protected]> Gerrit-Reviewer: Alex Monk <[email protected]> Gerrit-Reviewer: BryanDavis <[email protected]> Gerrit-Reviewer: Dduvall <[email protected]> Gerrit-Reviewer: Hashar <[email protected]> Gerrit-Reviewer: JanZerebecki <[email protected]> Gerrit-Reviewer: Jforrester <[email protected]> Gerrit-Reviewer: Krinkle <[email protected]> Gerrit-Reviewer: Legoktm <[email protected]> Gerrit-Reviewer: Polybuildr <[email protected]> Gerrit-Reviewer: jenkins-bot <> _______________________________________________ MediaWiki-commits mailing list [email protected] https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits
