Title: [294582] trunk/Tools/CISupport/ews-build
Revision
294582
Author
jbed...@apple.com
Date
2022-05-20 14:42:03 -0700 (Fri, 20 May 2022)

Log Message

[Merge-Queue] Validate reviewers in commit message
https://bugs.webkit.org/show_bug.cgi?id=240718
<rdar://problem/93665771>

Reviewed by Aakash Jain.

* Tools/CISupport/ews-build/steps.py:
(ValidateCommitMessage):
(ValidateCommitMessage.__init__):
(ValidateCommitMessage.extract_reviewers): Extract reviewers from commit message.
(ValidateCommitMessage.is_reviewer): Check if a contributor is a reviewer.
(ValidateCommitMessage.run): Check if extracted reviewers are reviewers.
* Tools/CISupport/ews-build/steps_unittest.py:

Canonical link: https://commits.webkit.org/250818@main

Modified Paths

Diff

Modified: trunk/Tools/CISupport/ews-build/steps.py (294581 => 294582)


--- trunk/Tools/CISupport/ews-build/steps.py	2022-05-20 21:41:14 UTC (rev 294581)
+++ trunk/Tools/CISupport/ews-build/steps.py	2022-05-20 21:42:03 UTC (rev 294582)
@@ -479,6 +479,7 @@
             if name and emails:
                 bugzilla_email = emails[0].lower()  # We're requiring that the first email is the primary bugzilla email
                 cls.contributors[bugzilla_email] = {'name': name, 'status': value.get('status')}
+                cls.contributors[name] = {'status': value.get('status')}
             if github_username and name and emails:
                 cls.contributors[github_username.lower()] = dict(
                     name=name,
@@ -4871,7 +4872,7 @@
         return not self.doStepIf(step)
 
 
-class ValidateCommitMessage(steps.ShellSequence, ShellMixin):
+class ValidateCommitMessage(steps.ShellSequence, ShellMixin, AddToLogMixin):
     name = 'validate-commit-message'
     haltOnFailure = False
     flunkOnFailure = True
@@ -4878,15 +4879,36 @@
     OOPS_RE = 'OO*PP*S!'
     REVIEWED_STRINGS = (
         'Reviewed by',
-        'Unreviewed',
         'Rubber-stamped by',
         'Rubber stamped by',
+        'Unreviewed',
     )
     RE_CHANGELOG = br'^(\+\+\+)\s+(.*ChangeLog.*)'
+    BY_RE = re.compile(r'.+\s+by\s+(.+)$')
+    SPLIT_RE = re.compile(r'(,\s*)|( and )|\.')
 
     def __init__(self, **kwargs):
         super(ValidateCommitMessage, self).__init__(logEnviron=False, timeout=60, **kwargs)
+        self.contributors = {}
 
+    @classmethod
+    def extract_reviewers(cls, text):
+        reviewers = set()
+        stripped_text = ''
+        for line in text.splitlines():
+            match = cls.BY_RE.match(line)
+            if not match:
+                stripped_text += line + '\n'
+                continue
+            for person in cls.SPLIT_RE.split(match.group(1)):
+                if person and not cls.SPLIT_RE.match(person):
+                    reviewers.add(person)
+        return reviewers, stripped_text
+
+    def is_reviewer(self, name):
+        contributor = self.contributors.get(name)
+        return contributor and contributor['status'] == 'reviewer'
+
     def _files(self):
         sourcestamp = self.build.getSourceStamp(self.getProperty('codebase', ''))
         if sourcestamp and sourcestamp.changes:
@@ -4911,6 +4933,9 @@
             "git log {} ^{} | grep -q '\\({}\\)' || echo 'No reviewer information in commit message'".format(
                 head_ref, base_ref,
                 '\\|'.join(self.REVIEWED_STRINGS),
+            ), "git log {} ^{} | grep '\\({}\\)'".format(
+                head_ref, base_ref,
+                '\\|'.join(self.REVIEWED_STRINGS[:-1]),
             ),
         ]
         for command in commands:
@@ -4925,7 +4950,15 @@
             self.summary = 'Patches have no commit message'
             return rc
 
-        log_text = self.log_observer.getStdout().rstrip()
+        self.contributors, errors = Contributors.load(use_network=True)
+        for error in errors:
+            print(error)
+            self._addToLog('stdio', error)
+
+        reviewers, log_text = self.extract_reviewers(self.log_observer.getStdout())
+        log_text = log_text.rstrip()
+        author = self.getProperty('author', '')
+
         if any(['ChangeLog' in file for file in self._files()]):
             self.summary = 'ChangeLog modified, WebKit only allows commit messages'
             rc = FAILURE
@@ -4933,7 +4966,21 @@
             self.summary = log_text
             rc = FAILURE
         elif rc == SUCCESS:
-            self.summary = 'Validated commit message'
+            if reviewers and not self.contributors:
+                self.summary = "Failed to load contributors.json, can't validate reviewers"
+                rc = FAILURE
+            elif reviewers and any([not self.is_reviewer(reviewer) for reviewer in reviewers]):
+                self.summary = "'{}' is not a reviewer"
+                for reviewer in reviewers:
+                    if not self.is_reviewer(reviewer):
+                        self.summary = self.summary.format(reviewer)
+                        break
+                rc = FAILURE
+            elif reviewers and author and any([author.startswith(reviewer) for reviewer in reviewers]):
+                self.summary = f"'{author}' cannot review their own change"
+                rc = FAILURE
+            else:
+                self.summary = 'Validated commit message'
         else:
             self.summary = 'Error parsing commit message'
             rc = FAILURE

Modified: trunk/Tools/CISupport/ews-build/steps_unittest.py (294581 => 294582)


--- trunk/Tools/CISupport/ews-build/steps_unittest.py	2022-05-20 21:41:14 UTC (rev 294581)
+++ trunk/Tools/CISupport/ews-build/steps_unittest.py	2022-05-20 21:42:03 UTC (rev 294582)
@@ -77,8 +77,10 @@
     return {
         'revie...@apple.com': {'name': 'WebKit Reviewer', 'status': 'reviewer', 'email': 'revie...@apple.com'},
         'webkit-reviewer': {'name': 'WebKit Reviewer', 'status': 'reviewer', 'email': 'revie...@apple.com'},
+        'WebKit Reviewer': {'status': 'reviewer'},
         'commit...@webkit.org': {'name': 'WebKit Committer', 'status': 'committer', 'email': 'commit...@webkit.org'},
         'webkit-commit-queue': {'name': 'WebKit Committer', 'status': 'committer', 'email': 'commit...@webkit.org'},
+        'WebKit Committer': {'status': 'committer'},
     }, []
 
 
@@ -5944,6 +5946,7 @@
 class TestValidateCommitMessage(BuildStepMixinAdditions, unittest.TestCase):
     def setUp(self):
         self.longMessage = True
+        Contributors.load = mock_load_contributors
         return self.setUpBuildStep()
 
     def tearDown(self):
@@ -5960,9 +5963,13 @@
             + 0, ExpectShell(workdir='wkdir',
                         logEnviron=False,
                         timeout=60,
-                        command=['/bin/sh', '-c', "git log HEAD ^origin/main | grep -q '\\(Reviewed by\\|Unreviewed\\|Rubber-stamped by\\|Rubber stamped by\\)' || echo 'No reviewer information in commit message'"])
+                        command=['/bin/sh', '-c', "git log HEAD ^origin/main | grep -q '\\(Reviewed by\\|Rubber-stamped by\\|Rubber stamped by\\|Unreviewed\\)' || echo 'No reviewer information in commit message'"])
+            + 0, ExpectShell(workdir='wkdir',
+                        logEnviron=False,
+                        timeout=60,
+                        command=['/bin/sh', '-c', "git log HEAD ^origin/main | grep '\\(Reviewed by\\|Rubber-stamped by\\|Rubber stamped by\\)'"])
             + 0
-            + ExpectShell.log('stdio', stdout=''),
+            + ExpectShell.log('stdio', stdout='    Reviewed by WebKit Reviewer.\n'),
         )
         self.expectOutcome(result=SUCCESS, state_string='Validated commit message')
         return self.runStep()
@@ -5981,9 +5988,13 @@
             + 0, ExpectShell(workdir='wkdir',
                 logEnviron=False,
                         timeout=60,
-                        command=['/bin/sh', '-c', "git log eng/pull-request-branch ^main | grep -q '\\(Reviewed by\\|Unreviewed\\|Rubber-stamped by\\|Rubber stamped by\\)' || echo 'No reviewer information in commit message'"])
+                        command=['/bin/sh', '-c', "git log eng/pull-request-branch ^main | grep -q '\\(Reviewed by\\|Rubber-stamped by\\|Rubber stamped by\\|Unreviewed\\)' || echo 'No reviewer information in commit message'"])
+            + 0, ExpectShell(workdir='wkdir',
+                        logEnviron=False,
+                        timeout=60,
+                        command=['/bin/sh', '-c', "git log eng/pull-request-branch ^main | grep '\\(Reviewed by\\|Rubber-stamped by\\|Rubber stamped by\\)'"])
             + 0
-            + ExpectShell.log('stdio', stdout=''),
+            + ExpectShell.log('stdio', stdout='    Reviewed by WebKit Reviewer.\n'),
         )
         self.expectOutcome(result=SUCCESS, state_string='Validated commit message')
         return self.runStep()
@@ -6021,7 +6032,11 @@
             + 0, ExpectShell(workdir='wkdir',
                         logEnviron=False,
                         timeout=60,
-                        command=['/bin/sh', '-c', "git log eng/pull-request-branch ^main | grep -q '\\(Reviewed by\\|Unreviewed\\|Rubber-stamped by\\|Rubber stamped by\\)' || echo 'No reviewer information in commit message'"])
+                        command=['/bin/sh', '-c', "git log eng/pull-request-branch ^main | grep -q '\\(Reviewed by\\|Rubber-stamped by\\|Rubber stamped by\\|Unreviewed\\)' || echo 'No reviewer information in commit message'"])
+            + 0, ExpectShell(workdir='wkdir',
+                        logEnviron=False,
+                        timeout=60,
+                        command=['/bin/sh', '-c', "git log eng/pull-request-branch ^main | grep '\\(Reviewed by\\|Rubber-stamped by\\|Rubber stamped by\\)'"])
             + 0
             + ExpectShell.log('stdio', stdout='No reviewer information in commit message\n'),
         )
@@ -6044,14 +6059,69 @@
             + 0, ExpectShell(workdir='wkdir',
                 logEnviron=False,
                         timeout=60,
-                        command=['/bin/sh', '-c', "git log eng/pull-request-branch ^main | grep -q '\\(Reviewed by\\|Unreviewed\\|Rubber-stamped by\\|Rubber stamped by\\)' || echo 'No reviewer information in commit message'"])
+                        command=['/bin/sh', '-c', "git log eng/pull-request-branch ^main | grep -q '\\(Reviewed by\\|Rubber-stamped by\\|Rubber stamped by\\|Unreviewed\\)' || echo 'No reviewer information in commit message'"])
+            + 0, ExpectShell(workdir='wkdir',
+                        logEnviron=False,
+                        timeout=60,
+                        command=['/bin/sh', '-c', "git log eng/pull-request-branch ^main | grep '\\(Reviewed by\\|Rubber-stamped by\\|Rubber stamped by\\)'"])
             + 0
-            + ExpectShell.log('stdio', stdout=''),
+            + ExpectShell.log('stdio', stdout='    Reviewed by WebKit Reviewer.\n'),
         )
         self.expectOutcome(result=FAILURE, state_string='ChangeLog modified, WebKit only allows commit messages')
         return self.runStep()
 
+    def test_invalid_reviewer(self):
+        self.setupStep(ValidateCommitMessage())
+        ValidateCommitMessage._files = lambda x: ['+++ Tools/CISupport/ews-build/steps.py']
+        self.setProperty('github.number', '1234')
+        self.setProperty('github.base.ref', 'main')
+        self.setProperty('github.head.ref', 'eng/pull-request-branch')
+        self.expectRemoteCommands(
+            ExpectShell(workdir='wkdir',
+                        logEnviron=False,
+                        timeout=60,
+                        command=['/bin/sh', '-c', "git log eng/pull-request-branch ^main | grep -q 'OO*PP*S!' && echo 'Commit message contains (OOPS!)' || test $? -eq 1"])
+            + 0, ExpectShell(workdir='wkdir',
+                logEnviron=False,
+                        timeout=60,
+                        command=['/bin/sh', '-c', "git log eng/pull-request-branch ^main | grep -q '\\(Reviewed by\\|Rubber-stamped by\\|Rubber stamped by\\|Unreviewed\\)' || echo 'No reviewer information in commit message'"])
+            + 0, ExpectShell(workdir='wkdir',
+                        logEnviron=False,
+                        timeout=60,
+                        command=['/bin/sh', '-c', "git log eng/pull-request-branch ^main | grep '\\(Reviewed by\\|Rubber-stamped by\\|Rubber stamped by\\)'"])
+            + 0
+            + ExpectShell.log('stdio', stdout='    Reviewed by WebKit Contributor.\n'),
+        )
+        self.expectOutcome(result=FAILURE, state_string="'WebKit Contributor' is not a reviewer")
+        return self.runStep()
 
+    def test_self_reviewer(self):
+        self.setupStep(ValidateCommitMessage())
+        ValidateCommitMessage._files = lambda x: ['+++ Tools/CISupport/ews-build/steps.py']
+        self.setProperty('github.number', '1234')
+        self.setProperty('github.base.ref', 'main')
+        self.setProperty('github.head.ref', 'eng/pull-request-branch')
+        self.setProperty('author', 'WebKit Reviewer <revie...@apple.com>')
+        self.expectRemoteCommands(
+            ExpectShell(workdir='wkdir',
+                        logEnviron=False,
+                        timeout=60,
+                        command=['/bin/sh', '-c', "git log eng/pull-request-branch ^main | grep -q 'OO*PP*S!' && echo 'Commit message contains (OOPS!)' || test $? -eq 1"])
+            + 0, ExpectShell(workdir='wkdir',
+                logEnviron=False,
+                        timeout=60,
+                        command=['/bin/sh', '-c', "git log eng/pull-request-branch ^main | grep -q '\\(Reviewed by\\|Rubber-stamped by\\|Rubber stamped by\\|Unreviewed\\)' || echo 'No reviewer information in commit message'"])
+            + 0, ExpectShell(workdir='wkdir',
+                        logEnviron=False,
+                        timeout=60,
+                        command=['/bin/sh', '-c', "git log eng/pull-request-branch ^main | grep '\\(Reviewed by\\|Rubber-stamped by\\|Rubber stamped by\\)'"])
+            + 0
+            + ExpectShell.log('stdio', stdout='    Reviewed by WebKit Reviewer.\n'),
+        )
+        self.expectOutcome(result=FAILURE, state_string="'WebKit Reviewer <revie...@apple.com>' cannot review their own change")
+        return self.runStep()
+
+
 class TestCanonicalize(BuildStepMixinAdditions, unittest.TestCase):
     def setUp(self):
         self.longMessage = True
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to