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