2 # Copyright (c) 2016, 2017 Red Hat, Inc.
3 # Copyright (c) 2018 Nicira, Inc.
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at:
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16 from __future__
import print_function
27 extra_keywords
= ['ovs', 'vswitch', 'vswitchd', 'ovs-vswitchd', 'netdev',
28 'selinux', 'ovs-ctl', 'dpctl', 'ofctl', 'openvswitch',
29 'dpdk', 'hugepage', 'hugepages', 'pmd', 'upcall',
30 'vhost', 'rx', 'tx', 'vhostuser', 'openflow', 'qsort',
31 'rxq', 'txq', 'perf', 'stats', 'struct', 'int',
32 'char', 'bool', 'upcalls', 'nicira', 'bitmask', 'ipv4',
33 'ipv6', 'tcp', 'tcp4', 'tcpv4', 'udp', 'udp4', 'udpv4',
34 'icmp', 'icmp4', 'icmpv6', 'vlan', 'vxlan', 'cksum',
35 'csum', 'checksum', 'ofproto', 'numa', 'mempool',
36 'mempools', 'mbuf', 'mbufs', 'hmap', 'cmap', 'smap',
37 'dhcpv4', 'dhcp', 'dhcpv6', 'opts', 'metadata',
38 'geneve', 'mutex', 'netdev', 'netdevs', 'subtable',
39 'virtio', 'qos', 'policer', 'datapath', 'tunctl',
40 'attr', 'ethernet', 'ether', 'defrag', 'defragment',
41 'loopback', 'sflow', 'acl', 'initializer', 'recirc',
42 'xlated', 'unclosed', 'netlink', 'msec', 'usec',
43 'nsec', 'ms', 'us', 'ns', 'kilobits', 'kbps',
44 'kilobytes', 'megabytes', 'mbps', 'gigabytes', 'gbps',
45 'megabits', 'gigabits', 'pkts', 'tuple', 'miniflow',
46 'megaflow', 'conntrack', 'vlans', 'vxlans', 'arg',
47 'tpid', 'xbundle', 'xbundles', 'mbundle', 'mbundles',
48 'netflow', 'localnet', 'odp', 'pre', 'dst', 'dest',
49 'src', 'ethertype', 'cvlan', 'ips', 'msg', 'msgs',
50 'liveness', 'userspace', 'eventmask', 'datapaths',
51 'slowpath', 'fastpath', 'multicast', 'unicast',
52 'revalidation', 'namespace', 'qdisc', 'uuid', 'ofport',
53 'subnet', 'revalidation', 'revalidator', 'revalidate',
54 'l2', 'l3', 'l4', 'openssl', 'mtu', 'ifindex', 'enum',
55 'enums', 'http', 'https', 'num', 'vconn', 'vconns',
56 'conn', 'nat', 'memset', 'memcmp', 'strcmp',
57 'strcasecmp', 'tc', 'ufid', 'api', 'ofpbuf', 'ofpbufs',
58 'hashmaps', 'hashmap', 'deref', 'dereference', 'hw',
59 'prio', 'sendmmsg', 'sendmsg', 'malloc', 'free', 'alloc',
60 'pid', 'ppid', 'pgid', 'uid', 'gid', 'sid', 'utime',
61 'stime', 'cutime', 'cstime', 'vsize', 'rss', 'rsslim',
62 'whcan', 'gtime', 'eip', 'rip', 'cgtime', 'dbg', 'gw',
63 'sbrec', 'bfd', 'sizeof', 'pmds', 'nic', 'nics', 'hwol',
64 'encap', 'decap', 'tlv', 'tlvs', 'decapsulation', 'fd',
65 'cacheline', 'xlate', 'skiplist', 'idl', 'comparator',
66 'natting', 'alg', 'pasv', 'epasv', 'wildcard', 'nated',
67 'amd64', 'x86_64', 'recirculation']
69 spell_check_dict
= enchant
.Dict("en_US")
70 for kw
in extra_keywords
:
71 spell_check_dict
.add(kw
)
79 print_file_name
= None
83 spellcheck_comments
= False
100 def get_yellow_begin():
107 def print_error(message
):
109 print("%sERROR%s: %s" % (get_red_begin(), get_color_end(), message
))
111 __errors
= __errors
+ 1
114 def print_warning(message
):
116 print("%sWARNING%s: %s" % (get_yellow_begin(), get_color_end(), message
))
118 __warnings
= __warnings
+ 1
121 def reset_counters():
122 global __errors
, __warnings
, total_line
129 # These are keywords whose names are normally followed by a space and
130 # something in parentheses (usually an expression) then a left curly brace.
132 # 'do' almost qualifies but it's also used as "do { ... } while (...);".
133 __parenthesized_constructs
= 'if|for|while|switch|[_A-Z]+FOR_EACH[_A-Z]*'
135 __regex_added_line
= re
.compile(r
'^\+{1,2}[^\+][\w\W]*')
136 __regex_subtracted_line
= re
.compile(r
'^\-{1,2}[^\-][\w\W]*')
137 __regex_leading_with_whitespace_at_all
= re
.compile(r
'^\s+')
138 __regex_leading_with_spaces
= re
.compile(r
'^ +[\S]+')
139 __regex_trailing_whitespace
= re
.compile(r
'[^\S]+$')
140 __regex_single_line_feed
= re
.compile(r
'^\f$')
141 __regex_for_if_missing_whitespace
= re
.compile(r
' +(%s)[\(]'
142 % __parenthesized_constructs
)
143 __regex_for_if_too_much_whitespace
= re
.compile(r
' +(%s) +[\(]'
144 % __parenthesized_constructs
)
145 __regex_for_if_parens_whitespace
= \
146 re
.compile(r
' +(%s) \( +[\s\S]+\)' % __parenthesized_constructs
)
147 __regex_is_for_if_single_line_bracket
= \
148 re
.compile(r
'^ +(%s) \(.*\)' % __parenthesized_constructs
)
149 __regex_ends_with_bracket
= \
150 re
.compile(r
'[^\s]\) {(\s+/\*[\s\Sa-zA-Z0-9\.,\?\*/+-]*)?$')
151 __regex_ptr_declaration_missing_whitespace
= re
.compile(r
'[a-zA-Z0-9]\*[^*]')
152 __regex_is_comment_line
= re
.compile(r
'^\s*(/\*|\*\s)')
153 __regex_has_comment
= re
.compile(r
'.*(/\*|\*\s)')
154 __regex_trailing_operator
= re
.compile(r
'^[^ ]* [^ ]*[?:]$')
155 __regex_conditional_else_bracing
= re
.compile(r
'^\s*else\s*{?$')
156 __regex_conditional_else_bracing2
= re
.compile(r
'^\s*}\selse\s*$')
157 __regex_has_xxx_mark
= re
.compile(r
'.*xxx.*', re
.IGNORECASE
)
158 __regex_added_doc_rst
= re
.compile(
159 r
'\ndiff .*Documentation/.*rst\nnew file mode')
161 skip_leading_whitespace_check
= False
162 skip_trailing_whitespace_check
= False
163 skip_block_whitespace_check
= False
164 skip_signoff_check
= False
166 # Don't enforce character limit on files that include these characters in their
167 # name, as they may have legitimate reasons to have longer lines.
169 # Python isn't checked as flake8 performs these checks during build.
170 line_length_blacklist
= re
.compile(
171 r
'\.(am|at|etc|in|m4|mk|patch|py)$|debian/rules')
173 # Don't enforce a requirement that leading whitespace be all spaces on
174 # files that include these characters in their name, since these kinds
175 # of files need lines with leading tabs.
176 leading_whitespace_blacklist
= re
.compile(r
'\.(mk|am|at)$|debian/rules')
179 def is_subtracted_line(line
):
180 """Returns TRUE if the line in question has been removed."""
181 return __regex_subtracted_line
.search(line
) is not None
184 def is_added_line(line
):
185 """Returns TRUE if the line in question is an added line.
188 return __regex_added_line
.search(line
) is not None or checking_file
191 def added_line(line
):
192 """Returns the line formatted properly by removing diff syntax"""
194 if not checking_file
:
199 def leading_whitespace_is_spaces(line
):
200 """Returns TRUE if the leading whitespace in added lines is spaces
202 if skip_leading_whitespace_check
:
204 if (__regex_leading_with_whitespace_at_all
.search(line
) is not None and
205 __regex_single_line_feed
.search(line
) is None):
206 return __regex_leading_with_spaces
.search(line
) is not None
211 def trailing_whitespace_or_crlf(line
):
212 """Returns TRUE if the trailing characters is whitespace
214 if skip_trailing_whitespace_check
:
216 return (__regex_trailing_whitespace
.search(line
) is not None and
217 __regex_single_line_feed
.search(line
) is None)
220 def if_and_for_whitespace_checks(line
):
221 """Return TRUE if there is appropriate whitespace after if, for, while
223 if skip_block_whitespace_check
:
225 if (__regex_for_if_missing_whitespace
.search(line
) is not None or
226 __regex_for_if_too_much_whitespace
.search(line
) is not None or
227 __regex_for_if_parens_whitespace
.search(line
)):
232 def if_and_for_end_with_bracket_check(line
):
233 """Return TRUE if there is not a bracket at the end of an if, for, while
234 block which fits on a single line ie: 'if (foo)'"""
236 def balanced_parens(line
):
237 """This is a rather naive counter - it won't deal with quotes"""
246 if __regex_is_for_if_single_line_bracket
.search(line
) is not None:
247 if not balanced_parens(line
):
249 if __regex_ends_with_bracket
.search(line
) is None:
251 if __regex_conditional_else_bracing
.match(line
) is not None:
253 if __regex_conditional_else_bracing2
.match(line
) is not None:
258 def pointer_whitespace_check(line
):
259 """Return TRUE if there is no space between a pointer name and the
260 asterisk that denotes this is a apionter type, ie: 'struct foo*'"""
261 return __regex_ptr_declaration_missing_whitespace
.search(line
) is not None
264 def line_length_check(line
):
265 """Return TRUE if the line length is too long"""
267 print_warning("Line is %d characters long (recommended limit is 79)"
273 def is_comment_line(line
):
274 """Returns TRUE if the current line is part of a block comment."""
275 return __regex_is_comment_line
.match(line
) is not None
278 def has_comment(line
):
279 """Returns TRUE if the current line contains a comment or is part of
281 return __regex_has_comment
.match(line
) is not None
284 def trailing_operator(line
):
285 """Returns TRUE if the current line ends with an operatorsuch as ? or :"""
286 return __regex_trailing_operator
.match(line
) is not None
289 def has_xxx_mark(line
):
290 """Returns TRUE if the current line contains 'xxx'."""
291 return __regex_has_xxx_mark
.match(line
) is not None
294 def filter_comments(current_line
, keep
=False):
295 """remove all of the c-style comments in a line"""
297 STATE_COMMENT_SLASH
= 1
298 STATE_COMMENT_CONTENTS
= 3
299 STATE_COMMENT_END_SLASH
= 4
303 check_state
= STATE_NORMAL
304 only_whitespace
= True
307 check_state
= STATE_COMMENT_CONTENTS
309 for c
in current_line
:
311 if state
== STATE_NORMAL
:
312 state
= STATE_COMMENT_SLASH
313 elif state
== STATE_COMMENT_SLASH
:
314 # This is for c++ style comments. We will warn later
315 return sanitized_line
[:1]
316 elif state
== STATE_COMMENT_END_SLASH
:
321 # just assume this is a continuation from the previous line
323 state
= STATE_COMMENT_END_SLASH
324 elif state
== STATE_COMMENT_SLASH
:
325 state
= STATE_COMMENT_CONTENTS
326 sanitized_line
= sanitized_line
[:-1]
327 elif state
== STATE_COMMENT_CONTENTS
:
328 state
= STATE_COMMENT_END_SLASH
329 elif state
== STATE_COMMENT_END_SLASH
:
330 # Need to re-introduce the star from the previous state, since
331 # it may have been clipped by the state check below.
333 state
= STATE_COMMENT_CONTENTS
334 elif state
== STATE_COMMENT_SLASH
:
335 # Need to re-introduce the slash from the previous state, since
336 # it may have been clipped by the state check below.
340 if state
!= check_state
:
344 only_whitespace
= False
348 return sanitized_line
351 def check_comment_spelling(line
):
352 if no_spellcheck
or not spellcheck_comments
:
355 comment_words
= filter_comments(line
, True).replace(':', ' ').split(' ')
356 for word
in comment_words
:
358 strword
= re
.subn(r
'\W+', '', word
)[0].replace(',', '')
359 if len(strword
) and not spell_check_dict
.check(strword
.lower()):
360 if any([check_char
in word
361 for check_char
in ['=', '(', '-', '_', '/', '\'']]):
364 # special case the '.'
365 if '.' in word
and not word
.endswith('.'):
368 # skip proper nouns and references to macros
369 if strword
.isupper() or (strword
[0].isupper() and
370 strword
[1:].islower()):
373 # skip words that start with numbers
374 if strword
.startswith(tuple('0123456789')):
378 print_warning("Check for spelling mistakes (e.g. \"%s\")"
385 def __check_doc_is_listed(text
, doctype
, docdir
, docfile
):
387 beginre
= re
.compile(r
'\+\+\+.*{}/index.rst'.format(docdir
))
388 docre
= re
.compile(r
'\n\+.*{}'.format(docfile
.replace('.rst', '')))
389 elif doctype
== 'automake':
390 beginre
= re
.compile(r
'\+\+\+.*Documentation/automake.mk')
391 docre
= re
.compile(r
'\n\+\t{}/{}'.format(docdir
, docfile
))
393 raise NotImplementedError("Invalid doctype: {}".format(doctype
))
395 res
= beginre
.search(text
)
399 hunkstart
= res
.span()[1]
400 hunkre
= re
.compile(r
'\n(---|\+\+\+) (\S+)')
401 res
= hunkre
.search(text
[hunkstart
:])
405 hunkend
= hunkstart
+ res
.span()[0]
407 hunk
= text
[hunkstart
:hunkend
]
408 # find if the file is being added.
409 if docre
.search(hunk
) is not None:
415 def __check_new_docs(text
, doctype
):
416 """Check if the documentation is listed properly. If doctype is 'rst' then
417 the index.rst is checked. If the doctype is 'automake' then automake.mk
418 is checked. Returns TRUE if the new file is not listed."""
420 new_docs
= __regex_added_doc_rst
.findall(text
)
422 docpathname
= doc
.split(' ')[2]
423 gitdocdir
, docfile
= os
.path
.split(docpathname
.rstrip('\n'))
424 if docfile
== "index.rst":
427 if gitdocdir
.startswith('a/'):
428 docdir
= gitdocdir
.replace('a/', '', 1)
432 if __check_doc_is_listed(text
, doctype
, docdir
, docfile
):
434 print_warning("New doc {} not listed in {}/index.rst".format(
436 elif doctype
== 'automake':
437 print_warning("New doc {} not listed in "
438 "Documentation/automake.mk".format(docfile
))
440 raise NotImplementedError("Invalid doctype: {}".format(
448 def check_doc_docs_automake(text
):
449 return __check_new_docs(text
, 'automake')
452 def check_new_docs_index(text
):
453 return __check_new_docs(text
, 'rst')
457 {'regex': __regex_added_doc_rst
,
458 'check': check_new_docs_index
},
459 {'regex': __regex_added_doc_rst
,
460 'check': check_doc_docs_automake
}
465 'match_name': lambda x
: not line_length_blacklist
.search(x
),
466 'check': lambda x
: line_length_check(x
)},
469 'match_name': lambda x
: not leading_whitespace_blacklist
.search(x
),
470 'check': lambda x
: not leading_whitespace_is_spaces(x
),
471 'print': lambda: print_warning("Line has non-spaces leading whitespace")},
473 {'regex': None, 'match_name': None,
474 'check': lambda x
: trailing_whitespace_or_crlf(x
),
475 'print': lambda: print_warning("Line has trailing whitespace")},
477 {'regex': '(\.c|\.h)(\.in)?$', 'match_name': None,
478 'prereq': lambda x
: not is_comment_line(x
),
479 'check': lambda x
: not if_and_for_whitespace_checks(x
),
480 'print': lambda: print_error("Improper whitespace around control block")},
482 {'regex': '(\.c|\.h)(\.in)?$', 'match_name': None,
483 'prereq': lambda x
: not is_comment_line(x
),
484 'check': lambda x
: not if_and_for_end_with_bracket_check(x
),
485 'print': lambda: print_error("Inappropriate bracing around statement")},
487 {'regex': '(\.c|\.h)(\.in)?$', 'match_name': None,
488 'prereq': lambda x
: not is_comment_line(x
),
489 'check': lambda x
: pointer_whitespace_check(x
),
491 lambda: print_error("Inappropriate spacing in pointer declaration")},
493 {'regex': '(\.c|\.h)(\.in)?$', 'match_name': None,
494 'prereq': lambda x
: not is_comment_line(x
),
495 'check': lambda x
: trailing_operator(x
),
497 lambda: print_error("Line has '?' or ':' operator at end of line")},
499 {'regex': '(\.c|\.h)(\.in)?$', 'match_name': None,
500 'prereq': lambda x
: has_comment(x
),
501 'check': lambda x
: has_xxx_mark(x
),
502 'print': lambda: print_warning("Comment with 'xxx' marker")},
504 {'regex': '(\.c|\.h)(\.in)?$', 'match_name': None,
505 'prereq': lambda x
: has_comment(x
),
506 'check': lambda x
: check_comment_spelling(x
)},
510 def regex_function_factory(func_name
):
511 regex
= re
.compile(r
'\b%s\([^)]*\)' % func_name
)
512 return lambda x
: regex
.search(x
) is not None
515 def regex_error_factory(description
):
516 return lambda: print_error(description
)
520 ('malloc', 'Use xmalloc() in place of malloc()'),
521 ('calloc', 'Use xcalloc() in place of calloc()'),
522 ('realloc', 'Use xrealloc() in place of realloc()'),
523 ('strdup', 'Use xstrdup() in place of strdup()'),
524 ('asprintf', 'Use xasprintf() in place of asprintf()'),
525 ('vasprintf', 'Use xvasprintf() in place of vasprintf()'),
526 ('strcpy', 'Use ovs_strlcpy() in place of strcpy()'),
527 ('strlcpy', 'Use ovs_strlcpy() in place of strlcpy()'),
528 ('strncpy', 'Use ovs_strzcpy() in place of strncpy()'),
529 ('strerror', 'Use ovs_strerror() in place of strerror()'),
530 ('sleep', 'Use xsleep() in place of sleep()'),
531 ('abort', 'Use ovs_abort() in place of abort()'),
532 ('assert', 'Use ovs_assert() in place of assert()'),
533 ('error', 'Use ovs_error() in place of error()'),
536 {'regex': '(\.c|\.h)(\.in)?$',
538 'prereq': lambda x
: not is_comment_line(x
),
539 'check': regex_function_factory(function_name
),
540 'print': regex_error_factory(description
)}
541 for (function_name
, description
) in std_functions
]
544 def regex_operator_factory(operator
):
545 regex
= re
.compile(r
'^[^#][^"\']*[^
"]%s[^ "\'][^
"]*' % operator)
546 return lambda x: regex.search(filter_comments(x)) is not None
550 [re.escape(op) for op in ['%', '<<', '>>', '<=', '>=', '==', '!=',
551 '^', '|', '&&', '||', '?:', '=', '+=', '-=', '*=', '/=', '%=',
552 '&=', '^=', '|=', '<<=', '>>=']] \
553 + ['[^<" ]<[^
=" ]', '[^->" ]>[^
=" ]', '[^ !()/"]\
*[^
/]', '[^
!&()"]&',
554 '[^" +(]\
+[^
"+;]', '[^" -(]-[^
"->;]', '[^" <>=!^|
+\
-*/%&]=[^
"=]',
557 {'regex': '(\.c|\.h)(\.in)?$', 'match_name': None,
558 'prereq': lambda x: not is_comment_line(x),
559 'check': regex_operator_factory(operator),
560 'print': lambda: print_warning("Line lacks whitespace around operator
")}
561 for operator in infix_operators]
564 def get_file_type_checks(filename):
565 """Returns the list of checks for a file based on matching the filename
570 if check['regex'] is None and check['match_name'] is None:
571 checkList.append(check)
572 if check['regex'] is not None and \
573 re.compile(check['regex']).search(filename) is not None:
574 checkList.append(check)
575 elif check['match_name'] is not None and check['match_name'](filename):
576 checkList.append(check)
580 def run_checks(current_file, line, lineno):
581 """Runs the various checks for the particular line. This will take
582 filename into account."""
583 global checking_file, total_line
585 for check in get_file_type_checks(current_file):
586 if 'prereq' in check and not check['prereq'](line):
588 if check['check'](line):
595 print("%s:%d:" % (current_file, lineno))
597 print("#%d FILE: %s:%d:" % (total_line, current_file, lineno))
601 def run_file_checks(text
):
602 """Runs the various checks for the text."""
603 for check
in file_checks
:
604 if check
['regex'].search(text
) is not None:
608 def ovs_checkpatch_parse(text
, filename
):
609 global print_file_name
, total_line
, checking_file
611 PARSE_STATE_HEADING
= 0
612 PARSE_STATE_DIFF_HEADER
= 1
613 PARSE_STATE_CHANGE_BODY
= 2
619 current_file
= filename
if checking_file
else ''
621 scissors
= re
.compile(r
'^[\w]*---[\w]*')
622 hunks
= re
.compile('^(---|\+\+\+) (\S+)')
623 hunk_differences
= re
.compile(
624 r
'^@@ ([0-9-+]+),([0-9-+]+) ([0-9-+]+),([0-9-+]+) @@')
625 is_signature
= re
.compile(r
'((\s*Signed-off-by: )(.*))$',
627 is_co_author
= re
.compile(r
'(\s*(Co-authored-by: )(.*))$',
629 is_gerrit_change_id
= re
.compile(r
'(\s*(change-id: )(.*))$',
634 for line
in text
.split('\n'):
635 if current_file
!= previous_file
:
636 previous_file
= current_file
639 total_line
= total_line
+ 1
644 parse
= PARSE_STATE_CHANGE_BODY
646 if parse
== PARSE_STATE_DIFF_HEADER
:
647 match
= hunks
.match(line
)
649 parse
= PARSE_STATE_CHANGE_BODY
650 current_file
= match
.group(2)[2:]
651 print_file_name
= current_file
653 elif parse
== PARSE_STATE_HEADING
:
654 if scissors
.match(line
):
655 parse
= PARSE_STATE_DIFF_HEADER
656 if not skip_signoff_check
:
657 if len(signatures
) == 0:
658 print_error("No signatures found.")
659 elif len(signatures
) != 1 + len(co_authors
):
660 print_error("Too many signoffs; "
661 "are you missing Co-authored-by lines?")
662 if not set(co_authors
) <= set(signatures
):
663 print_error("Co-authored-by/Signed-off-by corruption")
664 elif is_signature
.match(line
):
665 m
= is_signature
.match(line
)
666 signatures
.append(m
.group(3))
667 elif is_co_author
.match(line
):
668 m
= is_co_author
.match(line
)
669 co_authors
.append(m
.group(3))
670 elif is_gerrit_change_id
.match(line
):
672 "Remove Gerrit Change-Id's before submitting upstream.")
673 print("%d: %s\n" % (lineno
, line
))
674 elif parse
== PARSE_STATE_CHANGE_BODY
:
675 newfile
= hunks
.match(line
)
677 current_file
= newfile
.group(2)[2:]
678 print_file_name
= current_file
680 reset_line_number
= hunk_differences
.match(line
)
681 if reset_line_number
:
682 lineno
= int(reset_line_number
.group(3))
686 if is_subtracted_line(line
):
688 if not is_added_line(line
):
691 cmp_line
= added_line(line
)
693 # Skip files which have /datapath in them, since they are
694 # linux or windows coding standards
695 if current_file
.startswith('datapath'):
697 if current_file
.startswith('include/linux'):
699 run_checks(current_file
, cmp_line
, lineno
)
701 run_file_checks(text
)
702 if __errors
or __warnings
:
709 Open vSwitch checkpatch.py
710 Checks a patch for trivial mistakes.
712 %s [options] [PATCH1 [PATCH2 ...] | -f SOURCE1 [SOURCE2 ...] | -1 | -2 | ...]
715 -f|--check-file Arguments are source files, not patches.
716 -1, -2, ... Check recent commits in this repo.
719 -h|--help This help message
720 -b|--skip-block-whitespace Skips the if/while/for whitespace tests
721 -l|--skip-leading-whitespace Skips the leading whitespace test
722 -s|--skip-signoff-lines Tolerate missing Signed-off-by line
723 -S|--spellcheck-comments Check C comments for possible spelling mistakes
724 -t|--skip-trailing-whitespace Skips the trailing whitespace test"""
728 def ovs_checkpatch_print_result(result
):
729 global __warnings
, __errors
, total_line
731 print("Lines checked: %d, Warnings: %d, Errors: %d\n" %
732 (total_line
, __warnings
, __errors
))
734 print("Lines checked: %d, no obvious problems found\n" % (total_line
))
737 def ovs_checkpatch_file(filename
):
739 mail
= email
.message_from_file(open(filename
, 'r'))
741 print_error("Unable to parse file '%s'. Is it a patch?" % filename
)
744 for part
in mail
.walk():
745 if part
.get_content_maintype() == 'multipart':
747 result
= ovs_checkpatch_parse(part
.get_payload(decode
=False), filename
)
748 ovs_checkpatch_print_result(result
)
752 def partition(pred
, iterable
):
753 """Returns [[trues], [falses]], where [trues] is the items in
754 'iterable' that satisfy 'pred' and [falses] is all the rest."""
757 for item
in iterable
:
765 if __name__
== '__main__':
767 numeric_options
, args
= partition(lambda s
: re
.match('-[0-9]+$', s
),
769 n_patches
= int(numeric_options
[-1][1:]) if numeric_options
else 0
771 optlist
, args
= getopt
.getopt(args
, 'bhlstfS',
774 "skip-block-whitespace",
775 "skip-leading-whitespace",
776 "skip-signoff-lines",
777 "skip-trailing-whitespace",
778 "spellcheck-comments"])
780 print("Unknown option encountered. Please rerun with -h for help.")
784 if o
in ("-h", "--help"):
787 elif o
in ("-b", "--skip-block-whitespace"):
788 skip_block_whitespace_check
= True
789 elif o
in ("-l", "--skip-leading-whitespace"):
790 skip_leading_whitespace_check
= True
791 elif o
in ("-s", "--skip-signoff-lines"):
792 skip_signoff_check
= True
793 elif o
in ("-t", "--skip-trailing-whitespace"):
794 skip_trailing_whitespace_check
= True
795 elif o
in ("-f", "--check-file"):
797 elif o
in ("-S", "--spellcheck-comments"):
799 print("WARNING: The enchant library isn't availble.")
800 print(" Please install python enchant.")
802 spellcheck_comments
= True
804 print("Unknown option '%s'" % o
)
807 if sys
.stdout
.isatty():
813 git_log
= 'git log --no-color --no-merges --pretty=format:"%H %s" '
814 with os
.popen(git_log
+ '-%d' % n_patches
, 'r') as f
:
815 commits
= f
.read().split("\n")
817 for i
in reversed(range(0, n_patches
)):
818 revision
, name
= commits
[i
].split(" ", 1)
819 f
= os
.popen('git format-patch -1 --stdout %s' % revision
, 'r')
823 print('== Checking %s ("%s") ==' % (revision
[0:12], name
))
824 result
= ovs_checkpatch_parse(patch
, revision
)
825 ovs_checkpatch_print_result(result
)
831 if sys
.stdin
.isatty():
834 result
= ovs_checkpatch_parse(sys
.stdin
.read(), '-')
835 ovs_checkpatch_print_result(result
)
839 for filename
in args
:
840 print('== Checking "%s" ==' % filename
)
841 result
= ovs_checkpatch_file(filename
)