From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mga02.intel.com (mga02.intel.com [134.134.136.20]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ml01.01.org (Postfix) with ESMTPS id 2CD7F820A8 for ; Mon, 19 Dec 2016 00:31:19 -0800 (PST) Received: from fmsmga001.fm.intel.com ([10.253.24.23]) by orsmga101.jf.intel.com with ESMTP; 19 Dec 2016 00:31:18 -0800 X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.33,372,1477983600"; d="scan'208";a="1083974829" Received: from fmsmsx103.amr.corp.intel.com ([10.18.124.201]) by fmsmga001.fm.intel.com with ESMTP; 19 Dec 2016 00:31:18 -0800 Received: from fmsmsx101.amr.corp.intel.com (10.18.124.199) by FMSMSX103.amr.corp.intel.com (10.18.124.201) with Microsoft SMTP Server (TLS) id 14.3.248.2; Mon, 19 Dec 2016 00:31:18 -0800 Received: from shsmsx151.ccr.corp.intel.com (10.239.6.50) by fmsmsx101.amr.corp.intel.com (10.18.124.199) with Microsoft SMTP Server (TLS) id 14.3.248.2; Mon, 19 Dec 2016 00:31:17 -0800 Received: from shsmsx102.ccr.corp.intel.com ([169.254.2.54]) by SHSMSX151.ccr.corp.intel.com ([169.254.3.77]) with mapi id 14.03.0248.002; Mon, 19 Dec 2016 16:31:14 +0800 From: "Gao, Liming" To: Daniil Egranov , "edk2-devel@lists.01.org" CC: "leif.lindholm@linaro.org" , "Justen, Jordan L" Thread-Topic: [PATCH v7] BaseTools/Scripts/PatchCheck.py: Extended patch style check for c code Thread-Index: AQHSV/HBOx7oGwCOu0yLbM5q1Mn2CaEO87nA Date: Mon, 19 Dec 2016 08:31:13 +0000 Message-ID: <4A89E2EF3DFEDB4C8BFDE51014F606A14D6B289C@shsmsx102.ccr.corp.intel.com> References: <1481929878-30370-1-git-send-email-daniil.egranov@arm.com> In-Reply-To: <1481929878-30370-1-git-send-email-daniil.egranov@arm.com> Accept-Language: en-US X-MS-Has-Attach: X-MS-TNEF-Correlator: x-originating-ip: [10.239.127.40] MIME-Version: 1.0 Subject: Re: [PATCH v7] BaseTools/Scripts/PatchCheck.py: Extended patch style check for c code X-BeenThere: edk2-devel@lists.01.org X-Mailman-Version: 2.1.21 Precedence: list List-Id: EDK II Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Mon, 19 Dec 2016 08:31:19 -0000 Content-Language: en-US Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: quoted-printable Daniil: This version has addressed my comments. I see Jordan also provides some c= omments. Are they handled in this patch? Thanks Liming > -----Original Message----- > From: Daniil Egranov [mailto:daniil.egranov@arm.com] > Sent: Saturday, December 17, 2016 7:11 AM > To: edk2-devel@lists.01.org > Cc: leif.lindholm@linaro.org; Gao, Liming ; Daniil > Egranov > Subject: [PATCH v7] BaseTools/Scripts/PatchCheck.py: Extended patch style > check for c code >=20 > Changed output format to make it more comapct. > Disabled colored output by default. For terminals which support ANSI > escape codes, colored output can be enabled with the "--color" command > line parameter. >=20 > Contributed-under: TianoCore Contribution Agreement 1.0 > Signed-off-by: Daniil Egranov > --- > Changelog: >=20 > v6 > Added reporting a source code line number in case of error when > checking a patch file or using a git sha1 commit string. In case of > a patch file, both patch and source code line numbers will be provided. > Corrected handling the case with #define foo(..) macro. The absence of > the space before open parenthesis is not reported as the error anymore. > Added checking for lower case "void" and "static" words. > Added colors for error messages (experimental?) >=20 > v5 > Corrected code checking for multi-line and commented lines. Both > multi-line and open comment flags will be reset when leaving > diff "+" area of the patch. > Changed version of the tool to 0.2. >=20 > v4 > Corrected maximum code line size to 120 characters. >=20 > v3 > Corrected space detection before parentheses. >=20 > v2: > Fixed several indentation cases >=20 > v1: > Fixed reporting signature error for a cover letter. > Fixed reporting line size error for a file change information > included in the commit message. > Fixed line number reported in PatchCheck error messages. It > points to the correct line in the diff file. > The patch extends style checking for c code: > Added check for code indentation. > Added report line size greater than 80 characters. > Added checking for space before '('. > Added checking for space before '{'. > Added checking for '}' to be on a new line and have spaces > for "} else {" or "} while ()" cases. >=20 > BaseTools/Scripts/PatchCheck.py | 379 > ++++++++++++++++++++++++++++++++++++---- > 1 file changed, 341 insertions(+), 38 deletions(-) >=20 > diff --git a/BaseTools/Scripts/PatchCheck.py > b/BaseTools/Scripts/PatchCheck.py > index 7c30082..eae084c 100755 > --- a/BaseTools/Scripts/PatchCheck.py > +++ b/BaseTools/Scripts/PatchCheck.py > @@ -15,7 +15,7 @@ >=20 > from __future__ import print_function >=20 > -VersionNumber =3D '0.1' > +VersionNumber =3D '0.2' > __copyright__ =3D "Copyright (c) 2015 - 2016, Intel Corporation All rig= hts > reserved." >=20 > import email > @@ -25,6 +25,44 @@ import re > import subprocess > import sys >=20 > +class MsgFormat: > + ERROR, WARNING, REPORT, LINK, NORMAL =3D range(5) > + color_on =3D False > + > +class ColorTypes: > + RED =3D '\033[91m' > + GREEN =3D '\033[92m' > + BLUE =3D '\033[94m' > + CYAN =3D '\033[96m' > + WHITE =3D '\033[97m' > + YELLOW =3D '\033[93m' > + MAGENTA =3D '\033[95m' > + GREY =3D '\033[90m' > + BLACK =3D '\033[90m' > + DEFAULT =3D '\033[0m' > + > +class TextColor: > + def __init__(self, color_on, msg_format): > + self.color =3D '' > + if color_on =3D=3D True: > + if msg_format =3D=3D MsgFormat.ERROR: > + self.color =3D ColorTypes.RED > + elif msg_format =3D=3D MsgFormat.WARNING: > + self.color =3D ColorTypes.YELLOW > + elif msg_format =3D=3D MsgFormat.REPORT: > + self.color =3D ColorTypes.CYAN > + elif msg_format =3D=3D MsgFormat.LINK: > + self.color =3D ColorTypes.BLUE > + elif msg_format =3D=3D MsgFormat.NORMAL: > + self.color =3D ColorTypes.DEFAULT > + else: > + self.color =3D ColorTypes.DEFAULT > + else: > + self.color =3D '' > + > + def __str__(self): > + return self.color > + > class Verbose: > SILENT, ONELINE, NORMAL =3D range(3) > level =3D NORMAL > @@ -32,7 +70,7 @@ class Verbose: > class CommitMessageCheck: > """Checks the contents of a git commit message.""" >=20 > - def __init__(self, subject, message): > + def __init__(self, subject, message, message_offset, cover): > self.ok =3D True >=20 > if subject is None and message is None: > @@ -41,9 +79,15 @@ class CommitMessageCheck: >=20 > self.subject =3D subject > self.msg =3D message > + self.msg_offset =3D message_offset > + self.cover =3D cover > + > + if not cover: > + self.check_contributed_under() > + self.check_signed_off_by() > + else: > + print('The commit message is cover letter.') >=20 > - self.check_contributed_under() > - self.check_signed_off_by() > self.check_misc_signatures() > self.check_overall_format() > self.report_message_result() > @@ -60,9 +104,11 @@ class CommitMessageCheck: > else: > return_code =3D 1 > if not self.ok: > - print(self.url) > + print('\n' + self.url) >=20 > def error(self, *err): > + start =3D TextColor(MsgFormat.color_on, MsgFormat.ERROR) > + end =3D TextColor(MsgFormat.color_on, MsgFormat.NORMAL) > if self.ok and Verbose.level > Verbose.ONELINE: > print('The commit message format is not valid:') > self.ok =3D False > @@ -71,7 +117,7 @@ class CommitMessageCheck: > count =3D 0 > for line in err: > prefix =3D (' *', ' ')[count > 0] > - print(prefix, line) > + print(start, prefix, line, end) > count +=3D 1 >=20 > def check_contributed_under(self): > @@ -180,7 +226,12 @@ class CommitMessageCheck: > for sig in self.sig_types: > self.find_signatures(sig) >=20 > + diff_change_info_re =3D \ > + re.compile(r'.*\|\s+(\d|Bin)*\s.*[\+\-]') > + > def check_overall_format(self): > + start =3D TextColor(MsgFormat.color_on, MsgFormat.REPORT) > + end =3D TextColor(MsgFormat.color_on, MsgFormat.NORMAL) > lines =3D self.msg.splitlines() >=20 > if len(lines) >=3D 1 and lines[0].endswith('\r\n'): > @@ -197,9 +248,10 @@ class CommitMessageCheck: > self.error('Empty commit message!') > return >=20 > - if count >=3D 1 and len(lines[0]) >=3D 72: > + if count >=3D 1 and len(lines[0]) > 72: > self.error('First line of commit message (subject line) ' + > - 'is too long.') > + 'is too long (%d) (max 72 characters): ' %(len(li= nes[0])) + > + str(start) + lines[0] + str(end)) >=20 > if count >=3D 1 and len(lines[0].strip()) =3D=3D 0: > self.error('First line of commit message (subject line) ' + > @@ -210,10 +262,13 @@ class CommitMessageCheck: > 'empty.') >=20 > for i in range(2, count): > - if (len(lines[i]) >=3D 76 and > + if (len(lines[i]) > 76 and > len(lines[i].split()) > 1 and > - not lines[i].startswith('git-svn-id:')): > - self.error('Line %d of commit message is too long.' % (i= + 1)) > + not lines[i].startswith('git-svn-id:') and > + self.diff_change_info_re.search(lines[i]) is None): > + self.error('Line %d of commit message is too long (%d) (= max 76 > characters): ' \ > + % (i + self.msg_offset - 1, len(lines[i])) + > + str(start) + lines[i] + str(end)) >=20 > last_sig_line =3D None > for i in range(count - 1, 0, -1): > @@ -236,13 +291,21 @@ class CommitMessageCheck: > class GitDiffCheck: > """Checks the contents of a git diff.""" >=20 > - def __init__(self, diff): > + def __init__(self, diff, offset, patch_type): > self.ok =3D True > self.format_ok =3D True > self.lines =3D diff.splitlines(True) > self.count =3D len(self.lines) > self.line_num =3D 0 > self.state =3D START > + self.comments_open =3D False > + self.multiline_string =3D False > + self.current_indent_size =3D 0 > + self.parentheses_count =3D 0 > + self.offset =3D offset > + self.file_type =3D "other_type" > + self.patch_type =3D patch_type > + self.src_line_num =3D 0; > while self.line_num < self.count and self.format_ok: > line_num =3D self.line_num > self.run() > @@ -255,6 +318,14 @@ class GitDiffCheck: > if self.ok: > print('The code passed all checks.') >=20 > + def clean_counts(self): > + self.current_indent_size =3D -1 > + self.parentheses_count =3D 0 > + self.comments_open =3D False > + self.multiline_string =3D False > + > + diff_src_line_start_re =3D re.compile(r'(\+\d+)') > + > def run(self): > line =3D self.lines[self.line_num] >=20 > @@ -281,9 +352,28 @@ class GitDiffCheck: > self.format_error("didn't find diff command") > self.line_num +=3D 1 > elif self.state =3D=3D PRE_PATCH: > - if line.startswith('+++ b/'): > + if line.startswith('+++ '): > self.set_filename(line[6:].rstrip()) > + print("\nChecking patch for %s ..." %(self.hunk_filename= )) > + if self.hunk_filename.endswith(".c") or \ > + self.hunk_filename.endswith(".h"): > + self.file_type =3D "c_type" > + elif self.hunk_filename.endswith(".dec") or \ > + self.hunk_filename.endswith(".dsc") or \ > + self.hunk_filename.endswith(".fdf"): > + self.file_type =3D "edk2_type" > + elif self.hunk_filename.endswith(".S"): > + self.file_type =3D "asm_arm_type" > + elif self.hunk_filename.endswith(".asm"): > + self.file_type =3D "asm_type" > + else: > + self.file_type =3D "other_type" > if line.startswith('@@ '): > + sls =3D self.diff_src_line_start_re.search(line) > + if sls is not None: > + self.src_line_num =3D int(sls.group(0)) > + else: > + self.src_line_num =3D 0 > self.state =3D PATCH > self.binary =3D False > elif line.startswith('GIT binary patch'): > @@ -299,15 +389,25 @@ class GitDiffCheck: > self.line_num +=3D 1 > elif self.state =3D=3D PATCH: > if self.binary: > + self.clean_counts() > pass > if line.startswith('-'): > + self.clean_counts() > pass > elif line.startswith('+'): > - self.check_added_line(line[1:]) > + # indentation resets every time we leave "+" diff area > + self.check_added_line(line[1:], self.line_num + self.off= set, > + self.src_line_num) > + self.src_line_num +=3D1 > elif line.startswith(r'\ No newline '): > + self.clean_counts() > pass > elif not line.startswith(' '): > + self.clean_counts() > self.format_error("unexpected patch line") > + else: > + self.src_line_num +=3D1 > + self.clean_counts() > self.line_num +=3D 1 >=20 > pre_patch_prefixes =3D ( > @@ -332,13 +432,19 @@ class GitDiffCheck: > else: > self.force_crlf =3D True >=20 > - def added_line_error(self, msg, line): > + def added_line_error(self, msg, line, diff_line_num, src_line_num, > patch_type): > + start =3D TextColor(MsgFormat.color_on, MsgFormat.REPORT) > + end =3D TextColor(MsgFormat.color_on, MsgFormat.NORMAL) > lines =3D [ msg ] > - if self.hunk_filename is not None: > - lines.append('File: ' + self.hunk_filename) > - lines.append('Line: ' + line) > - > self.error(*lines) > + if self.hunk_filename is not None: > + print('File: ' + self.hunk_filename) > + line_info =3D 'Line ' > + if patch_type =3D=3D 'patch_file_type': > + line_info +=3D ('%d(patch) ' %(diff_line_num)) > + line_info +=3D ('%d(source) ' %(src_line_num)) > + line_info +=3D (': ' +str(start) + line.lstrip() + str(end)) > + print(line_info) >=20 > old_debug_re =3D \ > re.compile(r''' > @@ -348,28 +454,195 @@ class GitDiffCheck: > ''', > re.VERBOSE) >=20 > - def check_added_line(self, line): > + space_check_parentheses_re =3D re.compile(r'#define\s*[a-zA-Z0- > 9_]\S*\(|[a-zA-Z0-9_]\S*\((? + space_check_brace_open_re =3D re.compile(r'[a-zA-Z0-9_]\S*\{') > + space_check_brace_close_re =3D re.compile(r'[\}]\S.*') > + brace_open_check_re =3D re.compile(r'[a-zA-Z0-9_]*\{') > + brace_close_check_re =3D re.compile(r'.*\}.*') > + multiline_check_re =3D re.compile(r'[a-zA-Z0-9_]*(\\|\&|\,|\+|\- > |\*|\||\=3D)\Z') > + void_cap_check_re =3D re.compile(r'(\s|\b|\()void(\s+|\*|\,|\))') > + static_cap_check_re =3D re.compile(r'(\s|\b)static\s+') > + > + def check_added_line(self, line, diff_line_num, src_line_num): > eol =3D '' > for an_eol in self.line_endings: > if line.endswith(an_eol): > eol =3D an_eol > line =3D line[:-len(eol)] >=20 > - stripped =3D line.rstrip() > + rstripped =3D line.rstrip() > + lstripped =3D line.lstrip() >=20 > if self.force_crlf and eol !=3D '\r\n': > self.added_line_error('Line ending (%s) is not CRLF' % repr(= eol), > - line) > + line, diff_line_num, src_line_num, sel= f.patch_type) > if '\t' in line: > - self.added_line_error('Tab character used', line) > - if len(stripped) < len(line): > - self.added_line_error('Trailing whitespace found', line) > + self.added_line_error('Tab character used', > + line, diff_line_num, src_line_num, sel= f.patch_type) > + if len(rstripped) < len(line): > + self.added_line_error('Trailing whitespace found', > + line, diff_line_num, src_line_num, sel= f.patch_type) > + > + # empty "+" line, skip it > + if len(line) =3D=3D 0: > + return >=20 > - mo =3D 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) > + # the file type specific checks > + if self.file_type =3D=3D "c_type": > + > + # set initial indentation as the number of spaces after the = first "+" > + # indentation does not apply on lines with 0 characters > + # adjust the indentation if first "+" has "}" > + if self.current_indent_size =3D=3D -1: > + if len(lstripped) !=3D 0: > + self.current_indent_size =3D len(line) - len(lstripp= ed) > + else: > + self.clean_counts() > + > + if lstripped.startswith("}"): > + self.current_indent_size =3D line.find("}") + 2 > + > + indent_size =3D len(line) - len(lstripped) > + indent_size_adjust =3D 0 > + force_brace_check =3D False > + > + if len(line) > 120: > + self.added_line_error('Line is too long (%d) (max 120 ch= aracters)', > + line, diff_line_num, src_line_num,= self.patch_type) > + > + # skip comments for code checking > + if lstripped.startswith("//"): > + return > + if lstripped.startswith("/*") and not self.comments_open: > + self.comments_open =3D True > + > + if rstripped.endswith("*/") and self.comments_open: > + self.comments_open =3D False > + return > + > + if rstripped.endswith("*/") and not self.comments_open: > + # found unknown close comment, reset the indentation as > + # no idea where it started > + self.current_indent_size =3D -1 > + return > + > + if not self.comments_open: > + mo =3D 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, diff_line_num, src_line_= num, self.patch_type) > + > + # track down multiple braces in the same line > + braces_count =3D line.count('{') - line.count('}') > + > + # check if line has text in double quotes as we do not > + # need to check text messages > + text_str_start =3D line.find('\"') > + > + # track down a special case with "} else ..{" braces > + if line.find("else") !=3D -1: > + force_brace_check =3D True > + > + # check for closing brace which affects indentation > + if braces_count < 0 and not force_brace_check: > + self.current_indent_size -=3D 2 * abs(braces_count) > + elif force_brace_check: > + if line.find("}") !=3D -1: > + self.current_indent_size -=3D 2 > + braces_count =3D 1 > + > + # for the switch()/case:, adjust "case:" and "default:" = indentation > + # as they aligned with the "switch()" indentation > + if lstripped.startswith("case ") or \ > + lstripped.startswith("default:"): > + indent_size_adjust =3D -2 > + > + # indentation code check related to the diff structure > + # do not check code indentation between parentheses > + if indent_size !=3D self.current_indent_size + indent_si= ze_adjust \ > + and len(line) !=3D 0 and self.parentheses_count =3D= =3D 0 \ > + and not self.multiline_string: > + self.added_line_error('Invalid indentation. Expe= cting %d but > found %d spaces' \ > + %(self.current_indent_size= + indent_size_adjust, \ > + indent_size), > + line, diff_line_num, src_l= ine_num, self.patch_type) > + > + # check for missing space before open brace > + if braces_count > 0 or force_brace_check: > + if line.find("{") !=3D -1: > + self.current_indent_size +=3D 2 * braces_count > + if not line.startswith("{"): > + spb_check =3D self.space_check_brace_open_re.sea= rch(line) > + if spb_check is not None: > + self.added_line_error('Missing space before = \'{\'', > + line, diff_line_num, s= rc_line_num, self.patch_type) > + > + # check for missing space before open parenthesis > + spo =3D self.space_check_parentheses_re.finditer(line) > + if spo is not None and not self.multiline_string: > + # check if it's not inside of the double quoted stri= ng or > + # (aa)(bb)fff case or parenthesis belong to #define = macro > + for parstr in spo: > + if not (text_str_start !=3D -1 and text_str_star= t < parstr.start()) > and \ > + not parstr.group(0).startswith("#define"): > + self.added_line_error('Missing space before = \'(\'', > + line, diff_line_num, s= rc_line_num, self.patch_type) > + break; > + > + # check for closing brace cases > + if braces_count < 0 or \ > + (force_brace_check and line.find("}") !=3D -1): > + if not lstripped.startswith("}") and rstripped.endsw= ith("}"): > + self.added_line_error('The \'}\' is not on its o= wn line', > + line, diff_line_num, src_l= ine_num, self.patch_type) > + if not lstripped.endswith("}"): > + scb =3D self.space_check_brace_close_re.search(l= stripped) > + if scb is not None \ > + and lstripped[scb.start() + 1] !=3D ';' \ > + and lstripped[scb.start() + 1] !=3D ',' \ > + and lstripped[scb.start() + 1] !=3D '}': > + self.added_line_error('Missing space after \= '}\'', > + line, diff_line_num, s= rc_line_num, self.patch_type) > + > + # check for using "void" instead of "VOID" > + vcc =3D self.void_cap_check_re.search(line) > + if vcc is not None and \ > + not (text_str_start !=3D -1 and text_str_start < vcc= .start()): > + self.added_line_error('Using \"void\" instead of \"V= OID\"', > + line, diff_line_num, src_line_= num, self.patch_type) > + > + #check for using "static" instead of "STATIC" > + scc =3D self.static_cap_check_re.search(line) > + if scc is not None and \ > + not (text_str_start !=3D -1 and text_str_start < scc= .start()): > + self.added_line_error('Using \"static\" instead of \= "STATIC\"', > + line, diff_line_num, src_line_= num, self.patch_type) > + > + # track down parentheses, no indentation check between t= hem > + if not (self.parentheses_count =3D=3D 0 and lstripped.st= artswith(')') \ > + and not self.multiline_string and not self.comme= nts_open): > + self.parentheses_count +=3D line.count('(') - line.c= ount(')') > + > + # multiline strings indentation is unknown > + if not self.comments_open: > + mlc =3D self.multiline_check_re.search(line) > + if mlc is not None: > + self.multiline_string =3D True > + else: > + self.multiline_string =3D False > + > + elif self.file_type =3D=3D "edk2_type": > + pass > + elif self.file_type =3D=3D "asm_arm_type": > + pass > + elif self.file_type =3D=3D "asm_type": > + pass > + elif self.file_type =3D=3D "other_type": > + pass > + else: > + pass >=20 > split_diff_re =3D re.compile(r''' > (?P > @@ -388,6 +661,8 @@ class GitDiffCheck: > self.error(err, err2) >=20 > def error(self, *err): > + start =3D TextColor(MsgFormat.color_on, MsgFormat.ERROR) > + end =3D TextColor(MsgFormat.color_on, MsgFormat.NORMAL) > if self.ok and Verbose.level > Verbose.ONELINE: > print('Code format is not valid:') > self.ok =3D False > @@ -396,7 +671,7 @@ class GitDiffCheck: > count =3D 0 > for line in err: > prefix =3D (' *', ' ')[count > 0] > - print(prefix, line) > + print(start, prefix, line, end) > count +=3D 1 >=20 > class CheckOnePatch: > @@ -406,16 +681,17 @@ class CheckOnePatch: > patch content. > """ >=20 > - def __init__(self, name, patch): > + def __init__(self, name, patch, patch_type): > self.patch =3D patch > self.find_patch_pieces() >=20 > - msg_check =3D CommitMessageCheck(self.commit_subject, > self.commit_msg) > + msg_check =3D CommitMessageCheck(self.commit_subject, > self.commit_msg, \ > + self.commit_msg_offset, self.cove= r) > msg_ok =3D msg_check.ok >=20 > diff_ok =3D True > if self.diff is not None: > - diff_check =3D GitDiffCheck(self.diff) > + diff_check =3D GitDiffCheck(self.diff, self.diff_offset, pat= ch_type) > diff_ok =3D diff_check.ok >=20 > self.ok =3D msg_ok and diff_ok > @@ -458,6 +734,16 @@ class CheckOnePatch: > ''', > re.VERBOSE) >=20 > + cover_letter_re =3D re.compile(r'Subject:\s*\[PATCH.*.0/.*', > + re.IGNORECASE | re.MULTILINE) > + > + def is_cover_letter(self, patch): > + cl =3D self.cover_letter_re.search(patch) > + if cl is None: > + return False > + > + return True > + > def find_patch_pieces(self): > if sys.version_info < (3, 0): > patch =3D self.patch.encode('ascii', 'ignore') > @@ -465,10 +751,13 @@ class CheckOnePatch: > patch =3D self.patch >=20 > self.commit_msg =3D None > + self.commit_msg_offset =3D 0 > self.stat =3D None > self.commit_subject =3D None > self.commit_prefix =3D None > self.diff =3D None > + self.diff_offset =3D 0 > + self.cover =3D False >=20 > if patch.startswith('diff --git'): > self.diff =3D patch > @@ -480,6 +769,14 @@ class CheckOnePatch: > assert(parts[0].get_content_type() =3D=3D 'text/plain') > content =3D parts[0].get_payload(decode=3DTrue).decode('utf-8', = 'ignore') >=20 > + # find offset of content > + self.commit_msg_offset =3D len(patch.splitlines()) - > len(content.splitlines()) > + > + # find offset of first diff section > + mo =3D self.git_diff_re.search(patch) > + if mo is not None: > + self.diff_offset =3D len(patch[:mo.start()].splitlines()) + = 1 > + > mo =3D self.git_diff_re.search(content) > if mo is not None: > self.diff =3D content[mo.start():] > @@ -492,6 +789,7 @@ class CheckOnePatch: > self.stat =3D mo.group('stat') > self.commit_msg =3D mo.group('commit_message') >=20 > + self.cover =3D self.is_cover_letter(patch) > self.commit_subject =3D pmail['subject'].replace('\r\n', '') > self.commit_subject =3D self.commit_subject.replace('\n', '') > self.commit_subject =3D self.subject_prefix_re.sub('', > self.commit_subject, 1) > @@ -514,9 +812,9 @@ class CheckGitCommits: > print() > else: > blank_line =3D True > - print('Checking git commit:', commit) > + print('Checking git commit:', commit, '\n') > patch =3D self.read_patch_from_git(commit) > - self.ok &=3D CheckOnePatch(commit, patch).ok > + self.ok &=3D CheckOnePatch(commit, patch, "git_commit_type")= .ok >=20 > def read_commit_list_from_git(self, rev_spec, max_count): > # Run git to get the commit patch > @@ -554,8 +852,8 @@ class CheckOnePatchFile: > patch =3D f.read().decode('utf-8', 'ignore') > f.close() > if Verbose.level > Verbose.ONELINE: > - print('Checking patch file:', patch_filename) > - self.ok =3D CheckOnePatch(patch_filename, patch).ok > + print('Checking patch file:', patch_filename, '\n') > + self.ok =3D CheckOnePatch(patch_filename, patch, "patch_file_typ= e").ok >=20 > class CheckOneArg: > """Performs a patch check for a single command line argument. > @@ -618,11 +916,16 @@ class PatchCheckApp: > group.add_argument("--silent", > action=3D"store_true", > help=3D"Print nothing") > + group.add_argument("--color", > + action=3D"store_true", > + help=3D"Enable color messages") > self.args =3D parser.parse_args() > if self.args.oneline: > Verbose.level =3D Verbose.ONELINE > if self.args.silent: > Verbose.level =3D Verbose.SILENT > + if self.args.color: > + MsgFormat.color_on =3D True >=20 > if __name__ =3D=3D "__main__": > sys.exit(PatchCheckApp().retval) > -- > 2.7.4