]>
Commit | Line | Data |
---|---|---|
1ca0323e | 1 | #!/usr/bin/env python3 |
ebba2af6 | 2 | # Copyright (c) 2016, 2017 Red Hat, Inc. |
4e99b70d | 3 | # Copyright (c) 2018 Nicira, Inc. |
c599d5cc AC |
4 | # |
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: | |
8 | # | |
9 | # http://www.apache.org/licenses/LICENSE-2.0 | |
10 | # | |
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 | |
17 | ||
18 | import email | |
19 | import getopt | |
a1fccabc | 20 | import os |
c599d5cc AC |
21 | import re |
22 | import sys | |
23 | ||
a9e5ac0f BS |
24 | RETURN_CHECK_INITIAL_STATE = 0 |
25 | RETURN_CHECK_STATE_WITH_RETURN = 1 | |
26 | RETURN_CHECK_AWAITING_BRACE = 2 | |
241bc88a | 27 | EXIT_FAILURE = 1 |
c599d5cc AC |
28 | __errors = 0 |
29 | __warnings = 0 | |
a9e5ac0f | 30 | empty_return_check_state = 0 |
3239c793 | 31 | print_file_name = None |
fb9410d8 | 32 | checking_file = False |
ebba2af6 AC |
33 | total_line = 0 |
34 | colors = False | |
9802fafa | 35 | spellcheck = False |
de8fa82a | 36 | quiet = False |
0fb02e8e BP |
37 | spell_check_dict = None |
38 | ||
39 | ||
40 | def open_spell_check_dict(): | |
41 | import enchant | |
42 | ||
43 | try: | |
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', | |
9802fafa IM |
87 | 'recirculation', 'linux', 'afxdp', 'promisc', 'goto', |
88 | 'misconfigured', 'misconfiguration', 'checkpatch', | |
89 | 'debian', 'travis', 'cirrus', 'appveyor', 'faq', | |
90 | 'erspan', 'const', 'hotplug', 'addresssanitizer', | |
91 | 'ovsdb', 'dpif', 'veth', 'rhel', 'jsonrpc', 'json', | |
92 | 'syscall', 'lacp', 'ipf', 'skb', 'valgrind'] | |
0fb02e8e BP |
93 | |
94 | global spell_check_dict | |
95 | spell_check_dict = enchant.Dict("en_US") | |
96 | for kw in extra_keywords: | |
97 | spell_check_dict.add(kw) | |
98 | ||
99 | return True | |
100 | except: | |
101 | return False | |
3239c793 AC |
102 | |
103 | ||
ebba2af6 AC |
104 | def get_color_end(): |
105 | global colors | |
106 | if colors: | |
107 | return "\033[00m" | |
108 | return "" | |
c599d5cc AC |
109 | |
110 | ||
ebba2af6 AC |
111 | def get_red_begin(): |
112 | global colors | |
113 | if colors: | |
114 | return "\033[91m" | |
115 | return "" | |
116 | ||
117 | ||
118 | def get_yellow_begin(): | |
119 | global colors | |
120 | if colors: | |
121 | return "\033[93m" | |
122 | return "" | |
123 | ||
124 | ||
125 | def print_error(message): | |
c599d5cc | 126 | global __errors |
ebba2af6 | 127 | print("%sERROR%s: %s" % (get_red_begin(), get_color_end(), message)) |
c599d5cc AC |
128 | |
129 | __errors = __errors + 1 | |
130 | ||
131 | ||
ebba2af6 | 132 | def print_warning(message): |
c599d5cc | 133 | global __warnings |
ebba2af6 | 134 | print("%sWARNING%s: %s" % (get_yellow_begin(), get_color_end(), message)) |
c599d5cc AC |
135 | |
136 | __warnings = __warnings + 1 | |
137 | ||
138 | ||
81c2316f | 139 | def reset_counters(): |
f61d40de | 140 | global __errors, __warnings, total_line |
81c2316f IM |
141 | |
142 | __errors = 0 | |
143 | __warnings = 0 | |
f61d40de | 144 | total_line = 0 |
81c2316f IM |
145 | |
146 | ||
b537de13 BP |
147 | # These are keywords whose names are normally followed by a space and |
148 | # something in parentheses (usually an expression) then a left curly brace. | |
149 | # | |
150 | # 'do' almost qualifies but it's also used as "do { ... } while (...);". | |
df8c04b1 | 151 | __parenthesized_constructs = 'if|for|while|switch|[_A-Z]+FOR_*EACH[_A-Z0-9]*' |
b537de13 | 152 | |
c599d5cc | 153 | __regex_added_line = re.compile(r'^\+{1,2}[^\+][\w\W]*') |
4d7f5e51 | 154 | __regex_subtracted_line = re.compile(r'^\-{1,2}[^\-][\w\W]*') |
c599d5cc AC |
155 | __regex_leading_with_whitespace_at_all = re.compile(r'^\s+') |
156 | __regex_leading_with_spaces = re.compile(r'^ +[\S]+') | |
157 | __regex_trailing_whitespace = re.compile(r'[^\S]+$') | |
c61e93d6 | 158 | __regex_single_line_feed = re.compile(r'^\f$') |
b537de13 BP |
159 | __regex_for_if_missing_whitespace = re.compile(r' +(%s)[\(]' |
160 | % __parenthesized_constructs) | |
161 | __regex_for_if_too_much_whitespace = re.compile(r' +(%s) +[\(]' | |
162 | % __parenthesized_constructs) | |
a1193b4d | 163 | __regex_for_if_parens_whitespace = \ |
b537de13 | 164 | re.compile(r' +(%s) \( +[\s\S]+\)' % __parenthesized_constructs) |
30c7ffd5 | 165 | __regex_is_for_if_single_line_bracket = \ |
b537de13 | 166 | re.compile(r'^ +(%s) \(.*\)' % __parenthesized_constructs) |
a1193b4d AC |
167 | __regex_ends_with_bracket = \ |
168 | re.compile(r'[^\s]\) {(\s+/\*[\s\Sa-zA-Z0-9\.,\?\*/+-]*)?$') | |
d56ec3bc | 169 | __regex_ptr_declaration_missing_whitespace = re.compile(r'[a-zA-Z0-9]\*[^*]') |
dc497e36 | 170 | __regex_cast_missing_whitespace = re.compile(r'\)[a-zA-Z0-9]') |
d6ec6c10 | 171 | __regex_is_comment_line = re.compile(r'^\s*(/\*|\*\s)') |
4e99b70d | 172 | __regex_has_comment = re.compile(r'.*(/\*|\*\s)') |
b48aa143 | 173 | __regex_has_c99_comment = re.compile(r'.*//.*$') |
12f62e9d | 174 | __regex_trailing_operator = re.compile(r'^[^ ]* [^ ]*[?:]$') |
3f9e248f JS |
175 | __regex_conditional_else_bracing = re.compile(r'^\s*else\s*{?$') |
176 | __regex_conditional_else_bracing2 = re.compile(r'^\s*}\selse\s*$') | |
4e99b70d | 177 | __regex_has_xxx_mark = re.compile(r'.*xxx.*', re.IGNORECASE) |
8503a516 FL |
178 | __regex_added_doc_rst = re.compile( |
179 | r'\ndiff .*Documentation/.*rst\nnew file mode') | |
a9e5ac0f | 180 | __regex_empty_return = re.compile(r'\s*return;') |
aacad868 | 181 | __regex_if_macros = re.compile(r'^ +(%s) \([\S]([\s\S]+[\S])*\) { +\\' % |
16770c6d | 182 | __parenthesized_constructs) |
c599d5cc AC |
183 | |
184 | skip_leading_whitespace_check = False | |
185 | skip_trailing_whitespace_check = False | |
68a95c9c | 186 | skip_gerrit_change_id_check = False |
c599d5cc AC |
187 | skip_block_whitespace_check = False |
188 | skip_signoff_check = False | |
189 | ||
6982ee96 JS |
190 | # Don't enforce character limit on files that include these characters in their |
191 | # name, as they may have legitimate reasons to have longer lines. | |
192 | # | |
193 | # Python isn't checked as flake8 performs these checks during build. | |
8205fbc8 | 194 | line_length_ignore_list = re.compile( |
9aef43f0 | 195 | r'\.(am|at|etc|in|m4|mk|patch|py)$|debian/rules') |
6982ee96 | 196 | |
ae9c0985 BP |
197 | # Don't enforce a requirement that leading whitespace be all spaces on |
198 | # files that include these characters in their name, since these kinds | |
199 | # of files need lines with leading tabs. | |
8205fbc8 | 200 | leading_whitespace_ignore_list = re.compile(r'\.(mk|am|at)$|debian/rules') |
ae9c0985 | 201 | |
c599d5cc | 202 | |
4d7f5e51 AC |
203 | def is_subtracted_line(line): |
204 | """Returns TRUE if the line in question has been removed.""" | |
205 | return __regex_subtracted_line.search(line) is not None | |
206 | ||
fb9410d8 | 207 | |
c599d5cc AC |
208 | def is_added_line(line): |
209 | """Returns TRUE if the line in question is an added line. | |
210 | """ | |
fb9410d8 AC |
211 | global checking_file |
212 | return __regex_added_line.search(line) is not None or checking_file | |
213 | ||
214 | ||
215 | def added_line(line): | |
216 | """Returns the line formatted properly by removing diff syntax""" | |
217 | global checking_file | |
218 | if not checking_file: | |
219 | return line[1:] | |
220 | return line | |
c599d5cc AC |
221 | |
222 | ||
223 | def leading_whitespace_is_spaces(line): | |
224 | """Returns TRUE if the leading whitespace in added lines is spaces | |
225 | """ | |
226 | if skip_leading_whitespace_check: | |
227 | return True | |
c61e93d6 DDP |
228 | if (__regex_leading_with_whitespace_at_all.search(line) is not None and |
229 | __regex_single_line_feed.search(line) is None): | |
c599d5cc | 230 | return __regex_leading_with_spaces.search(line) is not None |
c61e93d6 | 231 | |
c599d5cc AC |
232 | return True |
233 | ||
234 | ||
235 | def trailing_whitespace_or_crlf(line): | |
236 | """Returns TRUE if the trailing characters is whitespace | |
237 | """ | |
238 | if skip_trailing_whitespace_check: | |
239 | return False | |
c61e93d6 DDP |
240 | return (__regex_trailing_whitespace.search(line) is not None and |
241 | __regex_single_line_feed.search(line) is None) | |
c599d5cc AC |
242 | |
243 | ||
244 | def if_and_for_whitespace_checks(line): | |
245 | """Return TRUE if there is appropriate whitespace after if, for, while | |
246 | """ | |
247 | if skip_block_whitespace_check: | |
248 | return True | |
249 | if (__regex_for_if_missing_whitespace.search(line) is not None or | |
250 | __regex_for_if_too_much_whitespace.search(line) is not None or | |
251 | __regex_for_if_parens_whitespace.search(line)): | |
252 | return False | |
253 | return True | |
254 | ||
255 | ||
30c7ffd5 AC |
256 | def if_and_for_end_with_bracket_check(line): |
257 | """Return TRUE if there is not a bracket at the end of an if, for, while | |
258 | block which fits on a single line ie: 'if (foo)'""" | |
259 | ||
260 | def balanced_parens(line): | |
261 | """This is a rather naive counter - it won't deal with quotes""" | |
262 | balance = 0 | |
263 | for letter in line: | |
973496b9 | 264 | if letter == '(': |
30c7ffd5 | 265 | balance += 1 |
973496b9 | 266 | elif letter == ')': |
30c7ffd5 | 267 | balance -= 1 |
973496b9 | 268 | return balance == 0 |
30c7ffd5 AC |
269 | |
270 | if __regex_is_for_if_single_line_bracket.search(line) is not None: | |
271 | if not balanced_parens(line): | |
272 | return True | |
16770c6d BS |
273 | |
274 | if __regex_ends_with_bracket.search(line) is None and \ | |
275 | __regex_if_macros.match(line) is None: | |
30c7ffd5 | 276 | return False |
3f9e248f JS |
277 | if __regex_conditional_else_bracing.match(line) is not None: |
278 | return False | |
279 | if __regex_conditional_else_bracing2.match(line) is not None: | |
280 | return False | |
30c7ffd5 AC |
281 | return True |
282 | ||
283 | ||
6fc2799e JS |
284 | def pointer_whitespace_check(line): |
285 | """Return TRUE if there is no space between a pointer name and the | |
286 | asterisk that denotes this is a apionter type, ie: 'struct foo*'""" | |
287 | return __regex_ptr_declaration_missing_whitespace.search(line) is not None | |
288 | ||
289 | ||
dc497e36 IM |
290 | def cast_whitespace_check(line): |
291 | """Return TRUE if there is no space between the '()' used in a cast and | |
292 | the expression whose type is cast, i.e.: '(void *)foo'""" | |
293 | return __regex_cast_missing_whitespace.search(line) is not None | |
294 | ||
295 | ||
517f04ad AC |
296 | def line_length_check(line): |
297 | """Return TRUE if the line length is too long""" | |
298 | if len(line) > 79: | |
860ffe70 BP |
299 | print_warning("Line is %d characters long (recommended limit is 79)" |
300 | % len(line)) | |
517f04ad AC |
301 | return True |
302 | return False | |
303 | ||
304 | ||
d6ec6c10 BP |
305 | def is_comment_line(line): |
306 | """Returns TRUE if the current line is part of a block comment.""" | |
307 | return __regex_is_comment_line.match(line) is not None | |
308 | ||
8df9a0c4 | 309 | |
4e99b70d JP |
310 | def has_comment(line): |
311 | """Returns TRUE if the current line contains a comment or is part of | |
312 | a block comment.""" | |
313 | return __regex_has_comment.match(line) is not None | |
d6ec6c10 | 314 | |
8df9a0c4 | 315 | |
b48aa143 IM |
316 | def has_c99_comment(line): |
317 | """Returns TRUE if the current line contains C99 style comment (//).""" | |
318 | return __regex_has_c99_comment.match(line) is not None | |
319 | ||
320 | ||
12f62e9d JS |
321 | def trailing_operator(line): |
322 | """Returns TRUE if the current line ends with an operatorsuch as ? or :""" | |
323 | return __regex_trailing_operator.match(line) is not None | |
324 | ||
8df9a0c4 | 325 | |
4e99b70d JP |
326 | def has_xxx_mark(line): |
327 | """Returns TRUE if the current line contains 'xxx'.""" | |
328 | return __regex_has_xxx_mark.match(line) is not None | |
329 | ||
12f62e9d | 330 | |
999c7773 | 331 | def filter_comments(current_line, keep=False): |
8361e647 AC |
332 | """remove all of the c-style comments in a line""" |
333 | STATE_NORMAL = 0 | |
334 | STATE_COMMENT_SLASH = 1 | |
335 | STATE_COMMENT_CONTENTS = 3 | |
336 | STATE_COMMENT_END_SLASH = 4 | |
337 | ||
338 | state = STATE_NORMAL | |
339 | sanitized_line = '' | |
340 | check_state = STATE_NORMAL | |
341 | only_whitespace = True | |
342 | ||
999c7773 AC |
343 | if keep: |
344 | check_state = STATE_COMMENT_CONTENTS | |
345 | ||
8361e647 AC |
346 | for c in current_line: |
347 | if c == '/': | |
348 | if state == STATE_NORMAL: | |
349 | state = STATE_COMMENT_SLASH | |
350 | elif state == STATE_COMMENT_SLASH: | |
351 | # This is for c++ style comments. We will warn later | |
352 | return sanitized_line[:1] | |
353 | elif state == STATE_COMMENT_END_SLASH: | |
354 | c = '' | |
355 | state = STATE_NORMAL | |
356 | elif c == '*': | |
357 | if only_whitespace: | |
358 | # just assume this is a continuation from the previous line | |
359 | # as a comment | |
360 | state = STATE_COMMENT_END_SLASH | |
361 | elif state == STATE_COMMENT_SLASH: | |
362 | state = STATE_COMMENT_CONTENTS | |
363 | sanitized_line = sanitized_line[:-1] | |
364 | elif state == STATE_COMMENT_CONTENTS: | |
365 | state = STATE_COMMENT_END_SLASH | |
366 | elif state == STATE_COMMENT_END_SLASH: | |
367 | # Need to re-introduce the star from the previous state, since | |
368 | # it may have been clipped by the state check below. | |
369 | c = '*' + c | |
370 | state = STATE_COMMENT_CONTENTS | |
371 | elif state == STATE_COMMENT_SLASH: | |
372 | # Need to re-introduce the slash from the previous state, since | |
373 | # it may have been clipped by the state check below. | |
374 | c = '/' + c | |
375 | state = STATE_NORMAL | |
376 | ||
377 | if state != check_state: | |
378 | c = '' | |
379 | ||
380 | if not c.isspace(): | |
381 | only_whitespace = False | |
382 | ||
383 | sanitized_line += c | |
384 | ||
385 | return sanitized_line | |
386 | ||
387 | ||
9802fafa IM |
388 | def check_spelling(line, comment): |
389 | if not spell_check_dict or not spellcheck: | |
999c7773 AC |
390 | return False |
391 | ||
9802fafa IM |
392 | words = filter_comments(line, True) if comment else line |
393 | words = words.replace(':', ' ').split(' ') | |
394 | ||
395 | for word in words: | |
999c7773 AC |
396 | skip = False |
397 | strword = re.subn(r'\W+', '', word)[0].replace(',', '') | |
98a411b3 IM |
398 | if (len(strword) |
399 | and not spell_check_dict.check(strword.lower()) | |
400 | and not spell_check_dict.check(word.lower())): | |
999c7773 AC |
401 | if any([check_char in word |
402 | for check_char in ['=', '(', '-', '_', '/', '\'']]): | |
403 | skip = True | |
404 | ||
405 | # special case the '.' | |
406 | if '.' in word and not word.endswith('.'): | |
407 | skip = True | |
408 | ||
409 | # skip proper nouns and references to macros | |
410 | if strword.isupper() or (strword[0].isupper() and | |
411 | strword[1:].islower()): | |
412 | skip = True | |
413 | ||
37c6dfab IM |
414 | # skip words containing numbers |
415 | if any(check_char.isdigit() for check_char in strword): | |
999c7773 AC |
416 | skip = True |
417 | ||
418 | if not skip: | |
860ffe70 BP |
419 | print_warning("Check for spelling mistakes (e.g. \"%s\")" |
420 | % strword) | |
999c7773 AC |
421 | return True |
422 | ||
423 | return False | |
424 | ||
425 | ||
8503a516 FL |
426 | def __check_doc_is_listed(text, doctype, docdir, docfile): |
427 | if doctype == 'rst': | |
428 | beginre = re.compile(r'\+\+\+.*{}/index.rst'.format(docdir)) | |
429 | docre = re.compile(r'\n\+.*{}'.format(docfile.replace('.rst', ''))) | |
430 | elif doctype == 'automake': | |
431 | beginre = re.compile(r'\+\+\+.*Documentation/automake.mk') | |
432 | docre = re.compile(r'\n\+\t{}/{}'.format(docdir, docfile)) | |
433 | else: | |
434 | raise NotImplementedError("Invalid doctype: {}".format(doctype)) | |
435 | ||
436 | res = beginre.search(text) | |
437 | if res is None: | |
438 | return True | |
439 | ||
440 | hunkstart = res.span()[1] | |
441 | hunkre = re.compile(r'\n(---|\+\+\+) (\S+)') | |
442 | res = hunkre.search(text[hunkstart:]) | |
443 | if res is None: | |
444 | hunkend = len(text) | |
445 | else: | |
446 | hunkend = hunkstart + res.span()[0] | |
447 | ||
448 | hunk = text[hunkstart:hunkend] | |
449 | # find if the file is being added. | |
450 | if docre.search(hunk) is not None: | |
451 | return False | |
452 | ||
453 | return True | |
454 | ||
455 | ||
456 | def __check_new_docs(text, doctype): | |
457 | """Check if the documentation is listed properly. If doctype is 'rst' then | |
458 | the index.rst is checked. If the doctype is 'automake' then automake.mk | |
459 | is checked. Returns TRUE if the new file is not listed.""" | |
460 | failed = False | |
461 | new_docs = __regex_added_doc_rst.findall(text) | |
462 | for doc in new_docs: | |
463 | docpathname = doc.split(' ')[2] | |
464 | gitdocdir, docfile = os.path.split(docpathname.rstrip('\n')) | |
465 | if docfile == "index.rst": | |
466 | continue | |
467 | ||
468 | if gitdocdir.startswith('a/'): | |
469 | docdir = gitdocdir.replace('a/', '', 1) | |
470 | else: | |
471 | docdir = gitdocdir | |
472 | ||
473 | if __check_doc_is_listed(text, doctype, docdir, docfile): | |
474 | if doctype == 'rst': | |
475 | print_warning("New doc {} not listed in {}/index.rst".format( | |
476 | docfile, docdir)) | |
477 | elif doctype == 'automake': | |
478 | print_warning("New doc {} not listed in " | |
479 | "Documentation/automake.mk".format(docfile)) | |
480 | else: | |
481 | raise NotImplementedError("Invalid doctype: {}".format( | |
482 | doctype)) | |
483 | ||
484 | failed = True | |
485 | ||
486 | return failed | |
487 | ||
488 | ||
489 | def check_doc_docs_automake(text): | |
490 | return __check_new_docs(text, 'automake') | |
491 | ||
492 | ||
493 | def check_new_docs_index(text): | |
494 | return __check_new_docs(text, 'rst') | |
495 | ||
496 | ||
a9e5ac0f BS |
497 | def empty_return_with_brace(line): |
498 | """Returns TRUE if a function contains a return; followed | |
499 | by one or more line feeds and terminates with a '}' | |
500 | at start of line""" | |
501 | ||
502 | def empty_return(line): | |
503 | """Returns TRUE if a function has a 'return;'""" | |
504 | return __regex_empty_return.match(line) is not None | |
505 | ||
506 | global empty_return_check_state | |
507 | if empty_return_check_state == RETURN_CHECK_INITIAL_STATE \ | |
508 | and empty_return(line): | |
509 | empty_return_check_state = RETURN_CHECK_STATE_WITH_RETURN | |
510 | elif empty_return_check_state == RETURN_CHECK_STATE_WITH_RETURN \ | |
511 | and (re.match(r'^}$', line) or len(line) == 0): | |
512 | if re.match('^}$', line): | |
513 | empty_return_check_state = RETURN_CHECK_AWAITING_BRACE | |
514 | else: | |
515 | empty_return_check_state = RETURN_CHECK_INITIAL_STATE | |
516 | ||
517 | if empty_return_check_state == RETURN_CHECK_AWAITING_BRACE: | |
518 | empty_return_check_state = RETURN_CHECK_INITIAL_STATE | |
519 | return True | |
520 | ||
521 | return False | |
522 | ||
523 | ||
8503a516 FL |
524 | file_checks = [ |
525 | {'regex': __regex_added_doc_rst, | |
526 | 'check': check_new_docs_index}, | |
527 | {'regex': __regex_added_doc_rst, | |
528 | 'check': check_doc_docs_automake} | |
529 | ] | |
530 | ||
517f04ad AC |
531 | checks = [ |
532 | {'regex': None, | |
8205fbc8 | 533 | 'match_name': lambda x: not line_length_ignore_list.search(x), |
860ffe70 | 534 | 'check': lambda x: line_length_check(x)}, |
907848bd | 535 | |
ae9c0985 | 536 | {'regex': None, |
8205fbc8 | 537 | 'match_name': lambda x: not leading_whitespace_ignore_list.search(x), |
907848bd | 538 | 'check': lambda x: not leading_whitespace_is_spaces(x), |
ebba2af6 | 539 | 'print': lambda: print_warning("Line has non-spaces leading whitespace")}, |
907848bd AC |
540 | |
541 | {'regex': None, 'match_name': None, | |
542 | 'check': lambda x: trailing_whitespace_or_crlf(x), | |
ebba2af6 | 543 | 'print': lambda: print_warning("Line has trailing whitespace")}, |
907848bd | 544 | |
145a7e88 | 545 | {'regex': r'(\.c|\.h)(\.in)?$', 'match_name': None, |
d6ec6c10 | 546 | 'prereq': lambda x: not is_comment_line(x), |
907848bd | 547 | 'check': lambda x: not if_and_for_whitespace_checks(x), |
ebba2af6 | 548 | 'print': lambda: print_error("Improper whitespace around control block")}, |
907848bd | 549 | |
145a7e88 | 550 | {'regex': r'(\.c|\.h)(\.in)?$', 'match_name': None, |
d6ec6c10 | 551 | 'prereq': lambda x: not is_comment_line(x), |
907848bd | 552 | 'check': lambda x: not if_and_for_end_with_bracket_check(x), |
ebba2af6 | 553 | 'print': lambda: print_error("Inappropriate bracing around statement")}, |
907848bd | 554 | |
145a7e88 | 555 | {'regex': r'(\.c|\.h)(\.in)?$', 'match_name': None, |
d6ec6c10 | 556 | 'prereq': lambda x: not is_comment_line(x), |
907848bd AC |
557 | 'check': lambda x: pointer_whitespace_check(x), |
558 | 'print': | |
12f62e9d JS |
559 | lambda: print_error("Inappropriate spacing in pointer declaration")}, |
560 | ||
dc497e36 IM |
561 | {'regex': r'(\.c|\.h)(\.in)?$', 'match_name': None, |
562 | 'prereq': lambda x: not is_comment_line(x), | |
563 | 'check': lambda x: cast_whitespace_check(x), | |
564 | 'print': | |
565 | lambda: print_error("Inappropriate spacing around cast")}, | |
566 | ||
145a7e88 | 567 | {'regex': r'(\.c|\.h)(\.in)?$', 'match_name': None, |
12f62e9d JS |
568 | 'prereq': lambda x: not is_comment_line(x), |
569 | 'check': lambda x: trailing_operator(x), | |
570 | 'print': | |
571 | lambda: print_error("Line has '?' or ':' operator at end of line")}, | |
4e99b70d | 572 | |
145a7e88 | 573 | {'regex': r'(\.c|\.h)(\.in)?$', 'match_name': None, |
4e99b70d JP |
574 | 'prereq': lambda x: has_comment(x), |
575 | 'check': lambda x: has_xxx_mark(x), | |
576 | 'print': lambda: print_warning("Comment with 'xxx' marker")}, | |
999c7773 | 577 | |
b48aa143 IM |
578 | {'regex': r'(\.c|\.h)(\.in)?$', 'match_name': None, |
579 | 'prereq': lambda x: not is_comment_line(x), | |
580 | 'check': lambda x: has_c99_comment(x), | |
581 | 'print': lambda: print_error("C99 style comment")}, | |
582 | ||
145a7e88 | 583 | {'regex': r'(\.c|\.h)(\.in)?$', 'match_name': None, |
999c7773 | 584 | 'prereq': lambda x: has_comment(x), |
9802fafa | 585 | 'check': lambda x: check_spelling(x, True)}, |
a9e5ac0f | 586 | |
145a7e88 | 587 | {'regex': r'(\.c|\.h)(\.in)?$', 'match_name': None, |
a9e5ac0f BS |
588 | 'check': lambda x: empty_return_with_brace(x), |
589 | 'interim_line': True, | |
590 | 'print': | |
591 | lambda: print_warning("Empty return followed by brace, consider omitting") | |
592 | }, | |
517f04ad AC |
593 | ] |
594 | ||
595 | ||
b95d82bf | 596 | def regex_function_factory(func_name): |
6ecf961e | 597 | regex = re.compile(r'\b%s\([^)]*\)' % func_name) |
b95d82bf JS |
598 | return lambda x: regex.search(x) is not None |
599 | ||
600 | ||
601 | def regex_error_factory(description): | |
602 | return lambda: print_error(description) | |
603 | ||
604 | ||
605 | std_functions = [ | |
606 | ('malloc', 'Use xmalloc() in place of malloc()'), | |
607 | ('calloc', 'Use xcalloc() in place of calloc()'), | |
608 | ('realloc', 'Use xrealloc() in place of realloc()'), | |
609 | ('strdup', 'Use xstrdup() in place of strdup()'), | |
610 | ('asprintf', 'Use xasprintf() in place of asprintf()'), | |
611 | ('vasprintf', 'Use xvasprintf() in place of vasprintf()'), | |
612 | ('strcpy', 'Use ovs_strlcpy() in place of strcpy()'), | |
613 | ('strlcpy', 'Use ovs_strlcpy() in place of strlcpy()'), | |
614 | ('strncpy', 'Use ovs_strzcpy() in place of strncpy()'), | |
615 | ('strerror', 'Use ovs_strerror() in place of strerror()'), | |
616 | ('sleep', 'Use xsleep() in place of sleep()'), | |
617 | ('abort', 'Use ovs_abort() in place of abort()'), | |
d9d849fe | 618 | ('assert', 'Use ovs_assert() in place of assert()'), |
b95d82bf JS |
619 | ('error', 'Use ovs_error() in place of error()'), |
620 | ] | |
621 | checks += [ | |
145a7e88 | 622 | {'regex': r'(\.c|\.h)(\.in)?$', |
b95d82bf | 623 | 'match_name': None, |
d6ec6c10 | 624 | 'prereq': lambda x: not is_comment_line(x), |
b95d82bf JS |
625 | 'check': regex_function_factory(function_name), |
626 | 'print': regex_error_factory(description)} | |
627 | for (function_name, description) in std_functions] | |
628 | ||
629 | ||
0d7b16da JS |
630 | def regex_operator_factory(operator): |
631 | regex = re.compile(r'^[^#][^"\']*[^ "]%s[^ "\'][^"]*' % operator) | |
8361e647 | 632 | return lambda x: regex.search(filter_comments(x)) is not None |
0d7b16da JS |
633 | |
634 | ||
635 | infix_operators = \ | |
bbb2cb20 | 636 | [re.escape(op) for op in ['%', '<<', '>>', '<=', '>=', '==', '!=', |
0d7b16da JS |
637 | '^', '|', '&&', '||', '?:', '=', '+=', '-=', '*=', '/=', '%=', |
638 | '&=', '^=', '|=', '<<=', '>>=']] \ | |
9307fc46 IM |
639 | + [r'[^<" ]<[^=" ]', |
640 | r'[^\->" ]>[^=" ]', | |
641 | r'[^ !()/"]\*[^/]', | |
642 | r'[^ !&()"]&', | |
643 | r'[^" +(]\+[^"+;]', | |
644 | r'[^" \-(]\-[^"\->;]', | |
645 | r'[^" <>=!^|+\-*/%&]=[^"=]', | |
646 | r'[^* ]/[^* ]'] | |
0d7b16da | 647 | checks += [ |
145a7e88 | 648 | {'regex': r'(\.c|\.h)(\.in)?$', 'match_name': None, |
0d7b16da JS |
649 | 'prereq': lambda x: not is_comment_line(x), |
650 | 'check': regex_operator_factory(operator), | |
651 | 'print': lambda: print_warning("Line lacks whitespace around operator")} | |
652 | for operator in infix_operators] | |
653 | ||
654 | ||
517f04ad AC |
655 | def get_file_type_checks(filename): |
656 | """Returns the list of checks for a file based on matching the filename | |
657 | against regex.""" | |
658 | global checks | |
659 | checkList = [] | |
660 | for check in checks: | |
661 | if check['regex'] is None and check['match_name'] is None: | |
662 | checkList.append(check) | |
663 | if check['regex'] is not None and \ | |
664 | re.compile(check['regex']).search(filename) is not None: | |
665 | checkList.append(check) | |
666 | elif check['match_name'] is not None and check['match_name'](filename): | |
667 | checkList.append(check) | |
668 | return checkList | |
669 | ||
670 | ||
671 | def run_checks(current_file, line, lineno): | |
672 | """Runs the various checks for the particular line. This will take | |
673 | filename into account.""" | |
ebba2af6 | 674 | global checking_file, total_line |
a84a1edb | 675 | print_line = False |
517f04ad | 676 | for check in get_file_type_checks(current_file): |
d6ec6c10 BP |
677 | if 'prereq' in check and not check['prereq'](line): |
678 | continue | |
517f04ad | 679 | if check['check'](line): |
860ffe70 BP |
680 | if 'print' in check: |
681 | check['print']() | |
a84a1edb AC |
682 | print_line = True |
683 | ||
684 | if print_line: | |
ebba2af6 AC |
685 | if checking_file: |
686 | print("%s:%d:" % (current_file, lineno)) | |
687 | else: | |
688 | print("#%d FILE: %s:%d:" % (total_line, current_file, lineno)) | |
689 | print("%s\n" % line) | |
517f04ad AC |
690 | |
691 | ||
a9e5ac0f BS |
692 | def interim_line_check(current_file, line, lineno): |
693 | """Runs the various checks for the particular interim line. This will | |
694 | take filename into account, and will check for the 'interim_line' | |
695 | key before running the check.""" | |
696 | global checking_file, total_line | |
697 | print_line = False | |
698 | for check in get_file_type_checks(current_file): | |
699 | if 'prereq' in check and not check['prereq'](line): | |
700 | continue | |
701 | if 'interim_line' in check and check['interim_line']: | |
702 | if check['check'](line): | |
703 | if 'print' in check: | |
704 | check['print']() | |
705 | print_line = True | |
706 | ||
707 | if print_line: | |
708 | if checking_file: | |
709 | print("%s:%d:" % (current_file, lineno)) | |
710 | else: | |
711 | print("#%d FILE: %s:%d:" % (total_line, current_file, lineno)) | |
712 | print("%s\n" % line) | |
713 | ||
714 | ||
8503a516 FL |
715 | def run_file_checks(text): |
716 | """Runs the various checks for the text.""" | |
717 | for check in file_checks: | |
718 | if check['regex'].search(text) is not None: | |
719 | check['check'](text) | |
720 | ||
721 | ||
3267343a | 722 | def ovs_checkpatch_parse(text, filename, author=None, committer=None): |
a9e5ac0f BS |
723 | global print_file_name, total_line, checking_file, \ |
724 | empty_return_check_state | |
6ed80686 AC |
725 | |
726 | PARSE_STATE_HEADING = 0 | |
727 | PARSE_STATE_DIFF_HEADER = 1 | |
728 | PARSE_STATE_CHANGE_BODY = 2 | |
729 | ||
c599d5cc AC |
730 | lineno = 0 |
731 | signatures = [] | |
732 | co_authors = [] | |
733 | parse = 0 | |
95bd35d3 | 734 | current_file = filename if checking_file else '' |
6982ee96 | 735 | previous_file = '' |
128b46a0 | 736 | seppatch = re.compile(r'^---([\w]*| \S+)$') |
145a7e88 | 737 | hunks = re.compile(r'^(---|\+\+\+) (\S+)') |
4d7f5e51 AC |
738 | hunk_differences = re.compile( |
739 | r'^@@ ([0-9-+]+),([0-9-+]+) ([0-9-+]+),([0-9-+]+) @@') | |
3267343a BP |
740 | is_author = re.compile(r'^(Author|From): (.*)$', re.I | re.M | re.S) |
741 | is_committer = re.compile(r'^(Commit: )(.*)$', re.I | re.M | re.S) | |
3ccb8899 | 742 | is_signature = re.compile(r'^(Signed-off-by: )(.*)$', |
c599d5cc | 743 | re.I | re.M | re.S) |
3ccb8899 | 744 | is_co_author = re.compile(r'^(Co-authored-by: )(.*)$', |
c599d5cc | 745 | re.I | re.M | re.S) |
bc03d850 IM |
746 | is_gerrit_change_id = re.compile(r'(\s*(change-id: )(.*))$', |
747 | re.I | re.M | re.S) | |
c599d5cc | 748 | |
81c2316f IM |
749 | reset_counters() |
750 | ||
4bee6d8b | 751 | for line in text.splitlines(): |
6982ee96 JS |
752 | if current_file != previous_file: |
753 | previous_file = current_file | |
6982ee96 | 754 | |
c599d5cc | 755 | lineno = lineno + 1 |
ebba2af6 | 756 | total_line = total_line + 1 |
c599d5cc AC |
757 | if len(line) <= 0: |
758 | continue | |
759 | ||
fb9410d8 | 760 | if checking_file: |
6ed80686 | 761 | parse = PARSE_STATE_CHANGE_BODY |
fb9410d8 | 762 | |
6ed80686 | 763 | if parse == PARSE_STATE_DIFF_HEADER: |
c599d5cc AC |
764 | match = hunks.match(line) |
765 | if match: | |
6ed80686 | 766 | parse = PARSE_STATE_CHANGE_BODY |
2797ff00 | 767 | current_file = match.group(2)[2:] |
3239c793 | 768 | print_file_name = current_file |
c599d5cc | 769 | continue |
6ed80686 | 770 | elif parse == PARSE_STATE_HEADING: |
128b46a0 | 771 | if seppatch.match(line): |
6ed80686 | 772 | parse = PARSE_STATE_DIFF_HEADER |
c599d5cc | 773 | if not skip_signoff_check: |
3267343a BP |
774 | |
775 | # Check that the patch has an author, that the | |
776 | # author is not among the co-authors, and that the | |
777 | # co-authors are unique. | |
778 | if not author: | |
779 | print_error("Patch lacks author.") | |
780 | continue | |
3bd2e465 BP |
781 | if " via " in author or "@openvswitch.org" in author: |
782 | print_error("Author should not be mailing list.") | |
783 | continue | |
3267343a BP |
784 | if author in co_authors: |
785 | print_error("Author should not be also be co-author.") | |
786 | continue | |
787 | if len(set(co_authors)) != len(co_authors): | |
788 | print_error("Duplicate co-author.") | |
789 | ||
790 | # Check that the author, all co-authors, and the | |
791 | # committer (if any) signed off. | |
792 | if author not in signatures: | |
793 | print_error("Author %s needs to sign off." % author) | |
794 | for ca in co_authors: | |
795 | if ca not in signatures: | |
796 | print_error("Co-author %s needs to sign off." % ca) | |
797 | break | |
798 | if (committer | |
799 | and author != committer | |
800 | and committer not in signatures): | |
801 | print_error("Committer %s needs to sign off." | |
802 | % committer) | |
803 | ||
804 | # Check for signatures that we do not expect. | |
805 | # This is only a warning because there can be, | |
806 | # rarely, a signature chain. | |
807 | # | |
808 | # If we don't have a known committer, and there is | |
809 | # a single extra sign-off, then do not warn | |
810 | # because that extra sign-off is probably the | |
811 | # committer. | |
812 | extra_sigs = [x for x in signatures | |
813 | if x not in co_authors | |
814 | and x != author | |
815 | and x != committer] | |
816 | if len(extra_sigs) > 1 or (committer and extra_sigs): | |
817 | print_warning("Unexpected sign-offs from developers " | |
818 | "who are not authors or co-authors or " | |
819 | "committers: %s" | |
820 | % ", ".join(extra_sigs)) | |
821 | elif is_committer.match(line): | |
822 | committer = is_committer.match(line).group(2) | |
823 | elif is_author.match(line): | |
824 | author = is_author.match(line).group(2) | |
c599d5cc AC |
825 | elif is_signature.match(line): |
826 | m = is_signature.match(line) | |
3ccb8899 | 827 | signatures.append(m.group(2)) |
c599d5cc AC |
828 | elif is_co_author.match(line): |
829 | m = is_co_author.match(line) | |
3ccb8899 | 830 | co_authors.append(m.group(2)) |
68a95c9c RD |
831 | elif (is_gerrit_change_id.match(line) and |
832 | not skip_gerrit_change_id_check): | |
bc03d850 IM |
833 | print_error( |
834 | "Remove Gerrit Change-Id's before submitting upstream.") | |
835 | print("%d: %s\n" % (lineno, line)) | |
9802fafa IM |
836 | elif spellcheck: |
837 | check_spelling(line, False) | |
838 | ||
6ed80686 | 839 | elif parse == PARSE_STATE_CHANGE_BODY: |
c599d5cc AC |
840 | newfile = hunks.match(line) |
841 | if newfile: | |
bbbe2fa2 | 842 | current_file = newfile.group(2)[2:] |
3239c793 | 843 | print_file_name = current_file |
c599d5cc | 844 | continue |
4d7f5e51 AC |
845 | reset_line_number = hunk_differences.match(line) |
846 | if reset_line_number: | |
a9e5ac0f | 847 | empty_return_check_state = RETURN_CHECK_INITIAL_STATE |
4d7f5e51 AC |
848 | lineno = int(reset_line_number.group(3)) |
849 | if lineno < 0: | |
850 | lineno = -1 * lineno | |
851 | lineno -= 1 | |
a9e5ac0f | 852 | |
4d7f5e51 AC |
853 | if is_subtracted_line(line): |
854 | lineno -= 1 | |
c599d5cc | 855 | continue |
fb9410d8 AC |
856 | |
857 | cmp_line = added_line(line) | |
858 | ||
a9e5ac0f BS |
859 | if not is_added_line(line): |
860 | interim_line_check(current_file, cmp_line, lineno) | |
861 | continue | |
862 | ||
c599d5cc AC |
863 | # Skip files which have /datapath in them, since they are |
864 | # linux or windows coding standards | |
2797ff00 | 865 | if current_file.startswith('datapath'): |
c599d5cc | 866 | continue |
4f74db48 JS |
867 | if current_file.startswith('include/linux'): |
868 | continue | |
7095b903 IM |
869 | # "sparse" includes could be copy-pasted from different sources |
870 | # like DPDK or Linux and could contain workarounds not suitable | |
871 | # for a common style. | |
872 | if current_file.startswith('include/sparse'): | |
873 | continue | |
334eec6a WT |
874 | if current_file.startswith('utilities/bugtool'): |
875 | continue | |
517f04ad | 876 | run_checks(current_file, cmp_line, lineno) |
8503a516 FL |
877 | |
878 | run_file_checks(text) | |
c599d5cc | 879 | if __errors or __warnings: |
241bc88a | 880 | return EXIT_FAILURE |
c599d5cc AC |
881 | return 0 |
882 | ||
883 | ||
884 | def usage(): | |
a1fccabc BP |
885 | print("""\ |
886 | Open vSwitch checkpatch.py | |
887 | Checks a patch for trivial mistakes. | |
888 | usage: | |
b90cfa86 | 889 | %s [options] [PATCH1 [PATCH2 ...] | -f SOURCE1 [SOURCE2 ...] | -1 | -2 | ...] |
a1fccabc BP |
890 | |
891 | Input options: | |
892 | -f|--check-file Arguments are source files, not patches. | |
893 | -1, -2, ... Check recent commits in this repo. | |
894 | ||
895 | Check options: | |
896 | -h|--help This help message | |
897 | -b|--skip-block-whitespace Skips the if/while/for whitespace tests | |
898 | -l|--skip-leading-whitespace Skips the leading whitespace test | |
de8fa82a | 899 | -q|--quiet Only print error and warning information |
a1fccabc | 900 | -s|--skip-signoff-lines Tolerate missing Signed-off-by line |
9802fafa IM |
901 | -S|--spellcheck Check C comments and commit-message for possible |
902 | spelling mistakes | |
68a95c9c RD |
903 | -t|--skip-trailing-whitespace Skips the trailing whitespace test |
904 | --skip-gerrit-change-id Skips the gerrit change id test""" | |
a1fccabc | 905 | % sys.argv[0]) |
c599d5cc | 906 | |
a5e9c53c | 907 | |
241bc88a | 908 | def ovs_checkpatch_print_result(): |
de8fa82a AC |
909 | global quiet, __warnings, __errors, total_line |
910 | ||
241bc88a | 911 | if __errors or __warnings: |
81c2316f IM |
912 | print("Lines checked: %d, Warnings: %d, Errors: %d\n" % |
913 | (total_line, __warnings, __errors)) | |
de8fa82a | 914 | elif not quiet: |
81c2316f IM |
915 | print("Lines checked: %d, no obvious problems found\n" % (total_line)) |
916 | ||
917 | ||
c599d5cc AC |
918 | def ovs_checkpatch_file(filename): |
919 | try: | |
920 | mail = email.message_from_file(open(filename, 'r')) | |
921 | except: | |
922 | print_error("Unable to parse file '%s'. Is it a patch?" % filename) | |
923 | return -1 | |
924 | ||
925 | for part in mail.walk(): | |
926 | if part.get_content_maintype() == 'multipart': | |
927 | continue | |
3267343a BP |
928 | result = ovs_checkpatch_parse(part.get_payload(decode=False), filename, |
929 | mail.get('Author', mail['From']), | |
930 | mail['Commit']) | |
241bc88a | 931 | ovs_checkpatch_print_result() |
7d6b834f | 932 | return result |
c599d5cc | 933 | |
884e0dfe | 934 | |
a1fccabc BP |
935 | def partition(pred, iterable): |
936 | """Returns [[trues], [falses]], where [trues] is the items in | |
937 | 'iterable' that satisfy 'pred' and [falses] is all the rest.""" | |
938 | trues = [] | |
939 | falses = [] | |
940 | for item in iterable: | |
941 | if pred(item): | |
942 | trues.append(item) | |
943 | else: | |
944 | falses.append(item) | |
945 | return trues, falses | |
946 | ||
947 | ||
c599d5cc AC |
948 | if __name__ == '__main__': |
949 | try: | |
a1fccabc BP |
950 | numeric_options, args = partition(lambda s: re.match('-[0-9]+$', s), |
951 | sys.argv[1:]) | |
952 | n_patches = int(numeric_options[-1][1:]) if numeric_options else 0 | |
953 | ||
de8fa82a | 954 | optlist, args = getopt.getopt(args, 'bhlstfSq', |
fb9410d8 AC |
955 | ["check-file", |
956 | "help", | |
c599d5cc AC |
957 | "skip-block-whitespace", |
958 | "skip-leading-whitespace", | |
959 | "skip-signoff-lines", | |
999c7773 | 960 | "skip-trailing-whitespace", |
68a95c9c | 961 | "skip-gerrit-change-id", |
9802fafa | 962 | "spellcheck", |
de8fa82a | 963 | "quiet"]) |
c599d5cc AC |
964 | except: |
965 | print("Unknown option encountered. Please rerun with -h for help.") | |
241bc88a | 966 | sys.exit(EXIT_FAILURE) |
c599d5cc AC |
967 | |
968 | for o, a in optlist: | |
969 | if o in ("-h", "--help"): | |
970 | usage() | |
971 | sys.exit(0) | |
972 | elif o in ("-b", "--skip-block-whitespace"): | |
973 | skip_block_whitespace_check = True | |
974 | elif o in ("-l", "--skip-leading-whitespace"): | |
975 | skip_leading_whitespace_check = True | |
976 | elif o in ("-s", "--skip-signoff-lines"): | |
977 | skip_signoff_check = True | |
978 | elif o in ("-t", "--skip-trailing-whitespace"): | |
979 | skip_trailing_whitespace_check = True | |
68a95c9c RD |
980 | elif o in ("--skip-gerrit-change-id"): |
981 | skip_gerrit_change_id_check = True | |
fb9410d8 AC |
982 | elif o in ("-f", "--check-file"): |
983 | checking_file = True | |
9802fafa | 984 | elif o in ("-S", "--spellcheck"): |
0fb02e8e | 985 | if not open_spell_check_dict(): |
64b90b30 | 986 | print("WARNING: The enchant library isn't available.") |
999c7773 AC |
987 | print(" Please install python enchant.") |
988 | else: | |
9802fafa | 989 | spellcheck = True |
de8fa82a AC |
990 | elif o in ("-q", "--quiet"): |
991 | quiet = True | |
c599d5cc AC |
992 | else: |
993 | print("Unknown option '%s'" % o) | |
241bc88a | 994 | sys.exit(EXIT_FAILURE) |
ebba2af6 AC |
995 | |
996 | if sys.stdout.isatty(): | |
997 | colors = True | |
998 | ||
a1fccabc BP |
999 | if n_patches: |
1000 | status = 0 | |
d962bad2 IM |
1001 | |
1002 | git_log = 'git log --no-color --no-merges --pretty=format:"%H %s" ' | |
1003 | with os.popen(git_log + '-%d' % n_patches, 'r') as f: | |
1004 | commits = f.read().split("\n") | |
1005 | ||
057653ab | 1006 | for i in reversed(range(0, n_patches)): |
d962bad2 | 1007 | revision, name = commits[i].split(" ", 1) |
3267343a BP |
1008 | f = os.popen('''git format-patch -1 --stdout --pretty=format:"\ |
1009 | Author: %an <%ae> | |
1010 | Commit: %cn <%ce> | |
1011 | Subject: %s | |
1012 | ||
1013 | %b" ''' + revision, 'r') | |
a1fccabc BP |
1014 | patch = f.read() |
1015 | f.close() | |
1016 | ||
de8fa82a AC |
1017 | if not quiet: |
1018 | print('== Checking %s ("%s") ==' % (revision[0:12], name)) | |
81c2316f | 1019 | result = ovs_checkpatch_parse(patch, revision) |
241bc88a | 1020 | ovs_checkpatch_print_result() |
81c2316f | 1021 | if result: |
241bc88a | 1022 | status = EXIT_FAILURE |
a1fccabc BP |
1023 | sys.exit(status) |
1024 | ||
b90cfa86 | 1025 | if not args: |
c599d5cc AC |
1026 | if sys.stdin.isatty(): |
1027 | usage() | |
241bc88a | 1028 | sys.exit(EXIT_FAILURE) |
81c2316f | 1029 | result = ovs_checkpatch_parse(sys.stdin.read(), '-') |
241bc88a | 1030 | ovs_checkpatch_print_result() |
81c2316f | 1031 | sys.exit(result) |
b90cfa86 IM |
1032 | |
1033 | status = 0 | |
1034 | for filename in args: | |
de8fa82a AC |
1035 | if not quiet: |
1036 | print('== Checking "%s" ==' % filename) | |
b90cfa86 IM |
1037 | result = ovs_checkpatch_file(filename) |
1038 | if result: | |
241bc88a | 1039 | status = EXIT_FAILURE |
b90cfa86 | 1040 | sys.exit(status) |