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
30 empty_return_check_state
= 0
31 print_file_name
= None
35 spellcheck_comments
= False
37 spell_check_dict
= None
40 def open_spell_check_dict():
44 extra_keywords
= ['ovs', 'vswitch', 'vswitchd', 'ovs-vswitchd',
45 'netdev', 'selinux', 'ovs-ctl', 'dpctl', 'ofctl',
46 'openvswitch', 'dpdk', 'hugepage', 'hugepages',
47 'pmd', 'upcall', 'vhost', 'rx', 'tx', 'vhostuser',
48 'openflow', 'qsort', 'rxq', 'txq', 'perf', 'stats',
49 'struct', 'int', 'char', 'bool', 'upcalls', 'nicira',
50 'bitmask', 'ipv4', 'ipv6', 'tcp', 'tcp4', 'tcpv4',
51 'udp', 'udp4', 'udpv4', 'icmp', 'icmp4', 'icmpv6',
52 'vlan', 'vxlan', 'cksum', 'csum', 'checksum',
53 'ofproto', 'numa', 'mempool', 'mempools', 'mbuf',
54 'mbufs', 'hmap', 'cmap', 'smap', 'dhcpv4', 'dhcp',
55 'dhcpv6', 'opts', 'metadata', 'geneve', 'mutex',
56 'netdev', 'netdevs', 'subtable', 'virtio', 'qos',
57 'policer', 'datapath', 'tunctl', 'attr', 'ethernet',
58 'ether', 'defrag', 'defragment', 'loopback', 'sflow',
59 'acl', 'initializer', 'recirc', 'xlated', 'unclosed',
60 'netlink', 'msec', 'usec', 'nsec', 'ms', 'us', 'ns',
61 'kilobits', 'kbps', 'kilobytes', 'megabytes', 'mbps',
62 'gigabytes', 'gbps', 'megabits', 'gigabits', 'pkts',
63 'tuple', 'miniflow', 'megaflow', 'conntrack',
64 'vlans', 'vxlans', 'arg', 'tpid', 'xbundle',
65 'xbundles', 'mbundle', 'mbundles', 'netflow',
66 'localnet', 'odp', 'pre', 'dst', 'dest', 'src',
67 'ethertype', 'cvlan', 'ips', 'msg', 'msgs',
68 'liveness', 'userspace', 'eventmask', 'datapaths',
69 'slowpath', 'fastpath', 'multicast', 'unicast',
70 'revalidation', 'namespace', 'qdisc', 'uuid',
71 'ofport', 'subnet', 'revalidation', 'revalidator',
72 'revalidate', 'l2', 'l3', 'l4', 'openssl', 'mtu',
73 'ifindex', 'enum', 'enums', 'http', 'https', 'num',
74 'vconn', 'vconns', 'conn', 'nat', 'memset', 'memcmp',
75 'strcmp', 'strcasecmp', 'tc', 'ufid', 'api',
76 'ofpbuf', 'ofpbufs', 'hashmaps', 'hashmap', 'deref',
77 'dereference', 'hw', 'prio', 'sendmmsg', 'sendmsg',
78 'malloc', 'free', 'alloc', 'pid', 'ppid', 'pgid',
79 'uid', 'gid', 'sid', 'utime', 'stime', 'cutime',
80 'cstime', 'vsize', 'rss', 'rsslim', 'whcan', 'gtime',
81 'eip', 'rip', 'cgtime', 'dbg', 'gw', 'sbrec', 'bfd',
82 'sizeof', 'pmds', 'nic', 'nics', 'hwol', 'encap',
83 'decap', 'tlv', 'tlvs', 'decapsulation', 'fd',
84 'cacheline', 'xlate', 'skiplist', 'idl',
85 'comparator', 'natting', 'alg', 'pasv', 'epasv',
86 'wildcard', 'nated', 'amd64', 'x86_64',
89 global spell_check_dict
90 spell_check_dict
= enchant
.Dict("en_US")
91 for kw
in extra_keywords
:
92 spell_check_dict
.add(kw
)
113 def get_yellow_begin():
120 def print_error(message
):
122 print("%sERROR%s: %s" % (get_red_begin(), get_color_end(), message
))
124 __errors
= __errors
+ 1
127 def print_warning(message
):
129 print("%sWARNING%s: %s" % (get_yellow_begin(), get_color_end(), message
))
131 __warnings
= __warnings
+ 1
134 def reset_counters():
135 global __errors
, __warnings
, total_line
142 # These are keywords whose names are normally followed by a space and
143 # something in parentheses (usually an expression) then a left curly brace.
145 # 'do' almost qualifies but it's also used as "do { ... } while (...);".
146 __parenthesized_constructs
= 'if|for|while|switch|[_A-Z]+FOR_*EACH[_A-Z]*'
148 __regex_added_line
= re
.compile(r
'^\+{1,2}[^\+][\w\W]*')
149 __regex_subtracted_line
= re
.compile(r
'^\-{1,2}[^\-][\w\W]*')
150 __regex_leading_with_whitespace_at_all
= re
.compile(r
'^\s+')
151 __regex_leading_with_spaces
= re
.compile(r
'^ +[\S]+')
152 __regex_trailing_whitespace
= re
.compile(r
'[^\S]+$')
153 __regex_single_line_feed
= re
.compile(r
'^\f$')
154 __regex_for_if_missing_whitespace
= re
.compile(r
' +(%s)[\(]'
155 % __parenthesized_constructs
)
156 __regex_for_if_too_much_whitespace
= re
.compile(r
' +(%s) +[\(]'
157 % __parenthesized_constructs
)
158 __regex_for_if_parens_whitespace
= \
159 re
.compile(r
' +(%s) \( +[\s\S]+\)' % __parenthesized_constructs
)
160 __regex_is_for_if_single_line_bracket
= \
161 re
.compile(r
'^ +(%s) \(.*\)' % __parenthesized_constructs
)
162 __regex_ends_with_bracket
= \
163 re
.compile(r
'[^\s]\) {(\s+/\*[\s\Sa-zA-Z0-9\.,\?\*/+-]*)?$')
164 __regex_ptr_declaration_missing_whitespace
= re
.compile(r
'[a-zA-Z0-9]\*[^*]')
165 __regex_is_comment_line
= re
.compile(r
'^\s*(/\*|\*\s)')
166 __regex_has_comment
= re
.compile(r
'.*(/\*|\*\s)')
167 __regex_has_c99_comment
= re
.compile(r
'.*//.*$')
168 __regex_trailing_operator
= re
.compile(r
'^[^ ]* [^ ]*[?:]$')
169 __regex_conditional_else_bracing
= re
.compile(r
'^\s*else\s*{?$')
170 __regex_conditional_else_bracing2
= re
.compile(r
'^\s*}\selse\s*$')
171 __regex_has_xxx_mark
= re
.compile(r
'.*xxx.*', re
.IGNORECASE
)
172 __regex_added_doc_rst
= re
.compile(
173 r
'\ndiff .*Documentation/.*rst\nnew file mode')
174 __regex_empty_return
= re
.compile(r
'\s*return;')
175 __regex_if_macros
= re
.compile(r
'^ +(%s) \([\S][\s\S]+[\S]\) { \\' %
176 __parenthesized_constructs
)
178 skip_leading_whitespace_check
= False
179 skip_trailing_whitespace_check
= False
180 skip_block_whitespace_check
= False
181 skip_signoff_check
= False
183 # Don't enforce character limit on files that include these characters in their
184 # name, as they may have legitimate reasons to have longer lines.
186 # Python isn't checked as flake8 performs these checks during build.
187 line_length_blacklist
= re
.compile(
188 r
'\.(am|at|etc|in|m4|mk|patch|py)$|debian/rules')
190 # Don't enforce a requirement that leading whitespace be all spaces on
191 # files that include these characters in their name, since these kinds
192 # of files need lines with leading tabs.
193 leading_whitespace_blacklist
= re
.compile(r
'\.(mk|am|at)$|debian/rules')
196 def is_subtracted_line(line
):
197 """Returns TRUE if the line in question has been removed."""
198 return __regex_subtracted_line
.search(line
) is not None
201 def is_added_line(line
):
202 """Returns TRUE if the line in question is an added line.
205 return __regex_added_line
.search(line
) is not None or checking_file
208 def added_line(line
):
209 """Returns the line formatted properly by removing diff syntax"""
211 if not checking_file
:
216 def leading_whitespace_is_spaces(line
):
217 """Returns TRUE if the leading whitespace in added lines is spaces
219 if skip_leading_whitespace_check
:
221 if (__regex_leading_with_whitespace_at_all
.search(line
) is not None and
222 __regex_single_line_feed
.search(line
) is None):
223 return __regex_leading_with_spaces
.search(line
) is not None
228 def trailing_whitespace_or_crlf(line
):
229 """Returns TRUE if the trailing characters is whitespace
231 if skip_trailing_whitespace_check
:
233 return (__regex_trailing_whitespace
.search(line
) is not None and
234 __regex_single_line_feed
.search(line
) is None)
237 def if_and_for_whitespace_checks(line
):
238 """Return TRUE if there is appropriate whitespace after if, for, while
240 if skip_block_whitespace_check
:
242 if (__regex_for_if_missing_whitespace
.search(line
) is not None or
243 __regex_for_if_too_much_whitespace
.search(line
) is not None or
244 __regex_for_if_parens_whitespace
.search(line
)):
249 def if_and_for_end_with_bracket_check(line
):
250 """Return TRUE if there is not a bracket at the end of an if, for, while
251 block which fits on a single line ie: 'if (foo)'"""
253 def balanced_parens(line
):
254 """This is a rather naive counter - it won't deal with quotes"""
263 if __regex_is_for_if_single_line_bracket
.search(line
) is not None:
264 if not balanced_parens(line
):
267 if __regex_ends_with_bracket
.search(line
) is None and \
268 __regex_if_macros
.match(line
) is None:
270 if __regex_conditional_else_bracing
.match(line
) is not None:
272 if __regex_conditional_else_bracing2
.match(line
) is not None:
277 def pointer_whitespace_check(line
):
278 """Return TRUE if there is no space between a pointer name and the
279 asterisk that denotes this is a apionter type, ie: 'struct foo*'"""
280 return __regex_ptr_declaration_missing_whitespace
.search(line
) is not None
283 def line_length_check(line
):
284 """Return TRUE if the line length is too long"""
286 print_warning("Line is %d characters long (recommended limit is 79)"
292 def is_comment_line(line
):
293 """Returns TRUE if the current line is part of a block comment."""
294 return __regex_is_comment_line
.match(line
) is not None
297 def has_comment(line
):
298 """Returns TRUE if the current line contains a comment or is part of
300 return __regex_has_comment
.match(line
) is not None
303 def has_c99_comment(line
):
304 """Returns TRUE if the current line contains C99 style comment (//)."""
305 return __regex_has_c99_comment
.match(line
) is not None
308 def trailing_operator(line
):
309 """Returns TRUE if the current line ends with an operatorsuch as ? or :"""
310 return __regex_trailing_operator
.match(line
) is not None
313 def has_xxx_mark(line
):
314 """Returns TRUE if the current line contains 'xxx'."""
315 return __regex_has_xxx_mark
.match(line
) is not None
318 def filter_comments(current_line
, keep
=False):
319 """remove all of the c-style comments in a line"""
321 STATE_COMMENT_SLASH
= 1
322 STATE_COMMENT_CONTENTS
= 3
323 STATE_COMMENT_END_SLASH
= 4
327 check_state
= STATE_NORMAL
328 only_whitespace
= True
331 check_state
= STATE_COMMENT_CONTENTS
333 for c
in current_line
:
335 if state
== STATE_NORMAL
:
336 state
= STATE_COMMENT_SLASH
337 elif state
== STATE_COMMENT_SLASH
:
338 # This is for c++ style comments. We will warn later
339 return sanitized_line
[:1]
340 elif state
== STATE_COMMENT_END_SLASH
:
345 # just assume this is a continuation from the previous line
347 state
= STATE_COMMENT_END_SLASH
348 elif state
== STATE_COMMENT_SLASH
:
349 state
= STATE_COMMENT_CONTENTS
350 sanitized_line
= sanitized_line
[:-1]
351 elif state
== STATE_COMMENT_CONTENTS
:
352 state
= STATE_COMMENT_END_SLASH
353 elif state
== STATE_COMMENT_END_SLASH
:
354 # Need to re-introduce the star from the previous state, since
355 # it may have been clipped by the state check below.
357 state
= STATE_COMMENT_CONTENTS
358 elif state
== STATE_COMMENT_SLASH
:
359 # Need to re-introduce the slash from the previous state, since
360 # it may have been clipped by the state check below.
364 if state
!= check_state
:
368 only_whitespace
= False
372 return sanitized_line
375 def check_comment_spelling(line
):
376 if not spell_check_dict
or not spellcheck_comments
:
379 comment_words
= filter_comments(line
, True).replace(':', ' ').split(' ')
380 for word
in comment_words
:
382 strword
= re
.subn(r
'\W+', '', word
)[0].replace(',', '')
383 if len(strword
) and not spell_check_dict
.check(strword
.lower()):
384 if any([check_char
in word
385 for check_char
in ['=', '(', '-', '_', '/', '\'']]):
388 # special case the '.'
389 if '.' in word
and not word
.endswith('.'):
392 # skip proper nouns and references to macros
393 if strword
.isupper() or (strword
[0].isupper() and
394 strword
[1:].islower()):
397 # skip words that start with numbers
398 if strword
.startswith(tuple('0123456789')):
402 print_warning("Check for spelling mistakes (e.g. \"%s\")"
409 def __check_doc_is_listed(text
, doctype
, docdir
, docfile
):
411 beginre
= re
.compile(r
'\+\+\+.*{}/index.rst'.format(docdir
))
412 docre
= re
.compile(r
'\n\+.*{}'.format(docfile
.replace('.rst', '')))
413 elif doctype
== 'automake':
414 beginre
= re
.compile(r
'\+\+\+.*Documentation/automake.mk')
415 docre
= re
.compile(r
'\n\+\t{}/{}'.format(docdir
, docfile
))
417 raise NotImplementedError("Invalid doctype: {}".format(doctype
))
419 res
= beginre
.search(text
)
423 hunkstart
= res
.span()[1]
424 hunkre
= re
.compile(r
'\n(---|\+\+\+) (\S+)')
425 res
= hunkre
.search(text
[hunkstart
:])
429 hunkend
= hunkstart
+ res
.span()[0]
431 hunk
= text
[hunkstart
:hunkend
]
432 # find if the file is being added.
433 if docre
.search(hunk
) is not None:
439 def __check_new_docs(text
, doctype
):
440 """Check if the documentation is listed properly. If doctype is 'rst' then
441 the index.rst is checked. If the doctype is 'automake' then automake.mk
442 is checked. Returns TRUE if the new file is not listed."""
444 new_docs
= __regex_added_doc_rst
.findall(text
)
446 docpathname
= doc
.split(' ')[2]
447 gitdocdir
, docfile
= os
.path
.split(docpathname
.rstrip('\n'))
448 if docfile
== "index.rst":
451 if gitdocdir
.startswith('a/'):
452 docdir
= gitdocdir
.replace('a/', '', 1)
456 if __check_doc_is_listed(text
, doctype
, docdir
, docfile
):
458 print_warning("New doc {} not listed in {}/index.rst".format(
460 elif doctype
== 'automake':
461 print_warning("New doc {} not listed in "
462 "Documentation/automake.mk".format(docfile
))
464 raise NotImplementedError("Invalid doctype: {}".format(
472 def check_doc_docs_automake(text
):
473 return __check_new_docs(text
, 'automake')
476 def check_new_docs_index(text
):
477 return __check_new_docs(text
, 'rst')
480 def empty_return_with_brace(line
):
481 """Returns TRUE if a function contains a return; followed
482 by one or more line feeds and terminates with a '}'
485 def empty_return(line
):
486 """Returns TRUE if a function has a 'return;'"""
487 return __regex_empty_return
.match(line
) is not None
489 global empty_return_check_state
490 if empty_return_check_state
== RETURN_CHECK_INITIAL_STATE \
491 and empty_return(line
):
492 empty_return_check_state
= RETURN_CHECK_STATE_WITH_RETURN
493 elif empty_return_check_state
== RETURN_CHECK_STATE_WITH_RETURN \
494 and (re
.match(r
'^}$', line
) or len(line
) == 0):
495 if re
.match('^}$', line
):
496 empty_return_check_state
= RETURN_CHECK_AWAITING_BRACE
498 empty_return_check_state
= RETURN_CHECK_INITIAL_STATE
500 if empty_return_check_state
== RETURN_CHECK_AWAITING_BRACE
:
501 empty_return_check_state
= RETURN_CHECK_INITIAL_STATE
508 {'regex': __regex_added_doc_rst
,
509 'check': check_new_docs_index
},
510 {'regex': __regex_added_doc_rst
,
511 'check': check_doc_docs_automake
}
516 'match_name': lambda x
: not line_length_blacklist
.search(x
),
517 'check': lambda x
: line_length_check(x
)},
520 'match_name': lambda x
: not leading_whitespace_blacklist
.search(x
),
521 'check': lambda x
: not leading_whitespace_is_spaces(x
),
522 'print': lambda: print_warning("Line has non-spaces leading whitespace")},
524 {'regex': None, 'match_name': None,
525 'check': lambda x
: trailing_whitespace_or_crlf(x
),
526 'print': lambda: print_warning("Line has trailing whitespace")},
528 {'regex': r
'(\.c|\.h)(\.in)?$', 'match_name': None,
529 'prereq': lambda x
: not is_comment_line(x
),
530 'check': lambda x
: not if_and_for_whitespace_checks(x
),
531 'print': lambda: print_error("Improper whitespace around control block")},
533 {'regex': r
'(\.c|\.h)(\.in)?$', 'match_name': None,
534 'prereq': lambda x
: not is_comment_line(x
),
535 'check': lambda x
: not if_and_for_end_with_bracket_check(x
),
536 'print': lambda: print_error("Inappropriate bracing around statement")},
538 {'regex': r
'(\.c|\.h)(\.in)?$', 'match_name': None,
539 'prereq': lambda x
: not is_comment_line(x
),
540 'check': lambda x
: pointer_whitespace_check(x
),
542 lambda: print_error("Inappropriate spacing in pointer declaration")},
544 {'regex': r
'(\.c|\.h)(\.in)?$', 'match_name': None,
545 'prereq': lambda x
: not is_comment_line(x
),
546 'check': lambda x
: trailing_operator(x
),
548 lambda: print_error("Line has '?' or ':' operator at end of line")},
550 {'regex': r
'(\.c|\.h)(\.in)?$', 'match_name': None,
551 'prereq': lambda x
: has_comment(x
),
552 'check': lambda x
: has_xxx_mark(x
),
553 'print': lambda: print_warning("Comment with 'xxx' marker")},
555 {'regex': r
'(\.c|\.h)(\.in)?$', 'match_name': None,
556 'prereq': lambda x
: not is_comment_line(x
),
557 'check': lambda x
: has_c99_comment(x
),
558 'print': lambda: print_error("C99 style comment")},
560 {'regex': r
'(\.c|\.h)(\.in)?$', 'match_name': None,
561 'prereq': lambda x
: has_comment(x
),
562 'check': lambda x
: check_comment_spelling(x
)},
564 {'regex': r
'(\.c|\.h)(\.in)?$', 'match_name': None,
565 'check': lambda x
: empty_return_with_brace(x
),
566 'interim_line': True,
568 lambda: print_warning("Empty return followed by brace, consider omitting")
573 def regex_function_factory(func_name
):
574 regex
= re
.compile(r
'\b%s\([^)]*\)' % func_name
)
575 return lambda x
: regex
.search(x
) is not None
578 def regex_error_factory(description
):
579 return lambda: print_error(description
)
583 ('malloc', 'Use xmalloc() in place of malloc()'),
584 ('calloc', 'Use xcalloc() in place of calloc()'),
585 ('realloc', 'Use xrealloc() in place of realloc()'),
586 ('strdup', 'Use xstrdup() in place of strdup()'),
587 ('asprintf', 'Use xasprintf() in place of asprintf()'),
588 ('vasprintf', 'Use xvasprintf() in place of vasprintf()'),
589 ('strcpy', 'Use ovs_strlcpy() in place of strcpy()'),
590 ('strlcpy', 'Use ovs_strlcpy() in place of strlcpy()'),
591 ('strncpy', 'Use ovs_strzcpy() in place of strncpy()'),
592 ('strerror', 'Use ovs_strerror() in place of strerror()'),
593 ('sleep', 'Use xsleep() in place of sleep()'),
594 ('abort', 'Use ovs_abort() in place of abort()'),
595 ('assert', 'Use ovs_assert() in place of assert()'),
596 ('error', 'Use ovs_error() in place of error()'),
599 {'regex': r
'(\.c|\.h)(\.in)?$',
601 'prereq': lambda x
: not is_comment_line(x
),
602 'check': regex_function_factory(function_name
),
603 'print': regex_error_factory(description
)}
604 for (function_name
, description
) in std_functions
]
607 def regex_operator_factory(operator
):
608 regex
= re
.compile(r
'^[^#][^"\']*[^
"]%s[^ "\'][^
"]*' % operator)
609 return lambda x: regex.search(filter_comments(x)) is not None
613 [re.escape(op) for op in ['%', '<<', '>>', '<=', '>=', '==', '!=',
614 '^', '|', '&&', '||', '?:', '=', '+=', '-=', '*=', '/=', '%=',
615 '&=', '^=', '|=', '<<=', '>>=']] \
621 r'[^" \
-(]\
-[^
"\->;]',
622 r'[^" <>=!^|
+\
-*/%&]=[^
"=]',
625 {'regex': r'(\.c|\.h)(\.in)?$', 'match_name': None,
626 'prereq': lambda x: not is_comment_line(x),
627 'check': regex_operator_factory(operator),
628 'print': lambda: print_warning("Line lacks whitespace around operator
")}
629 for operator in infix_operators]
632 def get_file_type_checks(filename):
633 """Returns the list of checks for a file based on matching the filename
638 if check['regex'] is None and check['match_name'] is None:
639 checkList.append(check)
640 if check['regex'] is not None and \
641 re.compile(check['regex']).search(filename) is not None:
642 checkList.append(check)
643 elif check['match_name'] is not None and check['match_name'](filename):
644 checkList.append(check)
648 def run_checks(current_file, line, lineno):
649 """Runs the various checks for the particular line. This will take
650 filename into account."""
651 global checking_file, total_line
653 for check in get_file_type_checks(current_file):
654 if 'prereq' in check and not check['prereq'](line):
656 if check['check'](line):
663 print("%s:%d:" % (current_file, lineno))
665 print("#%d FILE: %s:%d:" % (total_line, current_file, lineno))
669 def interim_line_check(current_file
, line
, lineno
):
670 """Runs the various checks for the particular interim line. This will
671 take filename into account, and will check for the 'interim_line'
672 key before running the check."""
673 global checking_file
, total_line
675 for check
in get_file_type_checks(current_file
):
676 if 'prereq' in check
and not check
['prereq'](line
):
678 if 'interim_line' in check
and check
['interim_line']:
679 if check
['check'](line
):
686 print("%s:%d:" % (current_file
, lineno
))
688 print("#%d FILE: %s:%d:" % (total_line
, current_file
, lineno
))
692 def run_file_checks(text
):
693 """Runs the various checks for the text."""
694 for check
in file_checks
:
695 if check
['regex'].search(text
) is not None:
699 def ovs_checkpatch_parse(text
, filename
, author
=None, committer
=None):
700 global print_file_name
, total_line
, checking_file
, \
701 empty_return_check_state
703 PARSE_STATE_HEADING
= 0
704 PARSE_STATE_DIFF_HEADER
= 1
705 PARSE_STATE_CHANGE_BODY
= 2
711 current_file
= filename
if checking_file
else ''
713 seppatch
= re
.compile(r
'^---([\w]*| \S+)$')
714 hunks
= re
.compile(r
'^(---|\+\+\+) (\S+)')
715 hunk_differences
= re
.compile(
716 r
'^@@ ([0-9-+]+),([0-9-+]+) ([0-9-+]+),([0-9-+]+) @@')
717 is_author
= re
.compile(r
'^(Author|From): (.*)$', re
.I | re
.M | re
.S
)
718 is_committer
= re
.compile(r
'^(Commit: )(.*)$', re
.I | re
.M | re
.S
)
719 is_signature
= re
.compile(r
'^(Signed-off-by: )(.*)$',
721 is_co_author
= re
.compile(r
'^(Co-authored-by: )(.*)$',
723 is_gerrit_change_id
= re
.compile(r
'(\s*(change-id: )(.*))$',
728 for line
in text
.split('\n'):
729 if current_file
!= previous_file
:
730 previous_file
= current_file
733 total_line
= total_line
+ 1
738 parse
= PARSE_STATE_CHANGE_BODY
740 if parse
== PARSE_STATE_DIFF_HEADER
:
741 match
= hunks
.match(line
)
743 parse
= PARSE_STATE_CHANGE_BODY
744 current_file
= match
.group(2)[2:]
745 print_file_name
= current_file
747 elif parse
== PARSE_STATE_HEADING
:
748 if seppatch
.match(line
):
749 parse
= PARSE_STATE_DIFF_HEADER
750 if not skip_signoff_check
:
752 # Check that the patch has an author, that the
753 # author is not among the co-authors, and that the
754 # co-authors are unique.
756 print_error("Patch lacks author.")
758 if " via " in author
or "@openvswitch.org" in author
:
759 print_error("Author should not be mailing list.")
761 if author
in co_authors
:
762 print_error("Author should not be also be co-author.")
764 if len(set(co_authors
)) != len(co_authors
):
765 print_error("Duplicate co-author.")
767 # Check that the author, all co-authors, and the
768 # committer (if any) signed off.
769 if author
not in signatures
:
770 print_error("Author %s needs to sign off." % author
)
771 for ca
in co_authors
:
772 if ca
not in signatures
:
773 print_error("Co-author %s needs to sign off." % ca
)
776 and author
!= committer
777 and committer
not in signatures
):
778 print_error("Committer %s needs to sign off."
781 # Check for signatures that we do not expect.
782 # This is only a warning because there can be,
783 # rarely, a signature chain.
785 # If we don't have a known committer, and there is
786 # a single extra sign-off, then do not warn
787 # because that extra sign-off is probably the
789 extra_sigs
= [x
for x
in signatures
790 if x
not in co_authors
793 if len(extra_sigs
) > 1 or (committer
and extra_sigs
):
794 print_warning("Unexpected sign-offs from developers "
795 "who are not authors or co-authors or "
797 % ", ".join(extra_sigs
))
798 elif is_committer
.match(line
):
799 committer
= is_committer
.match(line
).group(2)
800 elif is_author
.match(line
):
801 author
= is_author
.match(line
).group(2)
802 elif is_signature
.match(line
):
803 m
= is_signature
.match(line
)
804 signatures
.append(m
.group(2))
805 elif is_co_author
.match(line
):
806 m
= is_co_author
.match(line
)
807 co_authors
.append(m
.group(2))
808 elif is_gerrit_change_id
.match(line
):
810 "Remove Gerrit Change-Id's before submitting upstream.")
811 print("%d: %s\n" % (lineno
, line
))
812 elif parse
== PARSE_STATE_CHANGE_BODY
:
813 newfile
= hunks
.match(line
)
815 current_file
= newfile
.group(2)[2:]
816 print_file_name
= current_file
818 reset_line_number
= hunk_differences
.match(line
)
819 if reset_line_number
:
820 empty_return_check_state
= RETURN_CHECK_INITIAL_STATE
821 lineno
= int(reset_line_number
.group(3))
826 if is_subtracted_line(line
):
830 cmp_line
= added_line(line
)
832 if not is_added_line(line
):
833 interim_line_check(current_file
, cmp_line
, lineno
)
836 # Skip files which have /datapath in them, since they are
837 # linux or windows coding standards
838 if current_file
.startswith('datapath'):
840 if current_file
.startswith('include/linux'):
842 run_checks(current_file
, cmp_line
, lineno
)
844 run_file_checks(text
)
845 if __errors
or __warnings
:
852 Open vSwitch checkpatch.py
853 Checks a patch for trivial mistakes.
855 %s [options] [PATCH1 [PATCH2 ...] | -f SOURCE1 [SOURCE2 ...] | -1 | -2 | ...]
858 -f|--check-file Arguments are source files, not patches.
859 -1, -2, ... Check recent commits in this repo.
862 -h|--help This help message
863 -b|--skip-block-whitespace Skips the if/while/for whitespace tests
864 -l|--skip-leading-whitespace Skips the leading whitespace test
865 -q|--quiet Only print error and warning information
866 -s|--skip-signoff-lines Tolerate missing Signed-off-by line
867 -S|--spellcheck-comments Check C comments for possible spelling mistakes
868 -t|--skip-trailing-whitespace Skips the trailing whitespace test"""
872 def ovs_checkpatch_print_result():
873 global quiet
, __warnings
, __errors
, total_line
875 if __errors
or __warnings
:
876 print("Lines checked: %d, Warnings: %d, Errors: %d\n" %
877 (total_line
, __warnings
, __errors
))
879 print("Lines checked: %d, no obvious problems found\n" % (total_line
))
882 def ovs_checkpatch_file(filename
):
884 mail
= email
.message_from_file(open(filename
, 'r'))
886 print_error("Unable to parse file '%s'. Is it a patch?" % filename
)
889 for part
in mail
.walk():
890 if part
.get_content_maintype() == 'multipart':
892 result
= ovs_checkpatch_parse(part
.get_payload(decode
=False), filename
,
893 mail
.get('Author', mail
['From']),
895 ovs_checkpatch_print_result()
899 def partition(pred
, iterable
):
900 """Returns [[trues], [falses]], where [trues] is the items in
901 'iterable' that satisfy 'pred' and [falses] is all the rest."""
904 for item
in iterable
:
912 if __name__
== '__main__':
914 numeric_options
, args
= partition(lambda s
: re
.match('-[0-9]+$', s
),
916 n_patches
= int(numeric_options
[-1][1:]) if numeric_options
else 0
918 optlist
, args
= getopt
.getopt(args
, 'bhlstfSq',
921 "skip-block-whitespace",
922 "skip-leading-whitespace",
923 "skip-signoff-lines",
924 "skip-trailing-whitespace",
925 "spellcheck-comments",
928 print("Unknown option encountered. Please rerun with -h for help.")
929 sys
.exit(EXIT_FAILURE
)
932 if o
in ("-h", "--help"):
935 elif o
in ("-b", "--skip-block-whitespace"):
936 skip_block_whitespace_check
= True
937 elif o
in ("-l", "--skip-leading-whitespace"):
938 skip_leading_whitespace_check
= True
939 elif o
in ("-s", "--skip-signoff-lines"):
940 skip_signoff_check
= True
941 elif o
in ("-t", "--skip-trailing-whitespace"):
942 skip_trailing_whitespace_check
= True
943 elif o
in ("-f", "--check-file"):
945 elif o
in ("-S", "--spellcheck-comments"):
946 if not open_spell_check_dict():
947 print("WARNING: The enchant library isn't available.")
948 print(" Please install python enchant.")
950 spellcheck_comments
= True
951 elif o
in ("-q", "--quiet"):
954 print("Unknown option '%s'" % o
)
955 sys
.exit(EXIT_FAILURE
)
957 if sys
.stdout
.isatty():
963 git_log
= 'git log --no-color --no-merges --pretty=format:"%H %s" '
964 with os
.popen(git_log
+ '-%d' % n_patches
, 'r') as f
:
965 commits
= f
.read().split("\n")
967 for i
in reversed(range(0, n_patches
)):
968 revision
, name
= commits
[i
].split(" ", 1)
969 f
= os
.popen('''git format-patch -1 --stdout --pretty=format:"\
974 %b" ''' + revision
, 'r')
979 print('== Checking %s ("%s") ==' % (revision
[0:12], name
))
980 result
= ovs_checkpatch_parse(patch
, revision
)
981 ovs_checkpatch_print_result()
983 status
= EXIT_FAILURE
987 if sys
.stdin
.isatty():
989 sys
.exit(EXIT_FAILURE
)
990 result
= ovs_checkpatch_parse(sys
.stdin
.read(), '-')
991 ovs_checkpatch_print_result()
995 for filename
in args
:
997 print('== Checking "%s" ==' % filename
)
998 result
= ovs_checkpatch_file(filename
)
1000 status
= EXIT_FAILURE