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
24 RETURN_CHECK_INITIAL_STATE
= 0
25 RETURN_CHECK_STATE_WITH_RETURN
= 1
26 RETURN_CHECK_AWAITING_BRACE
= 2
29 empty_return_check_state
= 0
30 print_file_name
= None
34 spellcheck_comments
= False
36 spell_check_dict
= None
39 def open_spell_check_dict():
43 extra_keywords
= ['ovs', 'vswitch', 'vswitchd', 'ovs-vswitchd',
44 'netdev', 'selinux', 'ovs-ctl', 'dpctl', 'ofctl',
45 'openvswitch', 'dpdk', 'hugepage', 'hugepages',
46 'pmd', 'upcall', 'vhost', 'rx', 'tx', 'vhostuser',
47 'openflow', 'qsort', 'rxq', 'txq', 'perf', 'stats',
48 'struct', 'int', 'char', 'bool', 'upcalls', 'nicira',
49 'bitmask', 'ipv4', 'ipv6', 'tcp', 'tcp4', 'tcpv4',
50 'udp', 'udp4', 'udpv4', 'icmp', 'icmp4', 'icmpv6',
51 'vlan', 'vxlan', 'cksum', 'csum', 'checksum',
52 'ofproto', 'numa', 'mempool', 'mempools', 'mbuf',
53 'mbufs', 'hmap', 'cmap', 'smap', 'dhcpv4', 'dhcp',
54 'dhcpv6', 'opts', 'metadata', 'geneve', 'mutex',
55 'netdev', 'netdevs', 'subtable', 'virtio', 'qos',
56 'policer', 'datapath', 'tunctl', 'attr', 'ethernet',
57 'ether', 'defrag', 'defragment', 'loopback', 'sflow',
58 'acl', 'initializer', 'recirc', 'xlated', 'unclosed',
59 'netlink', 'msec', 'usec', 'nsec', 'ms', 'us', 'ns',
60 'kilobits', 'kbps', 'kilobytes', 'megabytes', 'mbps',
61 'gigabytes', 'gbps', 'megabits', 'gigabits', 'pkts',
62 'tuple', 'miniflow', 'megaflow', 'conntrack',
63 'vlans', 'vxlans', 'arg', 'tpid', 'xbundle',
64 'xbundles', 'mbundle', 'mbundles', 'netflow',
65 'localnet', 'odp', 'pre', 'dst', 'dest', 'src',
66 'ethertype', 'cvlan', 'ips', 'msg', 'msgs',
67 'liveness', 'userspace', 'eventmask', 'datapaths',
68 'slowpath', 'fastpath', 'multicast', 'unicast',
69 'revalidation', 'namespace', 'qdisc', 'uuid',
70 'ofport', 'subnet', 'revalidation', 'revalidator',
71 'revalidate', 'l2', 'l3', 'l4', 'openssl', 'mtu',
72 'ifindex', 'enum', 'enums', 'http', 'https', 'num',
73 'vconn', 'vconns', 'conn', 'nat', 'memset', 'memcmp',
74 'strcmp', 'strcasecmp', 'tc', 'ufid', 'api',
75 'ofpbuf', 'ofpbufs', 'hashmaps', 'hashmap', 'deref',
76 'dereference', 'hw', 'prio', 'sendmmsg', 'sendmsg',
77 'malloc', 'free', 'alloc', 'pid', 'ppid', 'pgid',
78 'uid', 'gid', 'sid', 'utime', 'stime', 'cutime',
79 'cstime', 'vsize', 'rss', 'rsslim', 'whcan', 'gtime',
80 'eip', 'rip', 'cgtime', 'dbg', 'gw', 'sbrec', 'bfd',
81 'sizeof', 'pmds', 'nic', 'nics', 'hwol', 'encap',
82 'decap', 'tlv', 'tlvs', 'decapsulation', 'fd',
83 'cacheline', 'xlate', 'skiplist', 'idl',
84 'comparator', 'natting', 'alg', 'pasv', 'epasv',
85 'wildcard', 'nated', 'amd64', 'x86_64',
88 global spell_check_dict
89 spell_check_dict
= enchant
.Dict("en_US")
90 for kw
in extra_keywords
:
91 spell_check_dict
.add(kw
)
112 def get_yellow_begin():
119 def print_error(message
):
121 print("%sERROR%s: %s" % (get_red_begin(), get_color_end(), message
))
123 __errors
= __errors
+ 1
126 def print_warning(message
):
128 print("%sWARNING%s: %s" % (get_yellow_begin(), get_color_end(), message
))
130 __warnings
= __warnings
+ 1
133 def reset_counters():
134 global __errors
, __warnings
, total_line
141 # These are keywords whose names are normally followed by a space and
142 # something in parentheses (usually an expression) then a left curly brace.
144 # 'do' almost qualifies but it's also used as "do { ... } while (...);".
145 __parenthesized_constructs
= 'if|for|while|switch|[_A-Z]+FOR_*EACH[_A-Z]*'
147 __regex_added_line
= re
.compile(r
'^\+{1,2}[^\+][\w\W]*')
148 __regex_subtracted_line
= re
.compile(r
'^\-{1,2}[^\-][\w\W]*')
149 __regex_leading_with_whitespace_at_all
= re
.compile(r
'^\s+')
150 __regex_leading_with_spaces
= re
.compile(r
'^ +[\S]+')
151 __regex_trailing_whitespace
= re
.compile(r
'[^\S]+$')
152 __regex_single_line_feed
= re
.compile(r
'^\f$')
153 __regex_for_if_missing_whitespace
= re
.compile(r
' +(%s)[\(]'
154 % __parenthesized_constructs
)
155 __regex_for_if_too_much_whitespace
= re
.compile(r
' +(%s) +[\(]'
156 % __parenthesized_constructs
)
157 __regex_for_if_parens_whitespace
= \
158 re
.compile(r
' +(%s) \( +[\s\S]+\)' % __parenthesized_constructs
)
159 __regex_is_for_if_single_line_bracket
= \
160 re
.compile(r
'^ +(%s) \(.*\)' % __parenthesized_constructs
)
161 __regex_ends_with_bracket
= \
162 re
.compile(r
'[^\s]\) {(\s+/\*[\s\Sa-zA-Z0-9\.,\?\*/+-]*)?$')
163 __regex_ptr_declaration_missing_whitespace
= re
.compile(r
'[a-zA-Z0-9]\*[^*]')
164 __regex_is_comment_line
= re
.compile(r
'^\s*(/\*|\*\s)')
165 __regex_has_comment
= re
.compile(r
'.*(/\*|\*\s)')
166 __regex_has_c99_comment
= re
.compile(r
'.*//.*$')
167 __regex_trailing_operator
= re
.compile(r
'^[^ ]* [^ ]*[?:]$')
168 __regex_conditional_else_bracing
= re
.compile(r
'^\s*else\s*{?$')
169 __regex_conditional_else_bracing2
= re
.compile(r
'^\s*}\selse\s*$')
170 __regex_has_xxx_mark
= re
.compile(r
'.*xxx.*', re
.IGNORECASE
)
171 __regex_added_doc_rst
= re
.compile(
172 r
'\ndiff .*Documentation/.*rst\nnew file mode')
173 __regex_empty_return
= re
.compile(r
'\s*return;')
174 __regex_if_macros
= re
.compile(r
'^ +(%s) \([\S][\s\S]+[\S]\) { \\' %
175 __parenthesized_constructs
)
177 skip_leading_whitespace_check
= False
178 skip_trailing_whitespace_check
= False
179 skip_block_whitespace_check
= False
180 skip_signoff_check
= False
182 # Don't enforce character limit on files that include these characters in their
183 # name, as they may have legitimate reasons to have longer lines.
185 # Python isn't checked as flake8 performs these checks during build.
186 line_length_blacklist
= re
.compile(
187 r
'\.(am|at|etc|in|m4|mk|patch|py)$|debian/rules')
189 # Don't enforce a requirement that leading whitespace be all spaces on
190 # files that include these characters in their name, since these kinds
191 # of files need lines with leading tabs.
192 leading_whitespace_blacklist
= re
.compile(r
'\.(mk|am|at)$|debian/rules')
195 def is_subtracted_line(line
):
196 """Returns TRUE if the line in question has been removed."""
197 return __regex_subtracted_line
.search(line
) is not None
200 def is_added_line(line
):
201 """Returns TRUE if the line in question is an added line.
204 return __regex_added_line
.search(line
) is not None or checking_file
207 def added_line(line
):
208 """Returns the line formatted properly by removing diff syntax"""
210 if not checking_file
:
215 def leading_whitespace_is_spaces(line
):
216 """Returns TRUE if the leading whitespace in added lines is spaces
218 if skip_leading_whitespace_check
:
220 if (__regex_leading_with_whitespace_at_all
.search(line
) is not None and
221 __regex_single_line_feed
.search(line
) is None):
222 return __regex_leading_with_spaces
.search(line
) is not None
227 def trailing_whitespace_or_crlf(line
):
228 """Returns TRUE if the trailing characters is whitespace
230 if skip_trailing_whitespace_check
:
232 return (__regex_trailing_whitespace
.search(line
) is not None and
233 __regex_single_line_feed
.search(line
) is None)
236 def if_and_for_whitespace_checks(line
):
237 """Return TRUE if there is appropriate whitespace after if, for, while
239 if skip_block_whitespace_check
:
241 if (__regex_for_if_missing_whitespace
.search(line
) is not None or
242 __regex_for_if_too_much_whitespace
.search(line
) is not None or
243 __regex_for_if_parens_whitespace
.search(line
)):
248 def if_and_for_end_with_bracket_check(line
):
249 """Return TRUE if there is not a bracket at the end of an if, for, while
250 block which fits on a single line ie: 'if (foo)'"""
252 def balanced_parens(line
):
253 """This is a rather naive counter - it won't deal with quotes"""
262 if __regex_is_for_if_single_line_bracket
.search(line
) is not None:
263 if not balanced_parens(line
):
266 if __regex_ends_with_bracket
.search(line
) is None and \
267 __regex_if_macros
.match(line
) is None:
269 if __regex_conditional_else_bracing
.match(line
) is not None:
271 if __regex_conditional_else_bracing2
.match(line
) is not None:
276 def pointer_whitespace_check(line
):
277 """Return TRUE if there is no space between a pointer name and the
278 asterisk that denotes this is a apionter type, ie: 'struct foo*'"""
279 return __regex_ptr_declaration_missing_whitespace
.search(line
) is not None
282 def line_length_check(line
):
283 """Return TRUE if the line length is too long"""
285 print_warning("Line is %d characters long (recommended limit is 79)"
291 def is_comment_line(line
):
292 """Returns TRUE if the current line is part of a block comment."""
293 return __regex_is_comment_line
.match(line
) is not None
296 def has_comment(line
):
297 """Returns TRUE if the current line contains a comment or is part of
299 return __regex_has_comment
.match(line
) is not None
302 def has_c99_comment(line
):
303 """Returns TRUE if the current line contains C99 style comment (//)."""
304 return __regex_has_c99_comment
.match(line
) is not None
307 def trailing_operator(line
):
308 """Returns TRUE if the current line ends with an operatorsuch as ? or :"""
309 return __regex_trailing_operator
.match(line
) is not None
312 def has_xxx_mark(line
):
313 """Returns TRUE if the current line contains 'xxx'."""
314 return __regex_has_xxx_mark
.match(line
) is not None
317 def filter_comments(current_line
, keep
=False):
318 """remove all of the c-style comments in a line"""
320 STATE_COMMENT_SLASH
= 1
321 STATE_COMMENT_CONTENTS
= 3
322 STATE_COMMENT_END_SLASH
= 4
326 check_state
= STATE_NORMAL
327 only_whitespace
= True
330 check_state
= STATE_COMMENT_CONTENTS
332 for c
in current_line
:
334 if state
== STATE_NORMAL
:
335 state
= STATE_COMMENT_SLASH
336 elif state
== STATE_COMMENT_SLASH
:
337 # This is for c++ style comments. We will warn later
338 return sanitized_line
[:1]
339 elif state
== STATE_COMMENT_END_SLASH
:
344 # just assume this is a continuation from the previous line
346 state
= STATE_COMMENT_END_SLASH
347 elif state
== STATE_COMMENT_SLASH
:
348 state
= STATE_COMMENT_CONTENTS
349 sanitized_line
= sanitized_line
[:-1]
350 elif state
== STATE_COMMENT_CONTENTS
:
351 state
= STATE_COMMENT_END_SLASH
352 elif state
== STATE_COMMENT_END_SLASH
:
353 # Need to re-introduce the star from the previous state, since
354 # it may have been clipped by the state check below.
356 state
= STATE_COMMENT_CONTENTS
357 elif state
== STATE_COMMENT_SLASH
:
358 # Need to re-introduce the slash from the previous state, since
359 # it may have been clipped by the state check below.
363 if state
!= check_state
:
367 only_whitespace
= False
371 return sanitized_line
374 def check_comment_spelling(line
):
375 if not spell_check_dict
or not spellcheck_comments
:
378 comment_words
= filter_comments(line
, True).replace(':', ' ').split(' ')
379 for word
in comment_words
:
381 strword
= re
.subn(r
'\W+', '', word
)[0].replace(',', '')
382 if len(strword
) and not spell_check_dict
.check(strword
.lower()):
383 if any([check_char
in word
384 for check_char
in ['=', '(', '-', '_', '/', '\'']]):
387 # special case the '.'
388 if '.' in word
and not word
.endswith('.'):
391 # skip proper nouns and references to macros
392 if strword
.isupper() or (strword
[0].isupper() and
393 strword
[1:].islower()):
396 # skip words that start with numbers
397 if strword
.startswith(tuple('0123456789')):
401 print_warning("Check for spelling mistakes (e.g. \"%s\")"
408 def __check_doc_is_listed(text
, doctype
, docdir
, docfile
):
410 beginre
= re
.compile(r
'\+\+\+.*{}/index.rst'.format(docdir
))
411 docre
= re
.compile(r
'\n\+.*{}'.format(docfile
.replace('.rst', '')))
412 elif doctype
== 'automake':
413 beginre
= re
.compile(r
'\+\+\+.*Documentation/automake.mk')
414 docre
= re
.compile(r
'\n\+\t{}/{}'.format(docdir
, docfile
))
416 raise NotImplementedError("Invalid doctype: {}".format(doctype
))
418 res
= beginre
.search(text
)
422 hunkstart
= res
.span()[1]
423 hunkre
= re
.compile(r
'\n(---|\+\+\+) (\S+)')
424 res
= hunkre
.search(text
[hunkstart
:])
428 hunkend
= hunkstart
+ res
.span()[0]
430 hunk
= text
[hunkstart
:hunkend
]
431 # find if the file is being added.
432 if docre
.search(hunk
) is not None:
438 def __check_new_docs(text
, doctype
):
439 """Check if the documentation is listed properly. If doctype is 'rst' then
440 the index.rst is checked. If the doctype is 'automake' then automake.mk
441 is checked. Returns TRUE if the new file is not listed."""
443 new_docs
= __regex_added_doc_rst
.findall(text
)
445 docpathname
= doc
.split(' ')[2]
446 gitdocdir
, docfile
= os
.path
.split(docpathname
.rstrip('\n'))
447 if docfile
== "index.rst":
450 if gitdocdir
.startswith('a/'):
451 docdir
= gitdocdir
.replace('a/', '', 1)
455 if __check_doc_is_listed(text
, doctype
, docdir
, docfile
):
457 print_warning("New doc {} not listed in {}/index.rst".format(
459 elif doctype
== 'automake':
460 print_warning("New doc {} not listed in "
461 "Documentation/automake.mk".format(docfile
))
463 raise NotImplementedError("Invalid doctype: {}".format(
471 def check_doc_docs_automake(text
):
472 return __check_new_docs(text
, 'automake')
475 def check_new_docs_index(text
):
476 return __check_new_docs(text
, 'rst')
479 def empty_return_with_brace(line
):
480 """Returns TRUE if a function contains a return; followed
481 by one or more line feeds and terminates with a '}'
484 def empty_return(line
):
485 """Returns TRUE if a function has a 'return;'"""
486 return __regex_empty_return
.match(line
) is not None
488 global empty_return_check_state
489 if empty_return_check_state
== RETURN_CHECK_INITIAL_STATE \
490 and empty_return(line
):
491 empty_return_check_state
= RETURN_CHECK_STATE_WITH_RETURN
492 elif empty_return_check_state
== RETURN_CHECK_STATE_WITH_RETURN \
493 and (re
.match(r
'^}$', line
) or len(line
) == 0):
494 if re
.match('^}$', line
):
495 empty_return_check_state
= RETURN_CHECK_AWAITING_BRACE
497 empty_return_check_state
= RETURN_CHECK_INITIAL_STATE
499 if empty_return_check_state
== RETURN_CHECK_AWAITING_BRACE
:
500 empty_return_check_state
= RETURN_CHECK_INITIAL_STATE
507 {'regex': __regex_added_doc_rst
,
508 'check': check_new_docs_index
},
509 {'regex': __regex_added_doc_rst
,
510 'check': check_doc_docs_automake
}
515 'match_name': lambda x
: not line_length_blacklist
.search(x
),
516 'check': lambda x
: line_length_check(x
)},
519 'match_name': lambda x
: not leading_whitespace_blacklist
.search(x
),
520 'check': lambda x
: not leading_whitespace_is_spaces(x
),
521 'print': lambda: print_warning("Line has non-spaces leading whitespace")},
523 {'regex': None, 'match_name': None,
524 'check': lambda x
: trailing_whitespace_or_crlf(x
),
525 'print': lambda: print_warning("Line has trailing whitespace")},
527 {'regex': r
'(\.c|\.h)(\.in)?$', 'match_name': None,
528 'prereq': lambda x
: not is_comment_line(x
),
529 'check': lambda x
: not if_and_for_whitespace_checks(x
),
530 'print': lambda: print_error("Improper whitespace around control block")},
532 {'regex': r
'(\.c|\.h)(\.in)?$', 'match_name': None,
533 'prereq': lambda x
: not is_comment_line(x
),
534 'check': lambda x
: not if_and_for_end_with_bracket_check(x
),
535 'print': lambda: print_error("Inappropriate bracing around statement")},
537 {'regex': r
'(\.c|\.h)(\.in)?$', 'match_name': None,
538 'prereq': lambda x
: not is_comment_line(x
),
539 'check': lambda x
: pointer_whitespace_check(x
),
541 lambda: print_error("Inappropriate spacing in pointer declaration")},
543 {'regex': r
'(\.c|\.h)(\.in)?$', 'match_name': None,
544 'prereq': lambda x
: not is_comment_line(x
),
545 'check': lambda x
: trailing_operator(x
),
547 lambda: print_error("Line has '?' or ':' operator at end of line")},
549 {'regex': r
'(\.c|\.h)(\.in)?$', 'match_name': None,
550 'prereq': lambda x
: has_comment(x
),
551 'check': lambda x
: has_xxx_mark(x
),
552 'print': lambda: print_warning("Comment with 'xxx' marker")},
554 {'regex': r
'(\.c|\.h)(\.in)?$', 'match_name': None,
555 'prereq': lambda x
: not is_comment_line(x
),
556 'check': lambda x
: has_c99_comment(x
),
557 'print': lambda: print_error("C99 style comment")},
559 {'regex': r
'(\.c|\.h)(\.in)?$', 'match_name': None,
560 'prereq': lambda x
: has_comment(x
),
561 'check': lambda x
: check_comment_spelling(x
)},
563 {'regex': r
'(\.c|\.h)(\.in)?$', 'match_name': None,
564 'check': lambda x
: empty_return_with_brace(x
),
565 'interim_line': True,
567 lambda: print_warning("Empty return followed by brace, consider omitting")
572 def regex_function_factory(func_name
):
573 regex
= re
.compile(r
'\b%s\([^)]*\)' % func_name
)
574 return lambda x
: regex
.search(x
) is not None
577 def regex_error_factory(description
):
578 return lambda: print_error(description
)
582 ('malloc', 'Use xmalloc() in place of malloc()'),
583 ('calloc', 'Use xcalloc() in place of calloc()'),
584 ('realloc', 'Use xrealloc() in place of realloc()'),
585 ('strdup', 'Use xstrdup() in place of strdup()'),
586 ('asprintf', 'Use xasprintf() in place of asprintf()'),
587 ('vasprintf', 'Use xvasprintf() in place of vasprintf()'),
588 ('strcpy', 'Use ovs_strlcpy() in place of strcpy()'),
589 ('strlcpy', 'Use ovs_strlcpy() in place of strlcpy()'),
590 ('strncpy', 'Use ovs_strzcpy() in place of strncpy()'),
591 ('strerror', 'Use ovs_strerror() in place of strerror()'),
592 ('sleep', 'Use xsleep() in place of sleep()'),
593 ('abort', 'Use ovs_abort() in place of abort()'),
594 ('assert', 'Use ovs_assert() in place of assert()'),
595 ('error', 'Use ovs_error() in place of error()'),
598 {'regex': r
'(\.c|\.h)(\.in)?$',
600 'prereq': lambda x
: not is_comment_line(x
),
601 'check': regex_function_factory(function_name
),
602 'print': regex_error_factory(description
)}
603 for (function_name
, description
) in std_functions
]
606 def regex_operator_factory(operator
):
607 regex
= re
.compile(r
'^[^#][^"\']*[^
"]%s[^ "\'][^
"]*' % operator)
608 return lambda x: regex.search(filter_comments(x)) is not None
612 [re.escape(op) for op in ['%', '<<', '>>', '<=', '>=', '==', '!=',
613 '^', '|', '&&', '||', '?:', '=', '+=', '-=', '*=', '/=', '%=',
614 '&=', '^=', '|=', '<<=', '>>=']] \
620 r'[^" \
-(]\
-[^
"\->;]',
621 r'[^" <>=!^|
+\
-*/%&]=[^
"=]',
624 {'regex': r'(\.c|\.h)(\.in)?$', 'match_name': None,
625 'prereq': lambda x: not is_comment_line(x),
626 'check': regex_operator_factory(operator),
627 'print': lambda: print_warning("Line lacks whitespace around operator
")}
628 for operator in infix_operators]
631 def get_file_type_checks(filename):
632 """Returns the list of checks for a file based on matching the filename
637 if check['regex'] is None and check['match_name'] is None:
638 checkList.append(check)
639 if check['regex'] is not None and \
640 re.compile(check['regex']).search(filename) is not None:
641 checkList.append(check)
642 elif check['match_name'] is not None and check['match_name'](filename):
643 checkList.append(check)
647 def run_checks(current_file, line, lineno):
648 """Runs the various checks for the particular line. This will take
649 filename into account."""
650 global checking_file, total_line
652 for check in get_file_type_checks(current_file):
653 if 'prereq' in check and not check['prereq'](line):
655 if check['check'](line):
662 print("%s:%d:" % (current_file, lineno))
664 print("#%d FILE: %s:%d:" % (total_line, current_file, lineno))
668 def interim_line_check(current_file
, line
, lineno
):
669 """Runs the various checks for the particular interim line. This will
670 take filename into account, and will check for the 'interim_line'
671 key before running the check."""
672 global checking_file
, total_line
674 for check
in get_file_type_checks(current_file
):
675 if 'prereq' in check
and not check
['prereq'](line
):
677 if 'interim_line' in check
and check
['interim_line']:
678 if check
['check'](line
):
685 print("%s:%d:" % (current_file
, lineno
))
687 print("#%d FILE: %s:%d:" % (total_line
, current_file
, lineno
))
691 def run_file_checks(text
):
692 """Runs the various checks for the text."""
693 for check
in file_checks
:
694 if check
['regex'].search(text
) is not None:
698 def ovs_checkpatch_parse(text
, filename
, author
=None, committer
=None):
699 global print_file_name
, total_line
, checking_file
, \
700 empty_return_check_state
702 PARSE_STATE_HEADING
= 0
703 PARSE_STATE_DIFF_HEADER
= 1
704 PARSE_STATE_CHANGE_BODY
= 2
710 current_file
= filename
if checking_file
else ''
712 seppatch
= re
.compile(r
'^---([\w]*| \S+)$')
713 hunks
= re
.compile(r
'^(---|\+\+\+) (\S+)')
714 hunk_differences
= re
.compile(
715 r
'^@@ ([0-9-+]+),([0-9-+]+) ([0-9-+]+),([0-9-+]+) @@')
716 is_author
= re
.compile(r
'^(Author|From): (.*)$', re
.I | re
.M | re
.S
)
717 is_committer
= re
.compile(r
'^(Commit: )(.*)$', re
.I | re
.M | re
.S
)
718 is_signature
= re
.compile(r
'^(Signed-off-by: )(.*)$',
720 is_co_author
= re
.compile(r
'^(Co-authored-by: )(.*)$',
722 is_gerrit_change_id
= re
.compile(r
'(\s*(change-id: )(.*))$',
727 for line
in text
.split('\n'):
728 if current_file
!= previous_file
:
729 previous_file
= current_file
732 total_line
= total_line
+ 1
737 parse
= PARSE_STATE_CHANGE_BODY
739 if parse
== PARSE_STATE_DIFF_HEADER
:
740 match
= hunks
.match(line
)
742 parse
= PARSE_STATE_CHANGE_BODY
743 current_file
= match
.group(2)[2:]
744 print_file_name
= current_file
746 elif parse
== PARSE_STATE_HEADING
:
747 if seppatch
.match(line
):
748 parse
= PARSE_STATE_DIFF_HEADER
749 if not skip_signoff_check
:
751 # Check that the patch has an author, that the
752 # author is not among the co-authors, and that the
753 # co-authors are unique.
755 print_error("Patch lacks author.")
757 if " via " in author
or "@openvswitch.org" in author
:
758 print_error("Author should not be mailing list.")
760 if author
in co_authors
:
761 print_error("Author should not be also be co-author.")
763 if len(set(co_authors
)) != len(co_authors
):
764 print_error("Duplicate co-author.")
766 # Check that the author, all co-authors, and the
767 # committer (if any) signed off.
768 if author
not in signatures
:
769 print_error("Author %s needs to sign off." % author
)
770 for ca
in co_authors
:
771 if ca
not in signatures
:
772 print_error("Co-author %s needs to sign off." % ca
)
775 and author
!= committer
776 and committer
not in signatures
):
777 print_error("Committer %s needs to sign off."
780 # Check for signatures that we do not expect.
781 # This is only a warning because there can be,
782 # rarely, a signature chain.
784 # If we don't have a known committer, and there is
785 # a single extra sign-off, then do not warn
786 # because that extra sign-off is probably the
788 extra_sigs
= [x
for x
in signatures
789 if x
not in co_authors
792 if len(extra_sigs
) > 1 or (committer
and extra_sigs
):
793 print_warning("Unexpected sign-offs from developers "
794 "who are not authors or co-authors or "
796 % ", ".join(extra_sigs
))
797 elif is_committer
.match(line
):
798 committer
= is_committer
.match(line
).group(2)
799 elif is_author
.match(line
):
800 author
= is_author
.match(line
).group(2)
801 elif is_signature
.match(line
):
802 m
= is_signature
.match(line
)
803 signatures
.append(m
.group(2))
804 elif is_co_author
.match(line
):
805 m
= is_co_author
.match(line
)
806 co_authors
.append(m
.group(2))
807 elif is_gerrit_change_id
.match(line
):
809 "Remove Gerrit Change-Id's before submitting upstream.")
810 print("%d: %s\n" % (lineno
, line
))
811 elif parse
== PARSE_STATE_CHANGE_BODY
:
812 newfile
= hunks
.match(line
)
814 current_file
= newfile
.group(2)[2:]
815 print_file_name
= current_file
817 reset_line_number
= hunk_differences
.match(line
)
818 if reset_line_number
:
819 empty_return_check_state
= RETURN_CHECK_INITIAL_STATE
820 lineno
= int(reset_line_number
.group(3))
825 if is_subtracted_line(line
):
829 cmp_line
= added_line(line
)
831 if not is_added_line(line
):
832 interim_line_check(current_file
, cmp_line
, lineno
)
835 # Skip files which have /datapath in them, since they are
836 # linux or windows coding standards
837 if current_file
.startswith('datapath'):
839 if current_file
.startswith('include/linux'):
841 run_checks(current_file
, cmp_line
, lineno
)
843 run_file_checks(text
)
844 if __errors
or __warnings
:
851 Open vSwitch checkpatch.py
852 Checks a patch for trivial mistakes.
854 %s [options] [PATCH1 [PATCH2 ...] | -f SOURCE1 [SOURCE2 ...] | -1 | -2 | ...]
857 -f|--check-file Arguments are source files, not patches.
858 -1, -2, ... Check recent commits in this repo.
861 -h|--help This help message
862 -b|--skip-block-whitespace Skips the if/while/for whitespace tests
863 -l|--skip-leading-whitespace Skips the leading whitespace test
864 -q|--quiet Only print error and warning information
865 -s|--skip-signoff-lines Tolerate missing Signed-off-by line
866 -S|--spellcheck-comments Check C comments for possible spelling mistakes
867 -t|--skip-trailing-whitespace Skips the trailing whitespace test"""
871 def ovs_checkpatch_print_result(result
):
872 global quiet
, __warnings
, __errors
, total_line
875 print("Lines checked: %d, Warnings: %d, Errors: %d\n" %
876 (total_line
, __warnings
, __errors
))
878 print("Lines checked: %d, no obvious problems found\n" % (total_line
))
881 def ovs_checkpatch_file(filename
):
883 mail
= email
.message_from_file(open(filename
, 'r'))
885 print_error("Unable to parse file '%s'. Is it a patch?" % filename
)
888 for part
in mail
.walk():
889 if part
.get_content_maintype() == 'multipart':
891 result
= ovs_checkpatch_parse(part
.get_payload(decode
=False), filename
,
892 mail
.get('Author', mail
['From']),
894 ovs_checkpatch_print_result(result
)
898 def partition(pred
, iterable
):
899 """Returns [[trues], [falses]], where [trues] is the items in
900 'iterable' that satisfy 'pred' and [falses] is all the rest."""
903 for item
in iterable
:
911 if __name__
== '__main__':
913 numeric_options
, args
= partition(lambda s
: re
.match('-[0-9]+$', s
),
915 n_patches
= int(numeric_options
[-1][1:]) if numeric_options
else 0
917 optlist
, args
= getopt
.getopt(args
, 'bhlstfSq',
920 "skip-block-whitespace",
921 "skip-leading-whitespace",
922 "skip-signoff-lines",
923 "skip-trailing-whitespace",
924 "spellcheck-comments",
927 print("Unknown option encountered. Please rerun with -h for help.")
931 if o
in ("-h", "--help"):
934 elif o
in ("-b", "--skip-block-whitespace"):
935 skip_block_whitespace_check
= True
936 elif o
in ("-l", "--skip-leading-whitespace"):
937 skip_leading_whitespace_check
= True
938 elif o
in ("-s", "--skip-signoff-lines"):
939 skip_signoff_check
= True
940 elif o
in ("-t", "--skip-trailing-whitespace"):
941 skip_trailing_whitespace_check
= True
942 elif o
in ("-f", "--check-file"):
944 elif o
in ("-S", "--spellcheck-comments"):
945 if not open_spell_check_dict():
946 print("WARNING: The enchant library isn't available.")
947 print(" Please install python enchant.")
949 spellcheck_comments
= True
950 elif o
in ("-q", "--quiet"):
953 print("Unknown option '%s'" % o
)
956 if sys
.stdout
.isatty():
962 git_log
= 'git log --no-color --no-merges --pretty=format:"%H %s" '
963 with os
.popen(git_log
+ '-%d' % n_patches
, 'r') as f
:
964 commits
= f
.read().split("\n")
966 for i
in reversed(range(0, n_patches
)):
967 revision
, name
= commits
[i
].split(" ", 1)
968 f
= os
.popen('''git format-patch -1 --stdout --pretty=format:"\
973 %b" ''' + revision
, 'r')
978 print('== Checking %s ("%s") ==' % (revision
[0:12], name
))
979 result
= ovs_checkpatch_parse(patch
, revision
)
980 ovs_checkpatch_print_result(result
)
986 if sys
.stdin
.isatty():
989 result
= ovs_checkpatch_parse(sys
.stdin
.read(), '-')
990 ovs_checkpatch_print_result(result
)
994 for filename
in args
:
996 print('== Checking "%s" ==' % filename
)
997 result
= ovs_checkpatch_file(filename
)