This reverts commit a4cfb842fc.
This commit suggests inclusion of non-edk2+license content without
a contribution agreement is something the community has made a
decision on, which is incorrect.
Cc: Shenglei Zhang <shenglei.zhang@intel.com>
Cc: Yuwei Chen <yuwei.chen@intel.com>
Cc: Liming Gao <liming.gao@intel.com>
Signed-off-by: Leif Lindholm <leif@nuviainc.com>
Reviewed-by: Liming Gao <liming.gao@intel.com>
		
	
		
			
				
	
	
		
			770 lines
		
	
	
		
			26 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			770 lines
		
	
	
		
			26 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
## @file
 | 
						|
#  Check a patch for various format issues
 | 
						|
#
 | 
						|
#  Copyright (c) 2015 - 2020, Intel Corporation. All rights reserved.<BR>
 | 
						|
#  Copyright (C) 2020, Red Hat, Inc.<BR>
 | 
						|
#  Copyright (c) 2020, ARM Ltd. All rights reserved.<BR>
 | 
						|
#
 | 
						|
#  SPDX-License-Identifier: BSD-2-Clause-Patent
 | 
						|
#
 | 
						|
 | 
						|
from __future__ import print_function
 | 
						|
 | 
						|
VersionNumber = '0.1'
 | 
						|
__copyright__ = "Copyright (c) 2015 - 2016, Intel Corporation  All rights reserved."
 | 
						|
 | 
						|
import email
 | 
						|
import argparse
 | 
						|
import os
 | 
						|
import re
 | 
						|
import subprocess
 | 
						|
import sys
 | 
						|
 | 
						|
import email.header
 | 
						|
 | 
						|
class Verbose:
 | 
						|
    SILENT, ONELINE, NORMAL = range(3)
 | 
						|
    level = NORMAL
 | 
						|
 | 
						|
class EmailAddressCheck:
 | 
						|
    """Checks an email address."""
 | 
						|
 | 
						|
    def __init__(self, email, description):
 | 
						|
        self.ok = True
 | 
						|
 | 
						|
        if email is None:
 | 
						|
            self.error('Email address is missing!')
 | 
						|
            return
 | 
						|
        if description is None:
 | 
						|
            self.error('Email description is missing!')
 | 
						|
            return
 | 
						|
 | 
						|
        self.description = "'" + description + "'"
 | 
						|
        self.check_email_address(email)
 | 
						|
 | 
						|
    def error(self, *err):
 | 
						|
        if self.ok and Verbose.level > Verbose.ONELINE:
 | 
						|
            print('The ' + self.description + ' email address is not valid:')
 | 
						|
        self.ok = False
 | 
						|
        if Verbose.level < Verbose.NORMAL:
 | 
						|
            return
 | 
						|
        count = 0
 | 
						|
        for line in err:
 | 
						|
            prefix = (' *', '  ')[count > 0]
 | 
						|
            print(prefix, line)
 | 
						|
            count += 1
 | 
						|
 | 
						|
    email_re1 = re.compile(r'(?:\s*)(.*?)(\s*)<(.+)>\s*$',
 | 
						|
                           re.MULTILINE|re.IGNORECASE)
 | 
						|
 | 
						|
    def check_email_address(self, email):
 | 
						|
        email = email.strip()
 | 
						|
        mo = self.email_re1.match(email)
 | 
						|
        if mo is None:
 | 
						|
            self.error("Email format is invalid: " + email.strip())
 | 
						|
            return
 | 
						|
 | 
						|
        name = mo.group(1).strip()
 | 
						|
        if name == '':
 | 
						|
            self.error("Name is not provided with email address: " +
 | 
						|
                       email)
 | 
						|
        else:
 | 
						|
            quoted = len(name) > 2 and name[0] == '"' and name[-1] == '"'
 | 
						|
            if name.find(',') >= 0 and not quoted:
 | 
						|
                self.error('Add quotes (") around name with a comma: ' +
 | 
						|
                           name)
 | 
						|
 | 
						|
        if mo.group(2) == '':
 | 
						|
            self.error("There should be a space between the name and " +
 | 
						|
                       "email address: " + email)
 | 
						|
 | 
						|
        if mo.group(3).find(' ') >= 0:
 | 
						|
            self.error("The email address cannot contain a space: " +
 | 
						|
                       mo.group(3))
 | 
						|
 | 
						|
        if ' via Groups.Io' in name and mo.group(3).endswith('@groups.io'):
 | 
						|
            self.error("Email rewritten by lists DMARC / DKIM / SPF: " +
 | 
						|
                       email)
 | 
						|
 | 
						|
class CommitMessageCheck:
 | 
						|
    """Checks the contents of a git commit message."""
 | 
						|
 | 
						|
    def __init__(self, subject, message):
 | 
						|
        self.ok = True
 | 
						|
 | 
						|
        if subject is None and  message is None:
 | 
						|
            self.error('Commit message is missing!')
 | 
						|
            return
 | 
						|
 | 
						|
        self.subject = subject
 | 
						|
        self.msg = message
 | 
						|
 | 
						|
        print (subject)
 | 
						|
 | 
						|
        self.check_contributed_under()
 | 
						|
        self.check_signed_off_by()
 | 
						|
        self.check_misc_signatures()
 | 
						|
        self.check_overall_format()
 | 
						|
        self.report_message_result()
 | 
						|
 | 
						|
    url = 'https://github.com/tianocore/tianocore.github.io/wiki/Commit-Message-Format'
 | 
						|
 | 
						|
    def report_message_result(self):
 | 
						|
        if Verbose.level < Verbose.NORMAL:
 | 
						|
            return
 | 
						|
        if self.ok:
 | 
						|
            # All checks passed
 | 
						|
            return_code = 0
 | 
						|
            print('The commit message format passed all checks.')
 | 
						|
        else:
 | 
						|
            return_code = 1
 | 
						|
        if not self.ok:
 | 
						|
            print(self.url)
 | 
						|
 | 
						|
    def error(self, *err):
 | 
						|
        if self.ok and Verbose.level > Verbose.ONELINE:
 | 
						|
            print('The commit message format is not valid:')
 | 
						|
        self.ok = False
 | 
						|
        if Verbose.level < Verbose.NORMAL:
 | 
						|
            return
 | 
						|
        count = 0
 | 
						|
        for line in err:
 | 
						|
            prefix = (' *', '  ')[count > 0]
 | 
						|
            print(prefix, line)
 | 
						|
            count += 1
 | 
						|
 | 
						|
    # Find 'contributed-under:' at the start of a line ignoring case and
 | 
						|
    # requires ':' to be present.  Matches if there is white space before
 | 
						|
    # the tag or between the tag and the ':'.
 | 
						|
    contributed_under_re = \
 | 
						|
        re.compile(r'^\s*contributed-under\s*:', re.MULTILINE|re.IGNORECASE)
 | 
						|
 | 
						|
    def check_contributed_under(self):
 | 
						|
        match = self.contributed_under_re.search(self.msg)
 | 
						|
        if match is not None:
 | 
						|
            self.error('Contributed-under! (Note: this must be ' +
 | 
						|
                       'removed by the code contributor!)')
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    def make_signature_re(sig, re_input=False):
 | 
						|
        if re_input:
 | 
						|
            sub_re = sig
 | 
						|
        else:
 | 
						|
            sub_re = sig.replace('-', r'[-\s]+')
 | 
						|
        re_str = (r'^(?P<tag>' + sub_re +
 | 
						|
                  r')(\s*):(\s*)(?P<value>\S.*?)(?:\s*)$')
 | 
						|
        try:
 | 
						|
            return re.compile(re_str, re.MULTILINE|re.IGNORECASE)
 | 
						|
        except Exception:
 | 
						|
            print("Tried to compile re:", re_str)
 | 
						|
            raise
 | 
						|
 | 
						|
    sig_block_re = \
 | 
						|
        re.compile(r'''^
 | 
						|
                        (?: (?P<tag>[^:]+) \s* : \s*
 | 
						|
                            (?P<value>\S.*?) )
 | 
						|
                            |
 | 
						|
                        (?: \[ (?P<updater>[^:]+) \s* : \s*
 | 
						|
                               (?P<note>.+?) \s* \] )
 | 
						|
                    \s* $''',
 | 
						|
                   re.VERBOSE | re.MULTILINE)
 | 
						|
 | 
						|
    def find_signatures(self, sig):
 | 
						|
        if not sig.endswith('-by') and sig != 'Cc':
 | 
						|
            sig += '-by'
 | 
						|
        regex = self.make_signature_re(sig)
 | 
						|
 | 
						|
        sigs = regex.findall(self.msg)
 | 
						|
 | 
						|
        bad_case_sigs = filter(lambda m: m[0] != sig, sigs)
 | 
						|
        for s in bad_case_sigs:
 | 
						|
            self.error("'" +s[0] + "' should be '" + sig + "'")
 | 
						|
 | 
						|
        for s in sigs:
 | 
						|
            if s[1] != '':
 | 
						|
                self.error('There should be no spaces between ' + sig +
 | 
						|
                           " and the ':'")
 | 
						|
            if s[2] != ' ':
 | 
						|
                self.error("There should be a space after '" + sig + ":'")
 | 
						|
 | 
						|
            EmailAddressCheck(s[3], sig)
 | 
						|
 | 
						|
        return sigs
 | 
						|
 | 
						|
    def check_signed_off_by(self):
 | 
						|
        sob='Signed-off-by'
 | 
						|
        if self.msg.find(sob) < 0:
 | 
						|
            self.error('Missing Signed-off-by! (Note: this must be ' +
 | 
						|
                       'added by the code contributor!)')
 | 
						|
            return
 | 
						|
 | 
						|
        sobs = self.find_signatures('Signed-off')
 | 
						|
 | 
						|
        if len(sobs) == 0:
 | 
						|
            self.error('Invalid Signed-off-by format!')
 | 
						|
            return
 | 
						|
 | 
						|
    sig_types = (
 | 
						|
        'Reviewed',
 | 
						|
        'Reported',
 | 
						|
        'Tested',
 | 
						|
        'Suggested',
 | 
						|
        'Acked',
 | 
						|
        'Cc'
 | 
						|
        )
 | 
						|
 | 
						|
    def check_misc_signatures(self):
 | 
						|
        for sig in self.sig_types:
 | 
						|
            self.find_signatures(sig)
 | 
						|
 | 
						|
    cve_re = re.compile('CVE-[0-9]{4}-[0-9]{5}[^0-9]')
 | 
						|
 | 
						|
    def check_overall_format(self):
 | 
						|
        lines = self.msg.splitlines()
 | 
						|
 | 
						|
        if len(lines) >= 1 and lines[0].endswith('\r\n'):
 | 
						|
            empty_line = '\r\n'
 | 
						|
        else:
 | 
						|
            empty_line = '\n'
 | 
						|
 | 
						|
        lines.insert(0, empty_line)
 | 
						|
        lines.insert(0, self.subject + empty_line)
 | 
						|
 | 
						|
        count = len(lines)
 | 
						|
 | 
						|
        if count <= 0:
 | 
						|
            self.error('Empty commit message!')
 | 
						|
            return
 | 
						|
 | 
						|
        if count >= 1 and re.search(self.cve_re, lines[0]):
 | 
						|
            #
 | 
						|
            # If CVE-xxxx-xxxxx is present in subject line, then limit length of
 | 
						|
            # subject line to 92 characters
 | 
						|
            #
 | 
						|
            if len(lines[0].rstrip()) >= 93:
 | 
						|
                self.error(
 | 
						|
                    'First line of commit message (subject line) is too long (%d >= 93).' %
 | 
						|
                    (len(lines[0].rstrip()))
 | 
						|
                    )
 | 
						|
        else:
 | 
						|
            #
 | 
						|
            # If CVE-xxxx-xxxxx is not present in subject line, then limit
 | 
						|
            # length of subject line to 75 characters
 | 
						|
            #
 | 
						|
            if len(lines[0].rstrip()) >= 76:
 | 
						|
                self.error(
 | 
						|
                    'First line of commit message (subject line) is too long (%d >= 76).' %
 | 
						|
                    (len(lines[0].rstrip()))
 | 
						|
                    )
 | 
						|
 | 
						|
        if count >= 1 and len(lines[0].strip()) == 0:
 | 
						|
            self.error('First line of commit message (subject line) ' +
 | 
						|
                       'is empty.')
 | 
						|
 | 
						|
        if count >= 2 and lines[1].strip() != '':
 | 
						|
            self.error('Second line of commit message should be ' +
 | 
						|
                       'empty.')
 | 
						|
 | 
						|
        for i in range(2, count):
 | 
						|
            if (len(lines[i]) >= 76 and
 | 
						|
                len(lines[i].split()) > 1 and
 | 
						|
                not lines[i].startswith('git-svn-id:') and
 | 
						|
                not lines[i].startswith('Reviewed-by') and
 | 
						|
                not lines[i].startswith('Acked-by:') and
 | 
						|
                not lines[i].startswith('Tested-by:') and
 | 
						|
                not lines[i].startswith('Reported-by:') and
 | 
						|
                not lines[i].startswith('Suggested-by:') and
 | 
						|
                not lines[i].startswith('Signed-off-by:') and
 | 
						|
                not lines[i].startswith('Cc:')):
 | 
						|
                #
 | 
						|
                # Print a warning if body line is longer than 75 characters
 | 
						|
                #
 | 
						|
                print(
 | 
						|
                    'WARNING - Line %d of commit message is too long (%d >= 76).' %
 | 
						|
                    (i + 1, len(lines[i]))
 | 
						|
                    )
 | 
						|
                print(lines[i])
 | 
						|
 | 
						|
        last_sig_line = None
 | 
						|
        for i in range(count - 1, 0, -1):
 | 
						|
            line = lines[i]
 | 
						|
            mo = self.sig_block_re.match(line)
 | 
						|
            if mo is None:
 | 
						|
                if line.strip() == '':
 | 
						|
                    break
 | 
						|
                elif last_sig_line is not None:
 | 
						|
                    err2 = 'Add empty line before "%s"?' % last_sig_line
 | 
						|
                    self.error('The line before the signature block ' +
 | 
						|
                               'should be empty', err2)
 | 
						|
                else:
 | 
						|
                    self.error('The signature block was not found')
 | 
						|
                break
 | 
						|
            last_sig_line = line.strip()
 | 
						|
 | 
						|
(START, PRE_PATCH, PATCH) = range(3)
 | 
						|
 | 
						|
class GitDiffCheck:
 | 
						|
    """Checks the contents of a git diff."""
 | 
						|
 | 
						|
    def __init__(self, diff):
 | 
						|
        self.ok = True
 | 
						|
        self.format_ok = True
 | 
						|
        self.lines = diff.splitlines(True)
 | 
						|
        self.count = len(self.lines)
 | 
						|
        self.line_num = 0
 | 
						|
        self.state = START
 | 
						|
        self.new_bin = []
 | 
						|
        while self.line_num < self.count and self.format_ok:
 | 
						|
            line_num = self.line_num
 | 
						|
            self.run()
 | 
						|
            assert(self.line_num > line_num)
 | 
						|
        self.report_message_result()
 | 
						|
 | 
						|
    def report_message_result(self):
 | 
						|
        if Verbose.level < Verbose.NORMAL:
 | 
						|
            return
 | 
						|
        if self.ok:
 | 
						|
            print('The code passed all checks.')
 | 
						|
        if self.new_bin:
 | 
						|
            print('\nWARNING - The following binary files will be added ' +
 | 
						|
                  'into the repository:')
 | 
						|
            for binary in self.new_bin:
 | 
						|
                print('  ' + binary)
 | 
						|
 | 
						|
    def run(self):
 | 
						|
        line = self.lines[self.line_num]
 | 
						|
 | 
						|
        if self.state in (PRE_PATCH, PATCH):
 | 
						|
            if line.startswith('diff --git'):
 | 
						|
                self.state = START
 | 
						|
        if self.state == PATCH:
 | 
						|
            if line.startswith('@@ '):
 | 
						|
                self.state = PRE_PATCH
 | 
						|
            elif len(line) >= 1 and line[0] not in ' -+' and \
 | 
						|
                 not line.startswith('\r\n') and  \
 | 
						|
                 not line.startswith(r'\ No newline ') and not self.binary:
 | 
						|
                for line in self.lines[self.line_num + 1:]:
 | 
						|
                    if line.startswith('diff --git'):
 | 
						|
                        self.format_error('diff found after end of patch')
 | 
						|
                        break
 | 
						|
                self.line_num = self.count
 | 
						|
                return
 | 
						|
 | 
						|
        if self.state == START:
 | 
						|
            if line.startswith('diff --git'):
 | 
						|
                self.state = PRE_PATCH
 | 
						|
                self.filename = line[13:].split(' ', 1)[0]
 | 
						|
                self.is_newfile = False
 | 
						|
                self.force_crlf = True
 | 
						|
                self.force_notabs = True
 | 
						|
                if self.filename.endswith('.sh') or \
 | 
						|
                    self.filename.startswith('BaseTools/BinWrappers/PosixLike/') or \
 | 
						|
                    self.filename.startswith('BaseTools/Bin/CYGWIN_NT-5.1-i686/') or \
 | 
						|
                    self.filename == 'BaseTools/BuildEnv':
 | 
						|
                    #
 | 
						|
                    # Do not enforce CR/LF line endings for linux shell scripts.
 | 
						|
                    # Some linux shell scripts don't end with the ".sh" extension,
 | 
						|
                    # they are identified by their path.
 | 
						|
                    #
 | 
						|
                    self.force_crlf = False
 | 
						|
                if self.filename == '.gitmodules' or \
 | 
						|
                   self.filename == 'BaseTools/Conf/diff.order':
 | 
						|
                    #
 | 
						|
                    # .gitmodules and diff orderfiles are used internally by git
 | 
						|
                    # use tabs and LF line endings.  Do not enforce no tabs and
 | 
						|
                    # do not enforce CR/LF line endings.
 | 
						|
                    #
 | 
						|
                    self.force_crlf = False
 | 
						|
                    self.force_notabs = False
 | 
						|
            elif len(line.rstrip()) != 0:
 | 
						|
                self.format_error("didn't find diff command")
 | 
						|
            self.line_num += 1
 | 
						|
        elif self.state == PRE_PATCH:
 | 
						|
            if line.startswith('@@ '):
 | 
						|
                self.state = PATCH
 | 
						|
                self.binary = False
 | 
						|
            elif line.startswith('GIT binary patch') or \
 | 
						|
                 line.startswith('Binary files'):
 | 
						|
                self.state = PATCH
 | 
						|
                self.binary = True
 | 
						|
                if self.is_newfile:
 | 
						|
                    self.new_bin.append(self.filename)
 | 
						|
            elif line.startswith('new file mode 160000'):
 | 
						|
                #
 | 
						|
                # New submodule.  Do not enforce CR/LF line endings
 | 
						|
                #
 | 
						|
                self.force_crlf = False
 | 
						|
            else:
 | 
						|
                ok = False
 | 
						|
                self.is_newfile = self.newfile_prefix_re.match(line)
 | 
						|
                for pfx in self.pre_patch_prefixes:
 | 
						|
                    if line.startswith(pfx):
 | 
						|
                        ok = True
 | 
						|
                if not ok:
 | 
						|
                    self.format_error("didn't find diff hunk marker (@@)")
 | 
						|
            self.line_num += 1
 | 
						|
        elif self.state == PATCH:
 | 
						|
            if self.binary:
 | 
						|
                pass
 | 
						|
            elif line.startswith('-'):
 | 
						|
                pass
 | 
						|
            elif line.startswith('+'):
 | 
						|
                self.check_added_line(line[1:])
 | 
						|
            elif line.startswith('\r\n'):
 | 
						|
                pass
 | 
						|
            elif line.startswith(r'\ No newline '):
 | 
						|
                pass
 | 
						|
            elif not line.startswith(' '):
 | 
						|
                self.format_error("unexpected patch line")
 | 
						|
            self.line_num += 1
 | 
						|
 | 
						|
    pre_patch_prefixes = (
 | 
						|
        '--- ',
 | 
						|
        '+++ ',
 | 
						|
        'index ',
 | 
						|
        'new file ',
 | 
						|
        'deleted file ',
 | 
						|
        'old mode ',
 | 
						|
        'new mode ',
 | 
						|
        'similarity index ',
 | 
						|
        'copy from ',
 | 
						|
        'copy to ',
 | 
						|
        'rename ',
 | 
						|
        )
 | 
						|
 | 
						|
    line_endings = ('\r\n', '\n\r', '\n', '\r')
 | 
						|
 | 
						|
    newfile_prefix_re = \
 | 
						|
        re.compile(r'''^
 | 
						|
                       index\ 0+\.\.
 | 
						|
                   ''',
 | 
						|
                   re.VERBOSE)
 | 
						|
 | 
						|
    def added_line_error(self, msg, line):
 | 
						|
        lines = [ msg ]
 | 
						|
        if self.filename is not None:
 | 
						|
            lines.append('File: ' + self.filename)
 | 
						|
        lines.append('Line: ' + line)
 | 
						|
 | 
						|
        self.error(*lines)
 | 
						|
 | 
						|
    old_debug_re = \
 | 
						|
        re.compile(r'''
 | 
						|
                        DEBUG \s* \( \s* \( \s*
 | 
						|
                        (?: DEBUG_[A-Z_]+ \s* \| \s*)*
 | 
						|
                        EFI_D_ ([A-Z_]+)
 | 
						|
                   ''',
 | 
						|
                   re.VERBOSE)
 | 
						|
 | 
						|
    def check_added_line(self, line):
 | 
						|
        eol = ''
 | 
						|
        for an_eol in self.line_endings:
 | 
						|
            if line.endswith(an_eol):
 | 
						|
                eol = an_eol
 | 
						|
                line = line[:-len(eol)]
 | 
						|
 | 
						|
        stripped = line.rstrip()
 | 
						|
 | 
						|
        if self.force_crlf and eol != '\r\n' and (line.find('Subproject commit') == -1):
 | 
						|
            self.added_line_error('Line ending (%s) is not CRLF' % repr(eol),
 | 
						|
                                  line)
 | 
						|
        if self.force_notabs and '\t' in line:
 | 
						|
            self.added_line_error('Tab character used', line)
 | 
						|
        if len(stripped) < len(line):
 | 
						|
            self.added_line_error('Trailing whitespace found', line)
 | 
						|
 | 
						|
        mo = self.old_debug_re.search(line)
 | 
						|
        if mo is not None:
 | 
						|
            self.added_line_error('EFI_D_' + mo.group(1) + ' was used, '
 | 
						|
                                  'but DEBUG_' + mo.group(1) +
 | 
						|
                                  ' is now recommended', line)
 | 
						|
 | 
						|
    split_diff_re = re.compile(r'''
 | 
						|
                                   (?P<cmd>
 | 
						|
                                       ^ diff \s+ --git \s+ a/.+ \s+ b/.+ $
 | 
						|
                                   )
 | 
						|
                                   (?P<index>
 | 
						|
                                       ^ index \s+ .+ $
 | 
						|
                                   )
 | 
						|
                               ''',
 | 
						|
                               re.IGNORECASE | re.VERBOSE | re.MULTILINE)
 | 
						|
 | 
						|
    def format_error(self, err):
 | 
						|
        self.format_ok = False
 | 
						|
        err = 'Patch format error: ' + err
 | 
						|
        err2 = 'Line: ' + self.lines[self.line_num].rstrip()
 | 
						|
        self.error(err, err2)
 | 
						|
 | 
						|
    def error(self, *err):
 | 
						|
        if self.ok and Verbose.level > Verbose.ONELINE:
 | 
						|
            print('Code format is not valid:')
 | 
						|
        self.ok = False
 | 
						|
        if Verbose.level < Verbose.NORMAL:
 | 
						|
            return
 | 
						|
        count = 0
 | 
						|
        for line in err:
 | 
						|
            prefix = (' *', '  ')[count > 0]
 | 
						|
            print(prefix, line)
 | 
						|
            count += 1
 | 
						|
 | 
						|
class CheckOnePatch:
 | 
						|
    """Checks the contents of a git email formatted patch.
 | 
						|
 | 
						|
    Various checks are performed on both the commit message and the
 | 
						|
    patch content.
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, name, patch):
 | 
						|
        self.patch = patch
 | 
						|
        self.find_patch_pieces()
 | 
						|
 | 
						|
        email_check = EmailAddressCheck(self.author_email, 'Author')
 | 
						|
        email_ok = email_check.ok
 | 
						|
 | 
						|
        msg_check = CommitMessageCheck(self.commit_subject, self.commit_msg)
 | 
						|
        msg_ok = msg_check.ok
 | 
						|
 | 
						|
        diff_ok = True
 | 
						|
        if self.diff is not None:
 | 
						|
            diff_check = GitDiffCheck(self.diff)
 | 
						|
            diff_ok = diff_check.ok
 | 
						|
 | 
						|
        self.ok = email_ok and msg_ok and diff_ok
 | 
						|
 | 
						|
        if Verbose.level == Verbose.ONELINE:
 | 
						|
            if self.ok:
 | 
						|
                result = 'ok'
 | 
						|
            else:
 | 
						|
                result = list()
 | 
						|
                if not msg_ok:
 | 
						|
                    result.append('commit message')
 | 
						|
                if not diff_ok:
 | 
						|
                    result.append('diff content')
 | 
						|
                result = 'bad ' + ' and '.join(result)
 | 
						|
            print(name, result)
 | 
						|
 | 
						|
 | 
						|
    git_diff_re = re.compile(r'''
 | 
						|
                                 ^ diff \s+ --git \s+ a/.+ \s+ b/.+ $
 | 
						|
                             ''',
 | 
						|
                             re.IGNORECASE | re.VERBOSE | re.MULTILINE)
 | 
						|
 | 
						|
    stat_re = \
 | 
						|
        re.compile(r'''
 | 
						|
                       (?P<commit_message> [\s\S\r\n]* )
 | 
						|
                       (?P<stat>
 | 
						|
                           ^ --- $ [\r\n]+
 | 
						|
                           (?: ^ \s+ .+ \s+ \| \s+ \d+ \s+ \+* \-*
 | 
						|
                               $ [\r\n]+ )+
 | 
						|
                           [\s\S\r\n]+
 | 
						|
                       )
 | 
						|
                   ''',
 | 
						|
                   re.IGNORECASE | re.VERBOSE | re.MULTILINE)
 | 
						|
 | 
						|
    subject_prefix_re = \
 | 
						|
        re.compile(r'''^
 | 
						|
                       \s* (\[
 | 
						|
                        [^\[\]]* # Allow all non-brackets
 | 
						|
                       \])* \s*
 | 
						|
                   ''',
 | 
						|
                   re.VERBOSE)
 | 
						|
 | 
						|
    def find_patch_pieces(self):
 | 
						|
        if sys.version_info < (3, 0):
 | 
						|
            patch = self.patch.encode('ascii', 'ignore')
 | 
						|
        else:
 | 
						|
            patch = self.patch
 | 
						|
 | 
						|
        self.commit_msg = None
 | 
						|
        self.stat = None
 | 
						|
        self.commit_subject = None
 | 
						|
        self.commit_prefix = None
 | 
						|
        self.diff = None
 | 
						|
 | 
						|
        if patch.startswith('diff --git'):
 | 
						|
            self.diff = patch
 | 
						|
            return
 | 
						|
 | 
						|
        pmail = email.message_from_string(patch)
 | 
						|
        parts = list(pmail.walk())
 | 
						|
        assert(len(parts) == 1)
 | 
						|
        assert(parts[0].get_content_type() == 'text/plain')
 | 
						|
        content = parts[0].get_payload(decode=True).decode('utf-8', 'ignore')
 | 
						|
 | 
						|
        mo = self.git_diff_re.search(content)
 | 
						|
        if mo is not None:
 | 
						|
            self.diff = content[mo.start():]
 | 
						|
            content = content[:mo.start()]
 | 
						|
 | 
						|
        mo = self.stat_re.search(content)
 | 
						|
        if mo is None:
 | 
						|
            self.commit_msg = content
 | 
						|
        else:
 | 
						|
            self.stat = mo.group('stat')
 | 
						|
            self.commit_msg = mo.group('commit_message')
 | 
						|
        #
 | 
						|
        # Parse subject line from email header.  The subject line may be
 | 
						|
        # composed of multiple parts with different encodings.  Decode and
 | 
						|
        # combine all the parts to produce a single string with the contents of
 | 
						|
        # the decoded subject line.
 | 
						|
        #
 | 
						|
        parts = email.header.decode_header(pmail.get('subject'))
 | 
						|
        subject = ''
 | 
						|
        for (part, encoding) in parts:
 | 
						|
            if encoding:
 | 
						|
                part = part.decode(encoding)
 | 
						|
            else:
 | 
						|
                try:
 | 
						|
                    part = part.decode()
 | 
						|
                except:
 | 
						|
                    pass
 | 
						|
            subject = subject + part
 | 
						|
 | 
						|
        self.commit_subject = subject.replace('\r\n', '')
 | 
						|
        self.commit_subject = self.commit_subject.replace('\n', '')
 | 
						|
        self.commit_subject = self.subject_prefix_re.sub('', self.commit_subject, 1)
 | 
						|
 | 
						|
        self.author_email = pmail['from']
 | 
						|
 | 
						|
class CheckGitCommits:
 | 
						|
    """Reads patches from git based on the specified git revision range.
 | 
						|
 | 
						|
    The patches are read from git, and then checked.
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, rev_spec, max_count):
 | 
						|
        commits = self.read_commit_list_from_git(rev_spec, max_count)
 | 
						|
        if len(commits) == 1 and Verbose.level > Verbose.ONELINE:
 | 
						|
            commits = [ rev_spec ]
 | 
						|
        self.ok = True
 | 
						|
        blank_line = False
 | 
						|
        for commit in commits:
 | 
						|
            if Verbose.level > Verbose.ONELINE:
 | 
						|
                if blank_line:
 | 
						|
                    print()
 | 
						|
                else:
 | 
						|
                    blank_line = True
 | 
						|
                print('Checking git commit:', commit)
 | 
						|
            email = self.read_committer_email_address_from_git(commit)
 | 
						|
            self.ok &= EmailAddressCheck(email, 'Committer').ok
 | 
						|
            patch = self.read_patch_from_git(commit)
 | 
						|
            self.ok &= CheckOnePatch(commit, patch).ok
 | 
						|
        if not commits:
 | 
						|
            print("Couldn't find commit matching: '{}'".format(rev_spec))
 | 
						|
 | 
						|
    def read_commit_list_from_git(self, rev_spec, max_count):
 | 
						|
        # Run git to get the commit patch
 | 
						|
        cmd = [ 'rev-list', '--abbrev-commit', '--no-walk' ]
 | 
						|
        if max_count is not None:
 | 
						|
            cmd.append('--max-count=' + str(max_count))
 | 
						|
        cmd.append(rev_spec)
 | 
						|
        out = self.run_git(*cmd)
 | 
						|
        return out.split() if out else []
 | 
						|
 | 
						|
    def read_patch_from_git(self, commit):
 | 
						|
        # Run git to get the commit patch
 | 
						|
        return self.run_git('show', '--pretty=email', '--no-textconv',
 | 
						|
                            '--no-use-mailmap', commit)
 | 
						|
 | 
						|
    def read_committer_email_address_from_git(self, commit):
 | 
						|
        # Run git to get the committer email
 | 
						|
        return self.run_git('show', '--pretty=%cn <%ce>', '--no-patch',
 | 
						|
                            '--no-use-mailmap', commit)
 | 
						|
 | 
						|
    def run_git(self, *args):
 | 
						|
        cmd = [ 'git' ]
 | 
						|
        cmd += args
 | 
						|
        p = subprocess.Popen(cmd,
 | 
						|
                     stdout=subprocess.PIPE,
 | 
						|
                     stderr=subprocess.STDOUT)
 | 
						|
        Result = p.communicate()
 | 
						|
        return Result[0].decode('utf-8', 'ignore') if Result[0] and Result[0].find(b"fatal")!=0 else None
 | 
						|
 | 
						|
class CheckOnePatchFile:
 | 
						|
    """Performs a patch check for a single file.
 | 
						|
 | 
						|
    stdin is used when the filename is '-'.
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, patch_filename):
 | 
						|
        if patch_filename == '-':
 | 
						|
            patch = sys.stdin.read()
 | 
						|
            patch_filename = 'stdin'
 | 
						|
        else:
 | 
						|
            f = open(patch_filename, 'rb')
 | 
						|
            patch = f.read().decode('utf-8', 'ignore')
 | 
						|
            f.close()
 | 
						|
        if Verbose.level > Verbose.ONELINE:
 | 
						|
            print('Checking patch file:', patch_filename)
 | 
						|
        self.ok = CheckOnePatch(patch_filename, patch).ok
 | 
						|
 | 
						|
class CheckOneArg:
 | 
						|
    """Performs a patch check for a single command line argument.
 | 
						|
 | 
						|
    The argument will be handed off to a file or git-commit based
 | 
						|
    checker.
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, param, max_count=None):
 | 
						|
        self.ok = True
 | 
						|
        if param == '-' or os.path.exists(param):
 | 
						|
            checker = CheckOnePatchFile(param)
 | 
						|
        else:
 | 
						|
            checker = CheckGitCommits(param, max_count)
 | 
						|
        self.ok = checker.ok
 | 
						|
 | 
						|
class PatchCheckApp:
 | 
						|
    """Checks patches based on the command line arguments."""
 | 
						|
 | 
						|
    def __init__(self):
 | 
						|
        self.parse_options()
 | 
						|
        patches = self.args.patches
 | 
						|
 | 
						|
        if len(patches) == 0:
 | 
						|
            patches = [ 'HEAD' ]
 | 
						|
 | 
						|
        self.ok = True
 | 
						|
        self.count = None
 | 
						|
        for patch in patches:
 | 
						|
            self.process_one_arg(patch)
 | 
						|
 | 
						|
        if self.count is not None:
 | 
						|
            self.process_one_arg('HEAD')
 | 
						|
 | 
						|
        if self.ok:
 | 
						|
            self.retval = 0
 | 
						|
        else:
 | 
						|
            self.retval = -1
 | 
						|
 | 
						|
    def process_one_arg(self, arg):
 | 
						|
        if len(arg) >= 2 and arg[0] == '-':
 | 
						|
            try:
 | 
						|
                self.count = int(arg[1:])
 | 
						|
                return
 | 
						|
            except ValueError:
 | 
						|
                pass
 | 
						|
        self.ok &= CheckOneArg(arg, self.count).ok
 | 
						|
        self.count = None
 | 
						|
 | 
						|
    def parse_options(self):
 | 
						|
        parser = argparse.ArgumentParser(description=__copyright__)
 | 
						|
        parser.add_argument('--version', action='version',
 | 
						|
                            version='%(prog)s ' + VersionNumber)
 | 
						|
        parser.add_argument('patches', nargs='*',
 | 
						|
                            help='[patch file | git rev list]')
 | 
						|
        group = parser.add_mutually_exclusive_group()
 | 
						|
        group.add_argument("--oneline",
 | 
						|
                           action="store_true",
 | 
						|
                           help="Print one result per line")
 | 
						|
        group.add_argument("--silent",
 | 
						|
                           action="store_true",
 | 
						|
                           help="Print nothing")
 | 
						|
        self.args = parser.parse_args()
 | 
						|
        if self.args.oneline:
 | 
						|
            Verbose.level = Verbose.ONELINE
 | 
						|
        if self.args.silent:
 | 
						|
            Verbose.level = Verbose.SILENT
 | 
						|
 | 
						|
if __name__ == "__main__":
 | 
						|
    sys.exit(PatchCheckApp().retval)
 |