]> git.proxmox.com Git - mirror_edk2.git/blame - BaseTools/Scripts/PatchCheck.py
BaseTools:Fix GenFds issue for BuildOption replace GenFdsOption
[mirror_edk2.git] / BaseTools / Scripts / PatchCheck.py
CommitLineData
a7e173b0
JJ
1## @file\r
2# Check a patch for various format issues\r
3#\r
a2813610 4# Copyright (c) 2015 - 2019, Intel Corporation. All rights reserved.<BR>\r
8ffa47fb 5# Copyright (C) 2020, Red Hat, Inc.<BR>\r
a7e173b0 6#\r
2e351cbe 7# SPDX-License-Identifier: BSD-2-Clause-Patent\r
a7e173b0
JJ
8#\r
9\r
10from __future__ import print_function\r
11\r
12VersionNumber = '0.1'\r
e6140670 13__copyright__ = "Copyright (c) 2015 - 2016, Intel Corporation All rights reserved."\r
a7e173b0
JJ
14\r
15import email\r
16import argparse\r
17import os\r
18import re\r
19import subprocess\r
20import sys\r
21\r
22class Verbose:\r
23 SILENT, ONELINE, NORMAL = range(3)\r
24 level = NORMAL\r
25\r
8ffa47fb
PMD
26class EmailAddressCheck:\r
27 """Checks an email address."""\r
28\r
8120390a 29 def __init__(self, email, description):\r
8ffa47fb
PMD
30 self.ok = True\r
31\r
32 if email is None:\r
33 self.error('Email address is missing!')\r
34 return\r
8120390a
PMD
35 if description is None:\r
36 self.error('Email description is missing!')\r
37 return\r
8ffa47fb 38\r
8120390a 39 self.description = "'" + description + "'"\r
8ffa47fb
PMD
40 self.check_email_address(email)\r
41\r
42 def error(self, *err):\r
43 if self.ok and Verbose.level > Verbose.ONELINE:\r
8120390a 44 print('The ' + self.description + ' email address is not valid:')\r
8ffa47fb
PMD
45 self.ok = False\r
46 if Verbose.level < Verbose.NORMAL:\r
47 return\r
48 count = 0\r
49 for line in err:\r
50 prefix = (' *', ' ')[count > 0]\r
51 print(prefix, line)\r
52 count += 1\r
53\r
54 email_re1 = re.compile(r'(?:\s*)(.*?)(\s*)<(.+)>\s*$',\r
55 re.MULTILINE|re.IGNORECASE)\r
56\r
57 def check_email_address(self, email):\r
58 email = email.strip()\r
59 mo = self.email_re1.match(email)\r
60 if mo is None:\r
61 self.error("Email format is invalid: " + email.strip())\r
62 return\r
63\r
64 name = mo.group(1).strip()\r
65 if name == '':\r
66 self.error("Name is not provided with email address: " +\r
67 email)\r
68 else:\r
69 quoted = len(name) > 2 and name[0] == '"' and name[-1] == '"'\r
70 if name.find(',') >= 0 and not quoted:\r
71 self.error('Add quotes (") around name with a comma: ' +\r
72 name)\r
73\r
74 if mo.group(2) == '':\r
75 self.error("There should be a space between the name and " +\r
76 "email address: " + email)\r
77\r
78 if mo.group(3).find(' ') >= 0:\r
79 self.error("The email address cannot contain a space: " +\r
80 mo.group(3))\r
81\r
a7e173b0
JJ
82class CommitMessageCheck:\r
83 """Checks the contents of a git commit message."""\r
84\r
85 def __init__(self, subject, message):\r
86 self.ok = True\r
87\r
88 if subject is None and message is None:\r
89 self.error('Commit message is missing!')\r
90 return\r
91\r
92 self.subject = subject\r
93 self.msg = message\r
94\r
95 self.check_contributed_under()\r
96 self.check_signed_off_by()\r
97 self.check_misc_signatures()\r
98 self.check_overall_format()\r
99 self.report_message_result()\r
100\r
101 url = 'https://github.com/tianocore/tianocore.github.io/wiki/Commit-Message-Format'\r
102\r
103 def report_message_result(self):\r
104 if Verbose.level < Verbose.NORMAL:\r
105 return\r
106 if self.ok:\r
107 # All checks passed\r
108 return_code = 0\r
109 print('The commit message format passed all checks.')\r
110 else:\r
111 return_code = 1\r
112 if not self.ok:\r
113 print(self.url)\r
114\r
115 def error(self, *err):\r
116 if self.ok and Verbose.level > Verbose.ONELINE:\r
117 print('The commit message format is not valid:')\r
118 self.ok = False\r
119 if Verbose.level < Verbose.NORMAL:\r
120 return\r
121 count = 0\r
122 for line in err:\r
123 prefix = (' *', ' ')[count > 0]\r
124 print(prefix, line)\r
125 count += 1\r
126\r
a2813610
MK
127 # Find 'contributed-under:' at the start of a line ignoring case and\r
128 # requires ':' to be present. Matches if there is white space before\r
129 # the tag or between the tag and the ':'.\r
130 contributed_under_re = \\r
131 re.compile(r'^\s*contributed-under\s*:', re.MULTILINE|re.IGNORECASE)\r
132\r
a7e173b0 133 def check_contributed_under(self):\r
a2813610
MK
134 match = self.contributed_under_re.search(self.msg)\r
135 if match is not None:\r
136 self.error('Contributed-under! (Note: this must be ' +\r
137 'removed by the code contributor!)')\r
a7e173b0
JJ
138\r
139 @staticmethod\r
140 def make_signature_re(sig, re_input=False):\r
141 if re_input:\r
142 sub_re = sig\r
143 else:\r
144 sub_re = sig.replace('-', r'[-\s]+')\r
145 re_str = (r'^(?P<tag>' + sub_re +\r
146 r')(\s*):(\s*)(?P<value>\S.*?)(?:\s*)$')\r
147 try:\r
148 return re.compile(re_str, re.MULTILINE|re.IGNORECASE)\r
149 except Exception:\r
150 print("Tried to compile re:", re_str)\r
151 raise\r
152\r
153 sig_block_re = \\r
154 re.compile(r'''^\r
155 (?: (?P<tag>[^:]+) \s* : \s*\r
156 (?P<value>\S.*?) )\r
157 |\r
158 (?: \[ (?P<updater>[^:]+) \s* : \s*\r
159 (?P<note>.+?) \s* \] )\r
160 \s* $''',\r
161 re.VERBOSE | re.MULTILINE)\r
162\r
163 def find_signatures(self, sig):\r
164 if not sig.endswith('-by') and sig != 'Cc':\r
165 sig += '-by'\r
166 regex = self.make_signature_re(sig)\r
167\r
168 sigs = regex.findall(self.msg)\r
169\r
170 bad_case_sigs = filter(lambda m: m[0] != sig, sigs)\r
171 for s in bad_case_sigs:\r
172 self.error("'" +s[0] + "' should be '" + sig + "'")\r
173\r
174 for s in sigs:\r
175 if s[1] != '':\r
176 self.error('There should be no spaces between ' + sig +\r
177 " and the ':'")\r
178 if s[2] != ' ':\r
179 self.error("There should be a space after '" + sig + ":'")\r
180\r
8120390a 181 EmailAddressCheck(s[3], sig)\r
a7e173b0
JJ
182\r
183 return sigs\r
184\r
a7e173b0
JJ
185 def check_signed_off_by(self):\r
186 sob='Signed-off-by'\r
187 if self.msg.find(sob) < 0:\r
188 self.error('Missing Signed-off-by! (Note: this must be ' +\r
189 'added by the code contributor!)')\r
190 return\r
191\r
192 sobs = self.find_signatures('Signed-off')\r
193\r
194 if len(sobs) == 0:\r
195 self.error('Invalid Signed-off-by format!')\r
196 return\r
197\r
198 sig_types = (\r
199 'Reviewed',\r
200 'Reported',\r
201 'Tested',\r
202 'Suggested',\r
203 'Acked',\r
204 'Cc'\r
205 )\r
206\r
207 def check_misc_signatures(self):\r
208 for sig in self.sig_types:\r
209 self.find_signatures(sig)\r
210\r
211 def check_overall_format(self):\r
212 lines = self.msg.splitlines()\r
213\r
214 if len(lines) >= 1 and lines[0].endswith('\r\n'):\r
215 empty_line = '\r\n'\r
216 else:\r
217 empty_line = '\n'\r
218\r
219 lines.insert(0, empty_line)\r
220 lines.insert(0, self.subject + empty_line)\r
221\r
222 count = len(lines)\r
223\r
224 if count <= 0:\r
225 self.error('Empty commit message!')\r
226 return\r
227\r
2649a735 228 if count >= 1 and len(lines[0].rstrip()) >= 72:\r
a7e173b0
JJ
229 self.error('First line of commit message (subject line) ' +\r
230 'is too long.')\r
231\r
232 if count >= 1 and len(lines[0].strip()) == 0:\r
233 self.error('First line of commit message (subject line) ' +\r
234 'is empty.')\r
235\r
236 if count >= 2 and lines[1].strip() != '':\r
237 self.error('Second line of commit message should be ' +\r
238 'empty.')\r
239\r
240 for i in range(2, count):\r
e6140670 241 if (len(lines[i]) >= 76 and\r
a7e173b0
JJ
242 len(lines[i].split()) > 1 and\r
243 not lines[i].startswith('git-svn-id:')):\r
244 self.error('Line %d of commit message is too long.' % (i + 1))\r
245\r
246 last_sig_line = None\r
247 for i in range(count - 1, 0, -1):\r
248 line = lines[i]\r
249 mo = self.sig_block_re.match(line)\r
250 if mo is None:\r
251 if line.strip() == '':\r
252 break\r
253 elif last_sig_line is not None:\r
254 err2 = 'Add empty line before "%s"?' % last_sig_line\r
255 self.error('The line before the signature block ' +\r
256 'should be empty', err2)\r
257 else:\r
258 self.error('The signature block was not found')\r
259 break\r
260 last_sig_line = line.strip()\r
261\r
262(START, PRE_PATCH, PATCH) = range(3)\r
263\r
264class GitDiffCheck:\r
265 """Checks the contents of a git diff."""\r
266\r
267 def __init__(self, diff):\r
268 self.ok = True\r
269 self.format_ok = True\r
270 self.lines = diff.splitlines(True)\r
271 self.count = len(self.lines)\r
272 self.line_num = 0\r
273 self.state = START\r
6a69dd49 274 self.new_bin = []\r
a7e173b0
JJ
275 while self.line_num < self.count and self.format_ok:\r
276 line_num = self.line_num\r
277 self.run()\r
278 assert(self.line_num > line_num)\r
279 self.report_message_result()\r
280\r
281 def report_message_result(self):\r
282 if Verbose.level < Verbose.NORMAL:\r
283 return\r
284 if self.ok:\r
285 print('The code passed all checks.')\r
6a69dd49
HW
286 if self.new_bin:\r
287 print('\nWARNING - The following binary files will be added ' +\r
288 'into the repository:')\r
289 for binary in self.new_bin:\r
290 print(' ' + binary)\r
a7e173b0
JJ
291\r
292 def run(self):\r
293 line = self.lines[self.line_num]\r
294\r
295 if self.state in (PRE_PATCH, PATCH):\r
296 if line.startswith('diff --git'):\r
297 self.state = START\r
298 if self.state == PATCH:\r
299 if line.startswith('@@ '):\r
300 self.state = PRE_PATCH\r
301 elif len(line) >= 1 and line[0] not in ' -+' and \\r
05579e51 302 not line.startswith('\r\n') and \\r
aab57eff 303 not line.startswith(r'\ No newline ') and not self.binary:\r
a7e173b0
JJ
304 for line in self.lines[self.line_num + 1:]:\r
305 if line.startswith('diff --git'):\r
306 self.format_error('diff found after end of patch')\r
307 break\r
308 self.line_num = self.count\r
309 return\r
310\r
311 if self.state == START:\r
312 if line.startswith('diff --git'):\r
313 self.state = PRE_PATCH\r
ccaa7754 314 self.filename = line[13:].split(' ', 1)[0]\r
6a69dd49
HW
315 self.is_newfile = False\r
316 self.force_crlf = not self.filename.endswith('.sh')\r
a7e173b0
JJ
317 elif len(line.rstrip()) != 0:\r
318 self.format_error("didn't find diff command")\r
319 self.line_num += 1\r
320 elif self.state == PRE_PATCH:\r
a7e173b0
JJ
321 if line.startswith('@@ '):\r
322 self.state = PATCH\r
103733f8 323 self.binary = False\r
6a69dd49
HW
324 elif line.startswith('GIT binary patch') or \\r
325 line.startswith('Binary files'):\r
103733f8
JJ
326 self.state = PATCH\r
327 self.binary = True\r
6a69dd49
HW
328 if self.is_newfile:\r
329 self.new_bin.append(self.filename)\r
a7e173b0
JJ
330 else:\r
331 ok = False\r
6a69dd49 332 self.is_newfile = self.newfile_prefix_re.match(line)\r
a7e173b0
JJ
333 for pfx in self.pre_patch_prefixes:\r
334 if line.startswith(pfx):\r
335 ok = True\r
336 if not ok:\r
337 self.format_error("didn't find diff hunk marker (@@)")\r
338 self.line_num += 1\r
339 elif self.state == PATCH:\r
103733f8
JJ
340 if self.binary:\r
341 pass\r
aab57eff 342 elif line.startswith('-'):\r
a7e173b0
JJ
343 pass\r
344 elif line.startswith('+'):\r
345 self.check_added_line(line[1:])\r
05579e51
MK
346 elif line.startswith('\r\n'):\r
347 pass\r
a7e173b0
JJ
348 elif line.startswith(r'\ No newline '):\r
349 pass\r
350 elif not line.startswith(' '):\r
351 self.format_error("unexpected patch line")\r
352 self.line_num += 1\r
353\r
354 pre_patch_prefixes = (\r
355 '--- ',\r
356 '+++ ',\r
357 'index ',\r
358 'new file ',\r
359 'deleted file ',\r
360 'old mode ',\r
361 'new mode ',\r
362 'similarity index ',\r
1eeb5ff1
MK
363 'copy from ',\r
364 'copy to ',\r
a7e173b0 365 'rename ',\r
a7e173b0
JJ
366 )\r
367\r
368 line_endings = ('\r\n', '\n\r', '\n', '\r')\r
369\r
6a69dd49
HW
370 newfile_prefix_re = \\r
371 re.compile(r'''^\r
372 index\ 0+\.\.\r
373 ''',\r
374 re.VERBOSE)\r
a7e173b0
JJ
375\r
376 def added_line_error(self, msg, line):\r
377 lines = [ msg ]\r
6a69dd49
HW
378 if self.filename is not None:\r
379 lines.append('File: ' + self.filename)\r
a7e173b0
JJ
380 lines.append('Line: ' + line)\r
381\r
382 self.error(*lines)\r
383\r
c3926cdb
YZ
384 old_debug_re = \\r
385 re.compile(r'''\r
386 DEBUG \s* \( \s* \( \s*\r
387 (?: DEBUG_[A-Z_]+ \s* \| \s*)*\r
388 EFI_D_ ([A-Z_]+)\r
389 ''',\r
390 re.VERBOSE)\r
391\r
a7e173b0
JJ
392 def check_added_line(self, line):\r
393 eol = ''\r
394 for an_eol in self.line_endings:\r
395 if line.endswith(an_eol):\r
396 eol = an_eol\r
397 line = line[:-len(eol)]\r
398\r
399 stripped = line.rstrip()\r
400\r
401 if self.force_crlf and eol != '\r\n':\r
402 self.added_line_error('Line ending (%s) is not CRLF' % repr(eol),\r
403 line)\r
404 if '\t' in line:\r
405 self.added_line_error('Tab character used', line)\r
406 if len(stripped) < len(line):\r
407 self.added_line_error('Trailing whitespace found', line)\r
408\r
c3926cdb
YZ
409 mo = self.old_debug_re.search(line)\r
410 if mo is not None:\r
411 self.added_line_error('EFI_D_' + mo.group(1) + ' was used, '\r
412 'but DEBUG_' + mo.group(1) +\r
413 ' is now recommended', line)\r
414\r
a7e173b0
JJ
415 split_diff_re = re.compile(r'''\r
416 (?P<cmd>\r
417 ^ diff \s+ --git \s+ a/.+ \s+ b/.+ $\r
418 )\r
419 (?P<index>\r
420 ^ index \s+ .+ $\r
421 )\r
422 ''',\r
423 re.IGNORECASE | re.VERBOSE | re.MULTILINE)\r
424\r
425 def format_error(self, err):\r
426 self.format_ok = False\r
427 err = 'Patch format error: ' + err\r
428 err2 = 'Line: ' + self.lines[self.line_num].rstrip()\r
429 self.error(err, err2)\r
430\r
431 def error(self, *err):\r
432 if self.ok and Verbose.level > Verbose.ONELINE:\r
433 print('Code format is not valid:')\r
434 self.ok = False\r
435 if Verbose.level < Verbose.NORMAL:\r
436 return\r
437 count = 0\r
438 for line in err:\r
439 prefix = (' *', ' ')[count > 0]\r
440 print(prefix, line)\r
441 count += 1\r
442\r
443class CheckOnePatch:\r
444 """Checks the contents of a git email formatted patch.\r
445\r
446 Various checks are performed on both the commit message and the\r
447 patch content.\r
448 """\r
449\r
450 def __init__(self, name, patch):\r
451 self.patch = patch\r
452 self.find_patch_pieces()\r
453\r
c0328cf3
PMD
454 email_check = EmailAddressCheck(self.author_email, 'Author')\r
455 email_ok = email_check.ok\r
456\r
a7e173b0
JJ
457 msg_check = CommitMessageCheck(self.commit_subject, self.commit_msg)\r
458 msg_ok = msg_check.ok\r
459\r
460 diff_ok = True\r
461 if self.diff is not None:\r
462 diff_check = GitDiffCheck(self.diff)\r
463 diff_ok = diff_check.ok\r
464\r
c0328cf3 465 self.ok = email_ok and msg_ok and diff_ok\r
a7e173b0
JJ
466\r
467 if Verbose.level == Verbose.ONELINE:\r
468 if self.ok:\r
469 result = 'ok'\r
470 else:\r
471 result = list()\r
472 if not msg_ok:\r
473 result.append('commit message')\r
474 if not diff_ok:\r
475 result.append('diff content')\r
476 result = 'bad ' + ' and '.join(result)\r
477 print(name, result)\r
478\r
479\r
480 git_diff_re = re.compile(r'''\r
481 ^ diff \s+ --git \s+ a/.+ \s+ b/.+ $\r
482 ''',\r
483 re.IGNORECASE | re.VERBOSE | re.MULTILINE)\r
484\r
485 stat_re = \\r
486 re.compile(r'''\r
487 (?P<commit_message> [\s\S\r\n]* )\r
488 (?P<stat>\r
489 ^ --- $ [\r\n]+\r
490 (?: ^ \s+ .+ \s+ \| \s+ \d+ \s+ \+* \-*\r
491 $ [\r\n]+ )+\r
492 [\s\S\r\n]+\r
493 )\r
494 ''',\r
495 re.IGNORECASE | re.VERBOSE | re.MULTILINE)\r
496\r
e709bbb1
YZ
497 subject_prefix_re = \\r
498 re.compile(r'''^\r
499 \s* (\[\r
500 [^\[\]]* # Allow all non-brackets\r
501 \])* \s*\r
502 ''',\r
503 re.VERBOSE)\r
504\r
a7e173b0
JJ
505 def find_patch_pieces(self):\r
506 if sys.version_info < (3, 0):\r
507 patch = self.patch.encode('ascii', 'ignore')\r
508 else:\r
509 patch = self.patch\r
510\r
511 self.commit_msg = None\r
512 self.stat = None\r
513 self.commit_subject = None\r
514 self.commit_prefix = None\r
515 self.diff = None\r
516\r
517 if patch.startswith('diff --git'):\r
518 self.diff = patch\r
519 return\r
520\r
521 pmail = email.message_from_string(patch)\r
522 parts = list(pmail.walk())\r
523 assert(len(parts) == 1)\r
524 assert(parts[0].get_content_type() == 'text/plain')\r
525 content = parts[0].get_payload(decode=True).decode('utf-8', 'ignore')\r
526\r
527 mo = self.git_diff_re.search(content)\r
528 if mo is not None:\r
529 self.diff = content[mo.start():]\r
530 content = content[:mo.start()]\r
531\r
532 mo = self.stat_re.search(content)\r
533 if mo is None:\r
534 self.commit_msg = content\r
535 else:\r
536 self.stat = mo.group('stat')\r
537 self.commit_msg = mo.group('commit_message')\r
538\r
539 self.commit_subject = pmail['subject'].replace('\r\n', '')\r
540 self.commit_subject = self.commit_subject.replace('\n', '')\r
e709bbb1 541 self.commit_subject = self.subject_prefix_re.sub('', self.commit_subject, 1)\r
a7e173b0 542\r
c0328cf3
PMD
543 self.author_email = pmail['from']\r
544\r
a7e173b0
JJ
545class CheckGitCommits:\r
546 """Reads patches from git based on the specified git revision range.\r
547\r
548 The patches are read from git, and then checked.\r
549 """\r
550\r
551 def __init__(self, rev_spec, max_count):\r
552 commits = self.read_commit_list_from_git(rev_spec, max_count)\r
553 if len(commits) == 1 and Verbose.level > Verbose.ONELINE:\r
554 commits = [ rev_spec ]\r
555 self.ok = True\r
556 blank_line = False\r
557 for commit in commits:\r
558 if Verbose.level > Verbose.ONELINE:\r
559 if blank_line:\r
560 print()\r
561 else:\r
562 blank_line = True\r
563 print('Checking git commit:', commit)\r
8f38b08b
PMD
564 email = self.read_committer_email_address_from_git(commit)\r
565 self.ok &= EmailAddressCheck(email, 'Committer').ok\r
a7e173b0
JJ
566 patch = self.read_patch_from_git(commit)\r
567 self.ok &= CheckOnePatch(commit, patch).ok\r
5ac4548c
JC
568 if not commits:\r
569 print("Couldn't find commit matching: '{}'".format(rev_spec))\r
a7e173b0
JJ
570\r
571 def read_commit_list_from_git(self, rev_spec, max_count):\r
572 # Run git to get the commit patch\r
573 cmd = [ 'rev-list', '--abbrev-commit', '--no-walk' ]\r
574 if max_count is not None:\r
575 cmd.append('--max-count=' + str(max_count))\r
576 cmd.append(rev_spec)\r
577 out = self.run_git(*cmd)\r
5ac4548c 578 return out.split() if out else []\r
a7e173b0
JJ
579\r
580 def read_patch_from_git(self, commit):\r
581 # Run git to get the commit patch\r
96603b4f 582 return self.run_git('show', '--pretty=email', '--no-textconv', commit)\r
a7e173b0 583\r
8f38b08b
PMD
584 def read_committer_email_address_from_git(self, commit):\r
585 # Run git to get the committer email\r
586 return self.run_git('show', '--pretty=%cn <%ce>', '--no-patch', commit)\r
587\r
a7e173b0
JJ
588 def run_git(self, *args):\r
589 cmd = [ 'git' ]\r
590 cmd += args\r
591 p = subprocess.Popen(cmd,\r
592 stdout=subprocess.PIPE,\r
593 stderr=subprocess.STDOUT)\r
5ac4548c 594 Result = p.communicate()\r
6460513a 595 return Result[0].decode('utf-8', 'ignore') if Result[0] and Result[0].find(b"fatal")!=0 else None\r
a7e173b0
JJ
596\r
597class CheckOnePatchFile:\r
598 """Performs a patch check for a single file.\r
599\r
600 stdin is used when the filename is '-'.\r
601 """\r
602\r
603 def __init__(self, patch_filename):\r
604 if patch_filename == '-':\r
605 patch = sys.stdin.read()\r
606 patch_filename = 'stdin'\r
607 else:\r
608 f = open(patch_filename, 'rb')\r
609 patch = f.read().decode('utf-8', 'ignore')\r
610 f.close()\r
611 if Verbose.level > Verbose.ONELINE:\r
612 print('Checking patch file:', patch_filename)\r
613 self.ok = CheckOnePatch(patch_filename, patch).ok\r
614\r
615class CheckOneArg:\r
616 """Performs a patch check for a single command line argument.\r
617\r
618 The argument will be handed off to a file or git-commit based\r
619 checker.\r
620 """\r
621\r
622 def __init__(self, param, max_count=None):\r
623 self.ok = True\r
624 if param == '-' or os.path.exists(param):\r
625 checker = CheckOnePatchFile(param)\r
626 else:\r
627 checker = CheckGitCommits(param, max_count)\r
628 self.ok = checker.ok\r
629\r
630class PatchCheckApp:\r
631 """Checks patches based on the command line arguments."""\r
632\r
633 def __init__(self):\r
634 self.parse_options()\r
635 patches = self.args.patches\r
636\r
637 if len(patches) == 0:\r
638 patches = [ 'HEAD' ]\r
639\r
640 self.ok = True\r
641 self.count = None\r
642 for patch in patches:\r
643 self.process_one_arg(patch)\r
644\r
645 if self.count is not None:\r
646 self.process_one_arg('HEAD')\r
647\r
648 if self.ok:\r
649 self.retval = 0\r
650 else:\r
651 self.retval = -1\r
652\r
653 def process_one_arg(self, arg):\r
654 if len(arg) >= 2 and arg[0] == '-':\r
655 try:\r
656 self.count = int(arg[1:])\r
657 return\r
658 except ValueError:\r
659 pass\r
660 self.ok &= CheckOneArg(arg, self.count).ok\r
661 self.count = None\r
662\r
663 def parse_options(self):\r
664 parser = argparse.ArgumentParser(description=__copyright__)\r
665 parser.add_argument('--version', action='version',\r
666 version='%(prog)s ' + VersionNumber)\r
667 parser.add_argument('patches', nargs='*',\r
668 help='[patch file | git rev list]')\r
669 group = parser.add_mutually_exclusive_group()\r
670 group.add_argument("--oneline",\r
671 action="store_true",\r
672 help="Print one result per line")\r
673 group.add_argument("--silent",\r
674 action="store_true",\r
675 help="Print nothing")\r
676 self.args = parser.parse_args()\r
677 if self.args.oneline:\r
678 Verbose.level = Verbose.ONELINE\r
679 if self.args.silent:\r
680 Verbose.level = Verbose.SILENT\r
681\r
682if __name__ == "__main__":\r
683 sys.exit(PatchCheckApp().retval)\r