]>
Commit | Line | Data |
---|---|---|
c599d5cc | 1 | #!/usr/bin/env python |
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 | |
999c7773 | 35 | spellcheck_comments = 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', | |
87 | 'recirculation'] | |
88 | ||
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) | |
93 | ||
94 | return True | |
95 | except: | |
96 | return False | |
3239c793 AC |
97 | |
98 | ||
ebba2af6 AC |
99 | def get_color_end(): |
100 | global colors | |
101 | if colors: | |
102 | return "\033[00m" | |
103 | return "" | |
c599d5cc AC |
104 | |
105 | ||
ebba2af6 AC |
106 | def get_red_begin(): |
107 | global colors | |
108 | if colors: | |
109 | return "\033[91m" | |
110 | return "" | |
111 | ||
112 | ||
113 | def get_yellow_begin(): | |
114 | global colors | |
115 | if colors: | |
116 | return "\033[93m" | |
117 | return "" | |
118 | ||
119 | ||
120 | def print_error(message): | |
c599d5cc | 121 | global __errors |
ebba2af6 | 122 | print("%sERROR%s: %s" % (get_red_begin(), get_color_end(), message)) |
c599d5cc AC |
123 | |
124 | __errors = __errors + 1 | |
125 | ||
126 | ||
ebba2af6 | 127 | def print_warning(message): |
c599d5cc | 128 | global __warnings |
ebba2af6 | 129 | print("%sWARNING%s: %s" % (get_yellow_begin(), get_color_end(), message)) |
c599d5cc AC |
130 | |
131 | __warnings = __warnings + 1 | |
132 | ||
133 | ||
81c2316f | 134 | def reset_counters(): |
f61d40de | 135 | global __errors, __warnings, total_line |
81c2316f IM |
136 | |
137 | __errors = 0 | |
138 | __warnings = 0 | |
f61d40de | 139 | total_line = 0 |
81c2316f IM |
140 | |
141 | ||
b537de13 BP |
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. | |
144 | # | |
145 | # 'do' almost qualifies but it's also used as "do { ... } while (...);". | |
df8c04b1 | 146 | __parenthesized_constructs = 'if|for|while|switch|[_A-Z]+FOR_*EACH[_A-Z0-9]*' |
b537de13 | 147 | |
c599d5cc | 148 | __regex_added_line = re.compile(r'^\+{1,2}[^\+][\w\W]*') |
4d7f5e51 | 149 | __regex_subtracted_line = re.compile(r'^\-{1,2}[^\-][\w\W]*') |
c599d5cc AC |
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]+$') | |
c61e93d6 | 153 | __regex_single_line_feed = re.compile(r'^\f$') |
b537de13 BP |
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) | |
a1193b4d | 158 | __regex_for_if_parens_whitespace = \ |
b537de13 | 159 | re.compile(r' +(%s) \( +[\s\S]+\)' % __parenthesized_constructs) |
30c7ffd5 | 160 | __regex_is_for_if_single_line_bracket = \ |
b537de13 | 161 | re.compile(r'^ +(%s) \(.*\)' % __parenthesized_constructs) |
a1193b4d AC |
162 | __regex_ends_with_bracket = \ |
163 | re.compile(r'[^\s]\) {(\s+/\*[\s\Sa-zA-Z0-9\.,\?\*/+-]*)?$') | |
d56ec3bc | 164 | __regex_ptr_declaration_missing_whitespace = re.compile(r'[a-zA-Z0-9]\*[^*]') |
d6ec6c10 | 165 | __regex_is_comment_line = re.compile(r'^\s*(/\*|\*\s)') |
4e99b70d | 166 | __regex_has_comment = re.compile(r'.*(/\*|\*\s)') |
b48aa143 | 167 | __regex_has_c99_comment = re.compile(r'.*//.*$') |
12f62e9d | 168 | __regex_trailing_operator = re.compile(r'^[^ ]* [^ ]*[?:]$') |
3f9e248f JS |
169 | __regex_conditional_else_bracing = re.compile(r'^\s*else\s*{?$') |
170 | __regex_conditional_else_bracing2 = re.compile(r'^\s*}\selse\s*$') | |
4e99b70d | 171 | __regex_has_xxx_mark = re.compile(r'.*xxx.*', re.IGNORECASE) |
8503a516 FL |
172 | __regex_added_doc_rst = re.compile( |
173 | r'\ndiff .*Documentation/.*rst\nnew file mode') | |
a9e5ac0f | 174 | __regex_empty_return = re.compile(r'\s*return;') |
aacad868 | 175 | __regex_if_macros = re.compile(r'^ +(%s) \([\S]([\s\S]+[\S])*\) { +\\' % |
16770c6d | 176 | __parenthesized_constructs) |
c599d5cc AC |
177 | |
178 | skip_leading_whitespace_check = False | |
179 | skip_trailing_whitespace_check = False | |
180 | skip_block_whitespace_check = False | |
181 | skip_signoff_check = False | |
182 | ||
6982ee96 JS |
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. | |
185 | # | |
186 | # Python isn't checked as flake8 performs these checks during build. | |
9aef43f0 BP |
187 | line_length_blacklist = re.compile( |
188 | r'\.(am|at|etc|in|m4|mk|patch|py)$|debian/rules') | |
6982ee96 | 189 | |
ae9c0985 BP |
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. | |
9aef43f0 | 193 | leading_whitespace_blacklist = re.compile(r'\.(mk|am|at)$|debian/rules') |
ae9c0985 | 194 | |
c599d5cc | 195 | |
4d7f5e51 AC |
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 | |
199 | ||
fb9410d8 | 200 | |
c599d5cc AC |
201 | def is_added_line(line): |
202 | """Returns TRUE if the line in question is an added line. | |
203 | """ | |
fb9410d8 AC |
204 | global checking_file |
205 | return __regex_added_line.search(line) is not None or checking_file | |
206 | ||
207 | ||
208 | def added_line(line): | |
209 | """Returns the line formatted properly by removing diff syntax""" | |
210 | global checking_file | |
211 | if not checking_file: | |
212 | return line[1:] | |
213 | return line | |
c599d5cc AC |
214 | |
215 | ||
216 | def leading_whitespace_is_spaces(line): | |
217 | """Returns TRUE if the leading whitespace in added lines is spaces | |
218 | """ | |
219 | if skip_leading_whitespace_check: | |
220 | return True | |
c61e93d6 DDP |
221 | if (__regex_leading_with_whitespace_at_all.search(line) is not None and |
222 | __regex_single_line_feed.search(line) is None): | |
c599d5cc | 223 | return __regex_leading_with_spaces.search(line) is not None |
c61e93d6 | 224 | |
c599d5cc AC |
225 | return True |
226 | ||
227 | ||
228 | def trailing_whitespace_or_crlf(line): | |
229 | """Returns TRUE if the trailing characters is whitespace | |
230 | """ | |
231 | if skip_trailing_whitespace_check: | |
232 | return False | |
c61e93d6 DDP |
233 | return (__regex_trailing_whitespace.search(line) is not None and |
234 | __regex_single_line_feed.search(line) is None) | |
c599d5cc AC |
235 | |
236 | ||
237 | def if_and_for_whitespace_checks(line): | |
238 | """Return TRUE if there is appropriate whitespace after if, for, while | |
239 | """ | |
240 | if skip_block_whitespace_check: | |
241 | return True | |
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)): | |
245 | return False | |
246 | return True | |
247 | ||
248 | ||
30c7ffd5 AC |
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)'""" | |
252 | ||
253 | def balanced_parens(line): | |
254 | """This is a rather naive counter - it won't deal with quotes""" | |
255 | balance = 0 | |
256 | for letter in line: | |
973496b9 | 257 | if letter == '(': |
30c7ffd5 | 258 | balance += 1 |
973496b9 | 259 | elif letter == ')': |
30c7ffd5 | 260 | balance -= 1 |
973496b9 | 261 | return balance == 0 |
30c7ffd5 AC |
262 | |
263 | if __regex_is_for_if_single_line_bracket.search(line) is not None: | |
264 | if not balanced_parens(line): | |
265 | return True | |
16770c6d BS |
266 | |
267 | if __regex_ends_with_bracket.search(line) is None and \ | |
268 | __regex_if_macros.match(line) is None: | |
30c7ffd5 | 269 | return False |
3f9e248f JS |
270 | if __regex_conditional_else_bracing.match(line) is not None: |
271 | return False | |
272 | if __regex_conditional_else_bracing2.match(line) is not None: | |
273 | return False | |
30c7ffd5 AC |
274 | return True |
275 | ||
276 | ||
6fc2799e JS |
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 | |
281 | ||
282 | ||
517f04ad AC |
283 | def line_length_check(line): |
284 | """Return TRUE if the line length is too long""" | |
285 | if len(line) > 79: | |
860ffe70 BP |
286 | print_warning("Line is %d characters long (recommended limit is 79)" |
287 | % len(line)) | |
517f04ad AC |
288 | return True |
289 | return False | |
290 | ||
291 | ||
d6ec6c10 BP |
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 | |
295 | ||
8df9a0c4 | 296 | |
4e99b70d JP |
297 | def has_comment(line): |
298 | """Returns TRUE if the current line contains a comment or is part of | |
299 | a block comment.""" | |
300 | return __regex_has_comment.match(line) is not None | |
d6ec6c10 | 301 | |
8df9a0c4 | 302 | |
b48aa143 IM |
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 | |
306 | ||
307 | ||
12f62e9d JS |
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 | |
311 | ||
8df9a0c4 | 312 | |
4e99b70d JP |
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 | |
316 | ||
12f62e9d | 317 | |
999c7773 | 318 | def filter_comments(current_line, keep=False): |
8361e647 AC |
319 | """remove all of the c-style comments in a line""" |
320 | STATE_NORMAL = 0 | |
321 | STATE_COMMENT_SLASH = 1 | |
322 | STATE_COMMENT_CONTENTS = 3 | |
323 | STATE_COMMENT_END_SLASH = 4 | |
324 | ||
325 | state = STATE_NORMAL | |
326 | sanitized_line = '' | |
327 | check_state = STATE_NORMAL | |
328 | only_whitespace = True | |
329 | ||
999c7773 AC |
330 | if keep: |
331 | check_state = STATE_COMMENT_CONTENTS | |
332 | ||
8361e647 AC |
333 | for c in current_line: |
334 | if c == '/': | |
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: | |
341 | c = '' | |
342 | state = STATE_NORMAL | |
343 | elif c == '*': | |
344 | if only_whitespace: | |
345 | # just assume this is a continuation from the previous line | |
346 | # as a comment | |
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. | |
356 | c = '*' + c | |
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. | |
361 | c = '/' + c | |
362 | state = STATE_NORMAL | |
363 | ||
364 | if state != check_state: | |
365 | c = '' | |
366 | ||
367 | if not c.isspace(): | |
368 | only_whitespace = False | |
369 | ||
370 | sanitized_line += c | |
371 | ||
372 | return sanitized_line | |
373 | ||
374 | ||
999c7773 | 375 | def check_comment_spelling(line): |
0fb02e8e | 376 | if not spell_check_dict or not spellcheck_comments: |
999c7773 AC |
377 | return False |
378 | ||
379 | comment_words = filter_comments(line, True).replace(':', ' ').split(' ') | |
380 | for word in comment_words: | |
381 | skip = False | |
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 ['=', '(', '-', '_', '/', '\'']]): | |
386 | skip = True | |
387 | ||
388 | # special case the '.' | |
389 | if '.' in word and not word.endswith('.'): | |
390 | skip = True | |
391 | ||
392 | # skip proper nouns and references to macros | |
393 | if strword.isupper() or (strword[0].isupper() and | |
394 | strword[1:].islower()): | |
395 | skip = True | |
396 | ||
397 | # skip words that start with numbers | |
398 | if strword.startswith(tuple('0123456789')): | |
399 | skip = True | |
400 | ||
401 | if not skip: | |
860ffe70 BP |
402 | print_warning("Check for spelling mistakes (e.g. \"%s\")" |
403 | % strword) | |
999c7773 AC |
404 | return True |
405 | ||
406 | return False | |
407 | ||
408 | ||
8503a516 FL |
409 | def __check_doc_is_listed(text, doctype, docdir, docfile): |
410 | if doctype == 'rst': | |
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)) | |
416 | else: | |
417 | raise NotImplementedError("Invalid doctype: {}".format(doctype)) | |
418 | ||
419 | res = beginre.search(text) | |
420 | if res is None: | |
421 | return True | |
422 | ||
423 | hunkstart = res.span()[1] | |
424 | hunkre = re.compile(r'\n(---|\+\+\+) (\S+)') | |
425 | res = hunkre.search(text[hunkstart:]) | |
426 | if res is None: | |
427 | hunkend = len(text) | |
428 | else: | |
429 | hunkend = hunkstart + res.span()[0] | |
430 | ||
431 | hunk = text[hunkstart:hunkend] | |
432 | # find if the file is being added. | |
433 | if docre.search(hunk) is not None: | |
434 | return False | |
435 | ||
436 | return True | |
437 | ||
438 | ||
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.""" | |
443 | failed = False | |
444 | new_docs = __regex_added_doc_rst.findall(text) | |
445 | for doc in new_docs: | |
446 | docpathname = doc.split(' ')[2] | |
447 | gitdocdir, docfile = os.path.split(docpathname.rstrip('\n')) | |
448 | if docfile == "index.rst": | |
449 | continue | |
450 | ||
451 | if gitdocdir.startswith('a/'): | |
452 | docdir = gitdocdir.replace('a/', '', 1) | |
453 | else: | |
454 | docdir = gitdocdir | |
455 | ||
456 | if __check_doc_is_listed(text, doctype, docdir, docfile): | |
457 | if doctype == 'rst': | |
458 | print_warning("New doc {} not listed in {}/index.rst".format( | |
459 | docfile, docdir)) | |
460 | elif doctype == 'automake': | |
461 | print_warning("New doc {} not listed in " | |
462 | "Documentation/automake.mk".format(docfile)) | |
463 | else: | |
464 | raise NotImplementedError("Invalid doctype: {}".format( | |
465 | doctype)) | |
466 | ||
467 | failed = True | |
468 | ||
469 | return failed | |
470 | ||
471 | ||
472 | def check_doc_docs_automake(text): | |
473 | return __check_new_docs(text, 'automake') | |
474 | ||
475 | ||
476 | def check_new_docs_index(text): | |
477 | return __check_new_docs(text, 'rst') | |
478 | ||
479 | ||
a9e5ac0f BS |
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 '}' | |
483 | at start of line""" | |
484 | ||
485 | def empty_return(line): | |
486 | """Returns TRUE if a function has a 'return;'""" | |
487 | return __regex_empty_return.match(line) is not None | |
488 | ||
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 | |
497 | else: | |
498 | empty_return_check_state = RETURN_CHECK_INITIAL_STATE | |
499 | ||
500 | if empty_return_check_state == RETURN_CHECK_AWAITING_BRACE: | |
501 | empty_return_check_state = RETURN_CHECK_INITIAL_STATE | |
502 | return True | |
503 | ||
504 | return False | |
505 | ||
506 | ||
8503a516 FL |
507 | file_checks = [ |
508 | {'regex': __regex_added_doc_rst, | |
509 | 'check': check_new_docs_index}, | |
510 | {'regex': __regex_added_doc_rst, | |
511 | 'check': check_doc_docs_automake} | |
512 | ] | |
513 | ||
517f04ad AC |
514 | checks = [ |
515 | {'regex': None, | |
09b94206 | 516 | 'match_name': lambda x: not line_length_blacklist.search(x), |
860ffe70 | 517 | 'check': lambda x: line_length_check(x)}, |
907848bd | 518 | |
ae9c0985 | 519 | {'regex': None, |
09b94206 | 520 | 'match_name': lambda x: not leading_whitespace_blacklist.search(x), |
907848bd | 521 | 'check': lambda x: not leading_whitespace_is_spaces(x), |
ebba2af6 | 522 | 'print': lambda: print_warning("Line has non-spaces leading whitespace")}, |
907848bd AC |
523 | |
524 | {'regex': None, 'match_name': None, | |
525 | 'check': lambda x: trailing_whitespace_or_crlf(x), | |
ebba2af6 | 526 | 'print': lambda: print_warning("Line has trailing whitespace")}, |
907848bd | 527 | |
145a7e88 | 528 | {'regex': r'(\.c|\.h)(\.in)?$', 'match_name': None, |
d6ec6c10 | 529 | 'prereq': lambda x: not is_comment_line(x), |
907848bd | 530 | 'check': lambda x: not if_and_for_whitespace_checks(x), |
ebba2af6 | 531 | 'print': lambda: print_error("Improper whitespace around control block")}, |
907848bd | 532 | |
145a7e88 | 533 | {'regex': r'(\.c|\.h)(\.in)?$', 'match_name': None, |
d6ec6c10 | 534 | 'prereq': lambda x: not is_comment_line(x), |
907848bd | 535 | 'check': lambda x: not if_and_for_end_with_bracket_check(x), |
ebba2af6 | 536 | 'print': lambda: print_error("Inappropriate bracing around statement")}, |
907848bd | 537 | |
145a7e88 | 538 | {'regex': r'(\.c|\.h)(\.in)?$', 'match_name': None, |
d6ec6c10 | 539 | 'prereq': lambda x: not is_comment_line(x), |
907848bd AC |
540 | 'check': lambda x: pointer_whitespace_check(x), |
541 | 'print': | |
12f62e9d JS |
542 | lambda: print_error("Inappropriate spacing in pointer declaration")}, |
543 | ||
145a7e88 | 544 | {'regex': r'(\.c|\.h)(\.in)?$', 'match_name': None, |
12f62e9d JS |
545 | 'prereq': lambda x: not is_comment_line(x), |
546 | 'check': lambda x: trailing_operator(x), | |
547 | 'print': | |
548 | lambda: print_error("Line has '?' or ':' operator at end of line")}, | |
4e99b70d | 549 | |
145a7e88 | 550 | {'regex': r'(\.c|\.h)(\.in)?$', 'match_name': None, |
4e99b70d JP |
551 | 'prereq': lambda x: has_comment(x), |
552 | 'check': lambda x: has_xxx_mark(x), | |
553 | 'print': lambda: print_warning("Comment with 'xxx' marker")}, | |
999c7773 | 554 | |
b48aa143 IM |
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")}, | |
559 | ||
145a7e88 | 560 | {'regex': r'(\.c|\.h)(\.in)?$', 'match_name': None, |
999c7773 | 561 | 'prereq': lambda x: has_comment(x), |
860ffe70 | 562 | 'check': lambda x: check_comment_spelling(x)}, |
a9e5ac0f | 563 | |
145a7e88 | 564 | {'regex': r'(\.c|\.h)(\.in)?$', 'match_name': None, |
a9e5ac0f BS |
565 | 'check': lambda x: empty_return_with_brace(x), |
566 | 'interim_line': True, | |
567 | 'print': | |
568 | lambda: print_warning("Empty return followed by brace, consider omitting") | |
569 | }, | |
517f04ad AC |
570 | ] |
571 | ||
572 | ||
b95d82bf | 573 | def regex_function_factory(func_name): |
6ecf961e | 574 | regex = re.compile(r'\b%s\([^)]*\)' % func_name) |
b95d82bf JS |
575 | return lambda x: regex.search(x) is not None |
576 | ||
577 | ||
578 | def regex_error_factory(description): | |
579 | return lambda: print_error(description) | |
580 | ||
581 | ||
582 | std_functions = [ | |
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()'), | |
d9d849fe | 595 | ('assert', 'Use ovs_assert() in place of assert()'), |
b95d82bf JS |
596 | ('error', 'Use ovs_error() in place of error()'), |
597 | ] | |
598 | checks += [ | |
145a7e88 | 599 | {'regex': r'(\.c|\.h)(\.in)?$', |
b95d82bf | 600 | 'match_name': None, |
d6ec6c10 | 601 | 'prereq': lambda x: not is_comment_line(x), |
b95d82bf JS |
602 | 'check': regex_function_factory(function_name), |
603 | 'print': regex_error_factory(description)} | |
604 | for (function_name, description) in std_functions] | |
605 | ||
606 | ||
0d7b16da JS |
607 | def regex_operator_factory(operator): |
608 | regex = re.compile(r'^[^#][^"\']*[^ "]%s[^ "\'][^"]*' % operator) | |
8361e647 | 609 | return lambda x: regex.search(filter_comments(x)) is not None |
0d7b16da JS |
610 | |
611 | ||
612 | infix_operators = \ | |
bbb2cb20 | 613 | [re.escape(op) for op in ['%', '<<', '>>', '<=', '>=', '==', '!=', |
0d7b16da JS |
614 | '^', '|', '&&', '||', '?:', '=', '+=', '-=', '*=', '/=', '%=', |
615 | '&=', '^=', '|=', '<<=', '>>=']] \ | |
9307fc46 IM |
616 | + [r'[^<" ]<[^=" ]', |
617 | r'[^\->" ]>[^=" ]', | |
618 | r'[^ !()/"]\*[^/]', | |
619 | r'[^ !&()"]&', | |
620 | r'[^" +(]\+[^"+;]', | |
621 | r'[^" \-(]\-[^"\->;]', | |
622 | r'[^" <>=!^|+\-*/%&]=[^"=]', | |
623 | r'[^* ]/[^* ]'] | |
0d7b16da | 624 | checks += [ |
145a7e88 | 625 | {'regex': r'(\.c|\.h)(\.in)?$', 'match_name': None, |
0d7b16da JS |
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] | |
630 | ||
631 | ||
517f04ad AC |
632 | def get_file_type_checks(filename): |
633 | """Returns the list of checks for a file based on matching the filename | |
634 | against regex.""" | |
635 | global checks | |
636 | checkList = [] | |
637 | for check in checks: | |
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) | |
645 | return checkList | |
646 | ||
647 | ||
648 | def run_checks(current_file, line, lineno): | |
649 | """Runs the various checks for the particular line. This will take | |
650 | filename into account.""" | |
ebba2af6 | 651 | global checking_file, total_line |
a84a1edb | 652 | print_line = False |
517f04ad | 653 | for check in get_file_type_checks(current_file): |
d6ec6c10 BP |
654 | if 'prereq' in check and not check['prereq'](line): |
655 | continue | |
517f04ad | 656 | if check['check'](line): |
860ffe70 BP |
657 | if 'print' in check: |
658 | check['print']() | |
a84a1edb AC |
659 | print_line = True |
660 | ||
661 | if print_line: | |
ebba2af6 AC |
662 | if checking_file: |
663 | print("%s:%d:" % (current_file, lineno)) | |
664 | else: | |
665 | print("#%d FILE: %s:%d:" % (total_line, current_file, lineno)) | |
666 | print("%s\n" % line) | |
517f04ad AC |
667 | |
668 | ||
a9e5ac0f BS |
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 | |
674 | print_line = False | |
675 | for check in get_file_type_checks(current_file): | |
676 | if 'prereq' in check and not check['prereq'](line): | |
677 | continue | |
678 | if 'interim_line' in check and check['interim_line']: | |
679 | if check['check'](line): | |
680 | if 'print' in check: | |
681 | check['print']() | |
682 | print_line = True | |
683 | ||
684 | if print_line: | |
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) | |
690 | ||
691 | ||
8503a516 FL |
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: | |
696 | check['check'](text) | |
697 | ||
698 | ||
3267343a | 699 | def ovs_checkpatch_parse(text, filename, author=None, committer=None): |
a9e5ac0f BS |
700 | global print_file_name, total_line, checking_file, \ |
701 | empty_return_check_state | |
6ed80686 AC |
702 | |
703 | PARSE_STATE_HEADING = 0 | |
704 | PARSE_STATE_DIFF_HEADER = 1 | |
705 | PARSE_STATE_CHANGE_BODY = 2 | |
706 | ||
c599d5cc AC |
707 | lineno = 0 |
708 | signatures = [] | |
709 | co_authors = [] | |
710 | parse = 0 | |
95bd35d3 | 711 | current_file = filename if checking_file else '' |
6982ee96 | 712 | previous_file = '' |
128b46a0 | 713 | seppatch = re.compile(r'^---([\w]*| \S+)$') |
145a7e88 | 714 | hunks = re.compile(r'^(---|\+\+\+) (\S+)') |
4d7f5e51 AC |
715 | hunk_differences = re.compile( |
716 | r'^@@ ([0-9-+]+),([0-9-+]+) ([0-9-+]+),([0-9-+]+) @@') | |
3267343a BP |
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) | |
3ccb8899 | 719 | is_signature = re.compile(r'^(Signed-off-by: )(.*)$', |
c599d5cc | 720 | re.I | re.M | re.S) |
3ccb8899 | 721 | is_co_author = re.compile(r'^(Co-authored-by: )(.*)$', |
c599d5cc | 722 | re.I | re.M | re.S) |
bc03d850 IM |
723 | is_gerrit_change_id = re.compile(r'(\s*(change-id: )(.*))$', |
724 | re.I | re.M | re.S) | |
c599d5cc | 725 | |
81c2316f IM |
726 | reset_counters() |
727 | ||
4bee6d8b | 728 | for line in text.splitlines(): |
6982ee96 JS |
729 | if current_file != previous_file: |
730 | previous_file = current_file | |
6982ee96 | 731 | |
c599d5cc | 732 | lineno = lineno + 1 |
ebba2af6 | 733 | total_line = total_line + 1 |
c599d5cc AC |
734 | if len(line) <= 0: |
735 | continue | |
736 | ||
fb9410d8 | 737 | if checking_file: |
6ed80686 | 738 | parse = PARSE_STATE_CHANGE_BODY |
fb9410d8 | 739 | |
6ed80686 | 740 | if parse == PARSE_STATE_DIFF_HEADER: |
c599d5cc AC |
741 | match = hunks.match(line) |
742 | if match: | |
6ed80686 | 743 | parse = PARSE_STATE_CHANGE_BODY |
2797ff00 | 744 | current_file = match.group(2)[2:] |
3239c793 | 745 | print_file_name = current_file |
c599d5cc | 746 | continue |
6ed80686 | 747 | elif parse == PARSE_STATE_HEADING: |
128b46a0 | 748 | if seppatch.match(line): |
6ed80686 | 749 | parse = PARSE_STATE_DIFF_HEADER |
c599d5cc | 750 | if not skip_signoff_check: |
3267343a BP |
751 | |
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. | |
755 | if not author: | |
756 | print_error("Patch lacks author.") | |
757 | continue | |
3bd2e465 BP |
758 | if " via " in author or "@openvswitch.org" in author: |
759 | print_error("Author should not be mailing list.") | |
760 | continue | |
3267343a BP |
761 | if author in co_authors: |
762 | print_error("Author should not be also be co-author.") | |
763 | continue | |
764 | if len(set(co_authors)) != len(co_authors): | |
765 | print_error("Duplicate co-author.") | |
766 | ||
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) | |
774 | break | |
775 | if (committer | |
776 | and author != committer | |
777 | and committer not in signatures): | |
778 | print_error("Committer %s needs to sign off." | |
779 | % committer) | |
780 | ||
781 | # Check for signatures that we do not expect. | |
782 | # This is only a warning because there can be, | |
783 | # rarely, a signature chain. | |
784 | # | |
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 | |
788 | # committer. | |
789 | extra_sigs = [x for x in signatures | |
790 | if x not in co_authors | |
791 | and x != author | |
792 | and x != committer] | |
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 " | |
796 | "committers: %s" | |
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) | |
c599d5cc AC |
802 | elif is_signature.match(line): |
803 | m = is_signature.match(line) | |
3ccb8899 | 804 | signatures.append(m.group(2)) |
c599d5cc AC |
805 | elif is_co_author.match(line): |
806 | m = is_co_author.match(line) | |
3ccb8899 | 807 | co_authors.append(m.group(2)) |
bc03d850 IM |
808 | elif is_gerrit_change_id.match(line): |
809 | print_error( | |
810 | "Remove Gerrit Change-Id's before submitting upstream.") | |
811 | print("%d: %s\n" % (lineno, line)) | |
6ed80686 | 812 | elif parse == PARSE_STATE_CHANGE_BODY: |
c599d5cc AC |
813 | newfile = hunks.match(line) |
814 | if newfile: | |
bbbe2fa2 | 815 | current_file = newfile.group(2)[2:] |
3239c793 | 816 | print_file_name = current_file |
c599d5cc | 817 | continue |
4d7f5e51 AC |
818 | reset_line_number = hunk_differences.match(line) |
819 | if reset_line_number: | |
a9e5ac0f | 820 | empty_return_check_state = RETURN_CHECK_INITIAL_STATE |
4d7f5e51 AC |
821 | lineno = int(reset_line_number.group(3)) |
822 | if lineno < 0: | |
823 | lineno = -1 * lineno | |
824 | lineno -= 1 | |
a9e5ac0f | 825 | |
4d7f5e51 AC |
826 | if is_subtracted_line(line): |
827 | lineno -= 1 | |
c599d5cc | 828 | continue |
fb9410d8 AC |
829 | |
830 | cmp_line = added_line(line) | |
831 | ||
a9e5ac0f BS |
832 | if not is_added_line(line): |
833 | interim_line_check(current_file, cmp_line, lineno) | |
834 | continue | |
835 | ||
c599d5cc AC |
836 | # Skip files which have /datapath in them, since they are |
837 | # linux or windows coding standards | |
2797ff00 | 838 | if current_file.startswith('datapath'): |
c599d5cc | 839 | continue |
4f74db48 JS |
840 | if current_file.startswith('include/linux'): |
841 | continue | |
7095b903 IM |
842 | # "sparse" includes could be copy-pasted from different sources |
843 | # like DPDK or Linux and could contain workarounds not suitable | |
844 | # for a common style. | |
845 | if current_file.startswith('include/sparse'): | |
846 | continue | |
334eec6a WT |
847 | if current_file.startswith('utilities/bugtool'): |
848 | continue | |
517f04ad | 849 | run_checks(current_file, cmp_line, lineno) |
8503a516 FL |
850 | |
851 | run_file_checks(text) | |
c599d5cc | 852 | if __errors or __warnings: |
241bc88a | 853 | return EXIT_FAILURE |
c599d5cc AC |
854 | return 0 |
855 | ||
856 | ||
857 | def usage(): | |
a1fccabc BP |
858 | print("""\ |
859 | Open vSwitch checkpatch.py | |
860 | Checks a patch for trivial mistakes. | |
861 | usage: | |
b90cfa86 | 862 | %s [options] [PATCH1 [PATCH2 ...] | -f SOURCE1 [SOURCE2 ...] | -1 | -2 | ...] |
a1fccabc BP |
863 | |
864 | Input options: | |
865 | -f|--check-file Arguments are source files, not patches. | |
866 | -1, -2, ... Check recent commits in this repo. | |
867 | ||
868 | Check options: | |
869 | -h|--help This help message | |
870 | -b|--skip-block-whitespace Skips the if/while/for whitespace tests | |
871 | -l|--skip-leading-whitespace Skips the leading whitespace test | |
de8fa82a | 872 | -q|--quiet Only print error and warning information |
a1fccabc | 873 | -s|--skip-signoff-lines Tolerate missing Signed-off-by line |
999c7773 | 874 | -S|--spellcheck-comments Check C comments for possible spelling mistakes |
a1fccabc BP |
875 | -t|--skip-trailing-whitespace Skips the trailing whitespace test""" |
876 | % sys.argv[0]) | |
c599d5cc | 877 | |
a5e9c53c | 878 | |
241bc88a | 879 | def ovs_checkpatch_print_result(): |
de8fa82a AC |
880 | global quiet, __warnings, __errors, total_line |
881 | ||
241bc88a | 882 | if __errors or __warnings: |
81c2316f IM |
883 | print("Lines checked: %d, Warnings: %d, Errors: %d\n" % |
884 | (total_line, __warnings, __errors)) | |
de8fa82a | 885 | elif not quiet: |
81c2316f IM |
886 | print("Lines checked: %d, no obvious problems found\n" % (total_line)) |
887 | ||
888 | ||
c599d5cc AC |
889 | def ovs_checkpatch_file(filename): |
890 | try: | |
891 | mail = email.message_from_file(open(filename, 'r')) | |
892 | except: | |
893 | print_error("Unable to parse file '%s'. Is it a patch?" % filename) | |
894 | return -1 | |
895 | ||
896 | for part in mail.walk(): | |
897 | if part.get_content_maintype() == 'multipart': | |
898 | continue | |
3267343a BP |
899 | result = ovs_checkpatch_parse(part.get_payload(decode=False), filename, |
900 | mail.get('Author', mail['From']), | |
901 | mail['Commit']) | |
241bc88a | 902 | ovs_checkpatch_print_result() |
7d6b834f | 903 | return result |
c599d5cc | 904 | |
884e0dfe | 905 | |
a1fccabc BP |
906 | def partition(pred, iterable): |
907 | """Returns [[trues], [falses]], where [trues] is the items in | |
908 | 'iterable' that satisfy 'pred' and [falses] is all the rest.""" | |
909 | trues = [] | |
910 | falses = [] | |
911 | for item in iterable: | |
912 | if pred(item): | |
913 | trues.append(item) | |
914 | else: | |
915 | falses.append(item) | |
916 | return trues, falses | |
917 | ||
918 | ||
c599d5cc AC |
919 | if __name__ == '__main__': |
920 | try: | |
a1fccabc BP |
921 | numeric_options, args = partition(lambda s: re.match('-[0-9]+$', s), |
922 | sys.argv[1:]) | |
923 | n_patches = int(numeric_options[-1][1:]) if numeric_options else 0 | |
924 | ||
de8fa82a | 925 | optlist, args = getopt.getopt(args, 'bhlstfSq', |
fb9410d8 AC |
926 | ["check-file", |
927 | "help", | |
c599d5cc AC |
928 | "skip-block-whitespace", |
929 | "skip-leading-whitespace", | |
930 | "skip-signoff-lines", | |
999c7773 | 931 | "skip-trailing-whitespace", |
de8fa82a AC |
932 | "spellcheck-comments", |
933 | "quiet"]) | |
c599d5cc AC |
934 | except: |
935 | print("Unknown option encountered. Please rerun with -h for help.") | |
241bc88a | 936 | sys.exit(EXIT_FAILURE) |
c599d5cc AC |
937 | |
938 | for o, a in optlist: | |
939 | if o in ("-h", "--help"): | |
940 | usage() | |
941 | sys.exit(0) | |
942 | elif o in ("-b", "--skip-block-whitespace"): | |
943 | skip_block_whitespace_check = True | |
944 | elif o in ("-l", "--skip-leading-whitespace"): | |
945 | skip_leading_whitespace_check = True | |
946 | elif o in ("-s", "--skip-signoff-lines"): | |
947 | skip_signoff_check = True | |
948 | elif o in ("-t", "--skip-trailing-whitespace"): | |
949 | skip_trailing_whitespace_check = True | |
fb9410d8 AC |
950 | elif o in ("-f", "--check-file"): |
951 | checking_file = True | |
999c7773 | 952 | elif o in ("-S", "--spellcheck-comments"): |
0fb02e8e | 953 | if not open_spell_check_dict(): |
64b90b30 | 954 | print("WARNING: The enchant library isn't available.") |
999c7773 AC |
955 | print(" Please install python enchant.") |
956 | else: | |
957 | spellcheck_comments = True | |
de8fa82a AC |
958 | elif o in ("-q", "--quiet"): |
959 | quiet = True | |
c599d5cc AC |
960 | else: |
961 | print("Unknown option '%s'" % o) | |
241bc88a | 962 | sys.exit(EXIT_FAILURE) |
ebba2af6 AC |
963 | |
964 | if sys.stdout.isatty(): | |
965 | colors = True | |
966 | ||
a1fccabc BP |
967 | if n_patches: |
968 | status = 0 | |
d962bad2 IM |
969 | |
970 | git_log = 'git log --no-color --no-merges --pretty=format:"%H %s" ' | |
971 | with os.popen(git_log + '-%d' % n_patches, 'r') as f: | |
972 | commits = f.read().split("\n") | |
973 | ||
057653ab | 974 | for i in reversed(range(0, n_patches)): |
d962bad2 | 975 | revision, name = commits[i].split(" ", 1) |
3267343a BP |
976 | f = os.popen('''git format-patch -1 --stdout --pretty=format:"\ |
977 | Author: %an <%ae> | |
978 | Commit: %cn <%ce> | |
979 | Subject: %s | |
980 | ||
981 | %b" ''' + revision, 'r') | |
a1fccabc BP |
982 | patch = f.read() |
983 | f.close() | |
984 | ||
de8fa82a AC |
985 | if not quiet: |
986 | print('== Checking %s ("%s") ==' % (revision[0:12], name)) | |
81c2316f | 987 | result = ovs_checkpatch_parse(patch, revision) |
241bc88a | 988 | ovs_checkpatch_print_result() |
81c2316f | 989 | if result: |
241bc88a | 990 | status = EXIT_FAILURE |
a1fccabc BP |
991 | sys.exit(status) |
992 | ||
b90cfa86 | 993 | if not args: |
c599d5cc AC |
994 | if sys.stdin.isatty(): |
995 | usage() | |
241bc88a | 996 | sys.exit(EXIT_FAILURE) |
81c2316f | 997 | result = ovs_checkpatch_parse(sys.stdin.read(), '-') |
241bc88a | 998 | ovs_checkpatch_print_result() |
81c2316f | 999 | sys.exit(result) |
b90cfa86 IM |
1000 | |
1001 | status = 0 | |
1002 | for filename in args: | |
de8fa82a AC |
1003 | if not quiet: |
1004 | print('== Checking "%s" ==' % filename) | |
b90cfa86 IM |
1005 | result = ovs_checkpatch_file(filename) |
1006 | if result: | |
241bc88a | 1007 | status = EXIT_FAILURE |
b90cfa86 | 1008 | sys.exit(status) |