]> git.proxmox.com Git - mirror_edk2.git/blobdiff - BaseTools/Scripts/PatchCheck.py
UefiCpuPkg: Move AsmRelocateApLoopStart from Mpfuncs.nasm to AmdSev.nasm
[mirror_edk2.git] / BaseTools / Scripts / PatchCheck.py
index 6b07241bfe69e6285146aab471f3464fb2456e82..fcdabfc8aceaad567fee8523da1209040201d97b 100755 (executable)
@@ -1,7 +1,9 @@
 ## @file\r
 #  Check a patch for various format issues\r
 #\r
-#  Copyright (c) 2015 - 2019, Intel Corporation. All rights reserved.<BR>\r
+#  Copyright (c) 2015 - 2021, Intel Corporation. All rights reserved.<BR>\r
+#  Copyright (C) 2020, Red Hat, Inc.<BR>\r
+#  Copyright (c) 2020, ARM Ltd. All rights reserved.<BR>\r
 #\r
 #  SPDX-License-Identifier: BSD-2-Clause-Patent\r
 #\r
@@ -18,27 +20,97 @@ import re
 import subprocess\r
 import sys\r
 \r
+import email.header\r
+\r
 class Verbose:\r
     SILENT, ONELINE, NORMAL = range(3)\r
     level = NORMAL\r
 \r
+class EmailAddressCheck:\r
+    """Checks an email address."""\r
+\r
+    def __init__(self, email, description):\r
+        self.ok = True\r
+\r
+        if email is None:\r
+            self.error('Email address is missing!')\r
+            return\r
+        if description is None:\r
+            self.error('Email description is missing!')\r
+            return\r
+\r
+        self.description = "'" + description + "'"\r
+        self.check_email_address(email)\r
+\r
+    def error(self, *err):\r
+        if self.ok and Verbose.level > Verbose.ONELINE:\r
+            print('The ' + self.description + ' email address is not valid:')\r
+        self.ok = False\r
+        if Verbose.level < Verbose.NORMAL:\r
+            return\r
+        count = 0\r
+        for line in err:\r
+            prefix = (' *', '  ')[count > 0]\r
+            print(prefix, line)\r
+            count += 1\r
+\r
+    email_re1 = re.compile(r'(?:\s*)(.*?)(\s*)<(.+)>\s*$',\r
+                           re.MULTILINE|re.IGNORECASE)\r
+\r
+    def check_email_address(self, email):\r
+        email = email.strip()\r
+        mo = self.email_re1.match(email)\r
+        if mo is None:\r
+            self.error("Email format is invalid: " + email.strip())\r
+            return\r
+\r
+        name = mo.group(1).strip()\r
+        if name == '':\r
+            self.error("Name is not provided with email address: " +\r
+                       email)\r
+        else:\r
+            quoted = len(name) > 2 and name[0] == '"' and name[-1] == '"'\r
+            if name.find(',') >= 0 and not quoted:\r
+                self.error('Add quotes (") around name with a comma: ' +\r
+                           name)\r
+\r
+        if mo.group(2) == '':\r
+            self.error("There should be a space between the name and " +\r
+                       "email address: " + email)\r
+\r
+        if mo.group(3).find(' ') >= 0:\r
+            self.error("The email address cannot contain a space: " +\r
+                       mo.group(3))\r
+\r
+        if ' via Groups.Io' in name and mo.group(3).endswith('@groups.io'):\r
+            self.error("Email rewritten by lists DMARC / DKIM / SPF: " +\r
+                       email)\r
+\r
 class CommitMessageCheck:\r
     """Checks the contents of a git commit message."""\r
 \r
-    def __init__(self, subject, message):\r
+    def __init__(self, subject, message, author_email):\r
         self.ok = True\r
 \r
         if subject is None and  message is None:\r
             self.error('Commit message is missing!')\r
             return\r
 \r
+        MergifyMerge = False\r
+        if "mergify[bot]@users.noreply.github.com" in author_email:\r
+            if "Merge branch" in subject:\r
+                MergifyMerge = True\r
+\r
         self.subject = subject\r
         self.msg = message\r
 \r
+        print (subject)\r
+\r
         self.check_contributed_under()\r
-        self.check_signed_off_by()\r
-        self.check_misc_signatures()\r
-        self.check_overall_format()\r
+        if not MergifyMerge:\r
+            self.check_signed_off_by()\r
+            self.check_misc_signatures()\r
+            self.check_overall_format()\r
         self.report_message_result()\r
 \r
     url = 'https://github.com/tianocore/tianocore.github.io/wiki/Commit-Message-Format'\r
@@ -121,38 +193,10 @@ class CommitMessageCheck:
             if s[2] != ' ':\r
                 self.error("There should be a space after '" + sig + ":'")\r
 \r
-            self.check_email_address(s[3])\r
+            EmailAddressCheck(s[3], sig)\r
 \r
         return sigs\r
 \r
-    email_re1 = re.compile(r'(?:\s*)(.*?)(\s*)<(.+)>\s*$',\r
-                           re.MULTILINE|re.IGNORECASE)\r
-\r
-    def check_email_address(self, email):\r
-        email = email.strip()\r
-        mo = self.email_re1.match(email)\r
-        if mo is None:\r
-            self.error("Email format is invalid: " + email.strip())\r
-            return\r
-\r
-        name = mo.group(1).strip()\r
-        if name == '':\r
-            self.error("Name is not provided with email address: " +\r
-                       email)\r
-        else:\r
-            quoted = len(name) > 2 and name[0] == '"' and name[-1] == '"'\r
-            if name.find(',') >= 0 and not quoted:\r
-                self.error('Add quotes (") around name with a comma: ' +\r
-                           name)\r
-\r
-        if mo.group(2) == '':\r
-            self.error("There should be a space between the name and " +\r
-                       "email address: " + email)\r
-\r
-        if mo.group(3).find(' ') >= 0:\r
-            self.error("The email address cannot contain a space: " +\r
-                       mo.group(3))\r
-\r
     def check_signed_off_by(self):\r
         sob='Signed-off-by'\r
         if self.msg.find(sob) < 0:\r
@@ -179,6 +223,8 @@ class CommitMessageCheck:
         for sig in self.sig_types:\r
             self.find_signatures(sig)\r
 \r
+    cve_re = re.compile('CVE-[0-9]{4}-[0-9]{5}[^0-9]')\r
+\r
     def check_overall_format(self):\r
         lines = self.msg.splitlines()\r
 \r
@@ -196,9 +242,26 @@ class CommitMessageCheck:
             self.error('Empty commit message!')\r
             return\r
 \r
-        if count >= 1 and len(lines[0]) >= 72:\r
-            self.error('First line of commit message (subject line) ' +\r
-                       'is too long.')\r
+        if count >= 1 and re.search(self.cve_re, lines[0]):\r
+            #\r
+            # If CVE-xxxx-xxxxx is present in subject line, then limit length of\r
+            # subject line to 92 characters\r
+            #\r
+            if len(lines[0].rstrip()) >= 93:\r
+                self.error(\r
+                    'First line of commit message (subject line) is too long (%d >= 93).' %\r
+                    (len(lines[0].rstrip()))\r
+                    )\r
+        else:\r
+            #\r
+            # If CVE-xxxx-xxxxx is not present in subject line, then limit\r
+            # length of subject line to 75 characters\r
+            #\r
+            if len(lines[0].rstrip()) >= 76:\r
+                self.error(\r
+                    'First line of commit message (subject line) is too long (%d >= 76).' %\r
+                    (len(lines[0].rstrip()))\r
+                    )\r
 \r
         if count >= 1 and len(lines[0].strip()) == 0:\r
             self.error('First line of commit message (subject line) ' +\r
@@ -211,8 +274,22 @@ class CommitMessageCheck:
         for i in range(2, count):\r
             if (len(lines[i]) >= 76 and\r
                 len(lines[i].split()) > 1 and\r
-                not lines[i].startswith('git-svn-id:')):\r
-                self.error('Line %d of commit message is too long.' % (i + 1))\r
+                not lines[i].startswith('git-svn-id:') and\r
+                not lines[i].startswith('Reviewed-by') and\r
+                not lines[i].startswith('Acked-by:') and\r
+                not lines[i].startswith('Tested-by:') and\r
+                not lines[i].startswith('Reported-by:') and\r
+                not lines[i].startswith('Suggested-by:') and\r
+                not lines[i].startswith('Signed-off-by:') and\r
+                not lines[i].startswith('Cc:')):\r
+                #\r
+                # Print a warning if body line is longer than 75 characters\r
+                #\r
+                print(\r
+                    'WARNING - Line %d of commit message is too long (%d >= 76).' %\r
+                    (i + 1, len(lines[i]))\r
+                    )\r
+                print(lines[i])\r
 \r
         last_sig_line = None\r
         for i in range(count - 1, 0, -1):\r
@@ -284,7 +361,30 @@ class GitDiffCheck:
                 self.state = PRE_PATCH\r
                 self.filename = line[13:].split(' ', 1)[0]\r
                 self.is_newfile = False\r
-                self.force_crlf = not self.filename.endswith('.sh')\r
+                self.force_crlf = True\r
+                self.force_notabs = True\r
+                if self.filename.endswith('.sh') or \\r
+                    self.filename.startswith('BaseTools/BinWrappers/PosixLike/') or \\r
+                    self.filename.startswith('BaseTools/BinPipWrappers/PosixLike/') or \\r
+                    self.filename == 'BaseTools/BuildEnv':\r
+                    #\r
+                    # Do not enforce CR/LF line endings for linux shell scripts.\r
+                    # Some linux shell scripts don't end with the ".sh" extension,\r
+                    # they are identified by their path.\r
+                    #\r
+                    self.force_crlf = False\r
+                if self.filename == '.gitmodules' or \\r
+                   self.filename == 'BaseTools/Conf/diff.order':\r
+                    #\r
+                    # .gitmodules and diff orderfiles are used internally by git\r
+                    # use tabs and LF line endings.  Do not enforce no tabs and\r
+                    # do not enforce CR/LF line endings.\r
+                    #\r
+                    self.force_crlf = False\r
+                    self.force_notabs = False\r
+                if os.path.basename(self.filename) == 'GNUmakefile' or \\r
+                   os.path.basename(self.filename) == 'Makefile':\r
+                    self.force_notabs = False\r
             elif len(line.rstrip()) != 0:\r
                 self.format_error("didn't find diff command")\r
             self.line_num += 1\r
@@ -298,6 +398,11 @@ class GitDiffCheck:
                 self.binary = True\r
                 if self.is_newfile:\r
                     self.new_bin.append(self.filename)\r
+            elif line.startswith('new file mode 160000'):\r
+                #\r
+                # New submodule.  Do not enforce CR/LF line endings\r
+                #\r
+                self.force_crlf = False\r
             else:\r
                 ok = False\r
                 self.is_newfile = self.newfile_prefix_re.match(line)\r
@@ -369,10 +474,10 @@ class GitDiffCheck:
 \r
         stripped = line.rstrip()\r
 \r
-        if self.force_crlf and eol != '\r\n':\r
+        if self.force_crlf and eol != '\r\n' and (line.find('Subproject commit') == -1):\r
             self.added_line_error('Line ending (%s) is not CRLF' % repr(eol),\r
                                   line)\r
-        if '\t' in line:\r
+        if self.force_notabs and '\t' in line:\r
             self.added_line_error('Tab character used', line)\r
         if len(stripped) < len(line):\r
             self.added_line_error('Trailing whitespace found', line)\r
@@ -422,7 +527,10 @@ class CheckOnePatch:
         self.patch = patch\r
         self.find_patch_pieces()\r
 \r
-        msg_check = CommitMessageCheck(self.commit_subject, self.commit_msg)\r
+        email_check = EmailAddressCheck(self.author_email, 'Author')\r
+        email_ok = email_check.ok\r
+\r
+        msg_check = CommitMessageCheck(self.commit_subject, self.commit_msg, self.author_email)\r
         msg_ok = msg_check.ok\r
 \r
         diff_ok = True\r
@@ -430,7 +538,7 @@ class CheckOnePatch:
             diff_check = GitDiffCheck(self.diff)\r
             diff_ok = diff_check.ok\r
 \r
-        self.ok = msg_ok and diff_ok\r
+        self.ok = email_ok and msg_ok and diff_ok\r
 \r
         if Verbose.level == Verbose.ONELINE:\r
             if self.ok:\r
@@ -503,11 +611,30 @@ class CheckOnePatch:
         else:\r
             self.stat = mo.group('stat')\r
             self.commit_msg = mo.group('commit_message')\r
+        #\r
+        # Parse subject line from email header.  The subject line may be\r
+        # composed of multiple parts with different encodings.  Decode and\r
+        # combine all the parts to produce a single string with the contents of\r
+        # the decoded subject line.\r
+        #\r
+        parts = email.header.decode_header(pmail.get('subject'))\r
+        subject = ''\r
+        for (part, encoding) in parts:\r
+            if encoding:\r
+                part = part.decode(encoding)\r
+            else:\r
+                try:\r
+                    part = part.decode()\r
+                except:\r
+                    pass\r
+            subject = subject + part\r
 \r
-        self.commit_subject = pmail['subject'].replace('\r\n', '')\r
+        self.commit_subject = subject.replace('\r\n', '')\r
         self.commit_subject = self.commit_subject.replace('\n', '')\r
         self.commit_subject = self.subject_prefix_re.sub('', self.commit_subject, 1)\r
 \r
+        self.author_email = pmail['from']\r
+\r
 class CheckGitCommits:\r
     """Reads patches from git based on the specified git revision range.\r
 \r
@@ -527,6 +654,8 @@ class CheckGitCommits:
                 else:\r
                     blank_line = True\r
                 print('Checking git commit:', commit)\r
+            email = self.read_committer_email_address_from_git(commit)\r
+            self.ok &= EmailAddressCheck(email, 'Committer').ok\r
             patch = self.read_patch_from_git(commit)\r
             self.ok &= CheckOnePatch(commit, patch).ok\r
         if not commits:\r
@@ -543,7 +672,13 @@ class CheckGitCommits:
 \r
     def read_patch_from_git(self, commit):\r
         # Run git to get the commit patch\r
-        return self.run_git('show', '--pretty=email', commit)\r
+        return self.run_git('show', '--pretty=email', '--no-textconv',\r
+                            '--no-use-mailmap', commit)\r
+\r
+    def read_committer_email_address_from_git(self, commit):\r
+        # Run git to get the committer email\r
+        return self.run_git('show', '--pretty=%cn <%ce>', '--no-patch',\r
+                            '--no-use-mailmap', commit)\r
 \r
     def run_git(self, *args):\r
         cmd = [ 'git' ]\r