VirtualBox

source: vbox/trunk/src/VBox/Devices/EFI/Firmware/BaseTools/Scripts/PatchCheck.py@ 105681

最後變更 在這個檔案從105681是 105670,由 vboxsync 提交於 3 月 前

Devices/EFI/FirmwareNew: Merge edk2-stable-202405 and make it build on aarch64, bugref:4643

  • 屬性 svn:eol-style 設為 native
  • 屬性 svn:executable 設為 *
檔案大小: 30.8 KB
 
1## @file
2# Check a patch for various format issues
3#
4# Copyright (c) 2015 - 2021, Intel Corporation. All rights reserved.<BR>
5# Copyright (C) 2020, Red Hat, Inc.<BR>
6# Copyright (c) 2020 - 2023, Arm Limited. All rights reserved.<BR>
7#
8# SPDX-License-Identifier: BSD-2-Clause-Patent
9#
10
11from __future__ import print_function
12
13VersionNumber = '0.1'
14__copyright__ = "Copyright (c) 2015 - 2016, Intel Corporation All rights reserved."
15
16import email
17import argparse
18import os
19import re
20import subprocess
21import sys
22
23import email.header
24
25class Verbose:
26 SILENT, ONELINE, NORMAL = range(3)
27 level = NORMAL
28
29class PatchCheckConf:
30 ignore_change_id = False
31 ignore_multi_package = False
32
33class EmailAddressCheck:
34 """Checks an email address."""
35
36 def __init__(self, email, description):
37 self.ok = True
38
39 if email is None:
40 self.error('Email address is missing!')
41 return
42 if description is None:
43 self.error('Email description is missing!')
44 return
45
46 self.description = "'" + description + "'"
47 self.check_email_address(email)
48
49 def error(self, *err):
50 if self.ok and Verbose.level > Verbose.ONELINE:
51 print('The ' + self.description + ' email address is not valid:')
52 self.ok = False
53 if Verbose.level < Verbose.NORMAL:
54 return
55 count = 0
56 for line in err:
57 prefix = (' *', ' ')[count > 0]
58 print(prefix, line)
59 count += 1
60
61 email_re1 = re.compile(r'(?:\s*)(.*?)(\s*)<(.+)>\s*$',
62 re.MULTILINE|re.IGNORECASE)
63
64 def check_email_address(self, email):
65 email = email.strip()
66 mo = self.email_re1.match(email)
67 if mo is None:
68 self.error("Email format is invalid: " + email.strip())
69 return
70
71 name = mo.group(1).strip()
72 if name == '':
73 self.error("Name is not provided with email address: " +
74 email)
75 else:
76 quoted = len(name) > 2 and name[0] == '"' and name[-1] == '"'
77 if name.find(',') >= 0 and not quoted:
78 self.error('Add quotes (") around name with a comma: ' +
79 name)
80
81 if mo.group(2) == '':
82 self.error("There should be a space between the name and " +
83 "email address: " + email)
84
85 if mo.group(3).find(' ') >= 0:
86 self.error("The email address cannot contain a space: " +
87 mo.group(3))
88
89 if mo.group(3) == '[email protected]':
90 self.error("Email rewritten by lists DMARC / DKIM / SPF: " +
91 email)
92
93 if ' via groups.io' in name.lower() and mo.group(3).endswith('@groups.io'):
94 self.error("Email rewritten by lists DMARC / DKIM / SPF: " +
95 email)
96
97class CommitMessageCheck:
98 """Checks the contents of a git commit message."""
99
100 def __init__(self, subject, message, author_email):
101 self.ok = True
102 self.ignore_multi_package = False
103
104 if subject is None and message is None:
105 self.error('Commit message is missing!')
106 return
107
108 MergifyMerge = False
109 if "mergify[bot]@users.noreply.github.com" in author_email:
110 if "Merge branch" in subject:
111 MergifyMerge = True
112
113 self.subject = subject
114 self.msg = message
115
116 print (subject)
117
118 self.check_contributed_under()
119 if not MergifyMerge:
120 self.check_signed_off_by()
121 self.check_misc_signatures()
122 self.check_overall_format()
123 if not PatchCheckConf.ignore_change_id:
124 self.check_change_id_format()
125 self.check_ci_options_format()
126 self.report_message_result()
127
128 url = 'https://github.com/tianocore/tianocore.github.io/wiki/Commit-Message-Format'
129
130 def report_message_result(self):
131 if Verbose.level < Verbose.NORMAL:
132 return
133 if self.ok:
134 # All checks passed
135 return_code = 0
136 print('The commit message format passed all checks.')
137 else:
138 return_code = 1
139 if not self.ok:
140 print(self.url)
141
142 def error(self, *err):
143 if self.ok and Verbose.level > Verbose.ONELINE:
144 print('The commit message format is not valid:')
145 self.ok = False
146 if Verbose.level < Verbose.NORMAL:
147 return
148 count = 0
149 for line in err:
150 prefix = (' *', ' ')[count > 0]
151 print(prefix, line)
152 count += 1
153
154 # Find 'contributed-under:' at the start of a line ignoring case and
155 # requires ':' to be present. Matches if there is white space before
156 # the tag or between the tag and the ':'.
157 contributed_under_re = \
158 re.compile(r'^\s*contributed-under\s*:', re.MULTILINE|re.IGNORECASE)
159
160 def check_contributed_under(self):
161 match = self.contributed_under_re.search(self.msg)
162 if match is not None:
163 self.error('Contributed-under! (Note: this must be ' +
164 'removed by the code contributor!)')
165
166 @staticmethod
167 def make_signature_re(sig, re_input=False):
168 if re_input:
169 sub_re = sig
170 else:
171 sub_re = sig.replace('-', r'[-\s]+')
172 re_str = (r'^(?P<tag>' + sub_re +
173 r')(\s*):(\s*)(?P<value>\S.*?)(?:\s*)$')
174 try:
175 return re.compile(re_str, re.MULTILINE|re.IGNORECASE)
176 except Exception:
177 print("Tried to compile re:", re_str)
178 raise
179
180 sig_block_re = \
181 re.compile(r'''^
182 (?: (?P<tag>[^:]+) \s* : \s*
183 (?P<value>\S.*?) )
184 |
185 (?: \[ (?P<updater>[^:]+) \s* : \s*
186 (?P<note>.+?) \s* \] )
187 \s* $''',
188 re.VERBOSE | re.MULTILINE)
189
190 def find_signatures(self, sig):
191 if not sig.endswith('-by') and sig != 'Cc':
192 sig += '-by'
193 regex = self.make_signature_re(sig)
194
195 sigs = regex.findall(self.msg)
196
197 bad_case_sigs = filter(lambda m: m[0] != sig, sigs)
198 for s in bad_case_sigs:
199 self.error("'" +s[0] + "' should be '" + sig + "'")
200
201 for s in sigs:
202 if s[1] != '':
203 self.error('There should be no spaces between ' + sig +
204 " and the ':'")
205 if s[2] != ' ':
206 self.error("There should be a space after '" + sig + ":'")
207
208 self.ok &= EmailAddressCheck(s[3], sig).ok
209
210 return sigs
211
212 def check_signed_off_by(self):
213 sob='Signed-off-by'
214 if self.msg.find(sob) < 0:
215 self.error('Missing Signed-off-by! (Note: this must be ' +
216 'added by the code contributor!)')
217 return
218
219 sobs = self.find_signatures('Signed-off')
220
221 if len(sobs) == 0:
222 self.error('Invalid Signed-off-by format!')
223 return
224
225 sig_types = (
226 'Reviewed',
227 'Reported',
228 'Tested',
229 'Suggested',
230 'Acked',
231 'Cc'
232 )
233
234 def check_misc_signatures(self):
235 for sigtype in self.sig_types:
236 sigs = self.find_signatures(sigtype)
237 if sigtype == 'Cc' and len(sigs) == 0:
238 self.error('No Cc: tags for maintainers/reviewers found!')
239
240 cve_re = re.compile('CVE-[0-9]{4}-[0-9]{5}[^0-9]')
241
242 def check_overall_format(self):
243 lines = self.msg.splitlines()
244
245 if len(lines) >= 1 and lines[0].endswith('\r\n'):
246 empty_line = '\r\n'
247 else:
248 empty_line = '\n'
249
250 lines.insert(0, empty_line)
251 lines.insert(0, self.subject + empty_line)
252
253 count = len(lines)
254
255 if count <= 0:
256 self.error('Empty commit message!')
257 return
258
259 if count >= 1 and re.search(self.cve_re, lines[0]):
260 #
261 # If CVE-xxxx-xxxxx is present in subject line, then limit length of
262 # subject line to 92 characters
263 #
264 if len(lines[0].rstrip()) >= 93:
265 self.error(
266 'First line of commit message (subject line) is too long (%d >= 93).' %
267 (len(lines[0].rstrip()))
268 )
269 else:
270 #
271 # If CVE-xxxx-xxxxx is not present in subject line, then limit
272 # length of subject line to 75 characters
273 #
274 if len(lines[0].rstrip()) >= 76:
275 self.error(
276 'First line of commit message (subject line) is too long (%d >= 76).' %
277 (len(lines[0].rstrip()))
278 )
279
280 if count >= 1 and len(lines[0].strip()) == 0:
281 self.error('First line of commit message (subject line) ' +
282 'is empty.')
283
284 if count >= 2 and lines[1].strip() != '':
285 self.error('Second line of commit message should be ' +
286 'empty.')
287
288 for i in range(2, count):
289 if (len(lines[i]) >= 76 and
290 len(lines[i].split()) > 1 and
291 not lines[i].startswith('git-svn-id:') and
292 not lines[i].startswith('Reviewed-by') and
293 not lines[i].startswith('Acked-by:') and
294 not lines[i].startswith('Tested-by:') and
295 not lines[i].startswith('Reported-by:') and
296 not lines[i].startswith('Suggested-by:') and
297 not lines[i].startswith('Signed-off-by:') and
298 not lines[i].startswith('Cc:')):
299 #
300 # Print a warning if body line is longer than 75 characters
301 #
302 print(
303 'WARNING - Line %d of commit message is too long (%d >= 76).' %
304 (i + 1, len(lines[i]))
305 )
306 print(lines[i])
307
308 last_sig_line = None
309 for i in range(count - 1, 0, -1):
310 line = lines[i]
311 mo = self.sig_block_re.match(line)
312 if mo is None:
313 if line.strip() == '':
314 break
315 elif last_sig_line is not None:
316 err2 = 'Add empty line before "%s"?' % last_sig_line
317 self.error('The line before the signature block ' +
318 'should be empty', err2)
319 else:
320 self.error('The signature block was not found')
321 break
322 last_sig_line = line.strip()
323
324 def check_change_id_format(self):
325 cid='Change-Id:'
326 if self.msg.find(cid) != -1:
327 self.error('\"%s\" found in commit message:' % cid)
328 return
329
330 def check_ci_options_format(self):
331 cio='Continuous-integration-options:'
332 for line in self.msg.splitlines():
333 if not line.startswith(cio):
334 continue
335 options = line.split(':', 1)[1].split()
336 if 'PatchCheck.ignore-multi-package' in options:
337 self.ignore_multi_package = True
338
339(START, PRE_PATCH, PATCH) = range(3)
340
341class GitDiffCheck:
342 """Checks the contents of a git diff."""
343
344 def __init__(self, diff):
345 self.ok = True
346 self.format_ok = True
347 self.lines = diff.splitlines(True)
348 self.count = len(self.lines)
349 self.line_num = 0
350 self.state = START
351 self.new_bin = []
352 while self.line_num < self.count and self.format_ok:
353 line_num = self.line_num
354 self.run()
355 assert(self.line_num > line_num)
356 self.report_message_result()
357
358 def report_message_result(self):
359 if Verbose.level < Verbose.NORMAL:
360 return
361 if self.ok:
362 print('The code passed all checks.')
363 if self.new_bin:
364 print('\nWARNING - The following binary files will be added ' +
365 'into the repository:')
366 for binary in self.new_bin:
367 print(' ' + binary)
368
369 def run(self):
370 line = self.lines[self.line_num]
371
372 if self.state in (PRE_PATCH, PATCH):
373 if line.startswith('diff --git'):
374 self.state = START
375 if self.state == PATCH:
376 if line.startswith('@@ '):
377 self.state = PRE_PATCH
378 elif len(line) >= 1 and line[0] not in ' -+' and \
379 not line.startswith('\r\n') and \
380 not line.startswith(r'\ No newline ') and not self.binary:
381 for line in self.lines[self.line_num + 1:]:
382 if line.startswith('diff --git'):
383 self.format_error('diff found after end of patch')
384 break
385 self.line_num = self.count
386 return
387
388 if self.state == START:
389 if line.startswith('diff --git'):
390 self.state = PRE_PATCH
391 self.filename = line[13:].split(' ', 1)[0]
392 self.is_newfile = False
393 self.force_crlf = True
394 self.force_notabs = True
395 if self.filename.endswith('.rtf'):
396 self.force_crlf = False
397 self.force_notabs = False
398 if self.filename.endswith('.sh') or \
399 self.filename.startswith('BaseTools/BinWrappers/PosixLike/') or \
400 self.filename.startswith('BaseTools/BinPipWrappers/PosixLike/') or \
401 self.filename == 'BaseTools/BuildEnv':
402 #
403 # Do not enforce CR/LF line endings for linux shell scripts.
404 # Some linux shell scripts don't end with the ".sh" extension,
405 # they are identified by their path.
406 #
407 self.force_crlf = False
408 if self.filename == '.gitmodules' or \
409 self.filename == 'BaseTools/Conf/diff.order':
410 #
411 # .gitmodules and diff orderfiles are used internally by git
412 # use tabs and LF line endings. Do not enforce no tabs and
413 # do not enforce CR/LF line endings.
414 #
415 self.force_crlf = False
416 self.force_notabs = False
417 if os.path.basename(self.filename) == 'GNUmakefile' or \
418 os.path.basename(self.filename).lower() == 'makefile' or \
419 os.path.splitext(self.filename)[1] == '.makefile' or \
420 self.filename.startswith(
421 'BaseTools/Source/C/VfrCompile/Pccts/'):
422 self.force_notabs = False
423 elif len(line.rstrip()) != 0:
424 self.format_error("didn't find diff command")
425 self.line_num += 1
426 elif self.state == PRE_PATCH:
427 if line.startswith('@@ '):
428 self.state = PATCH
429 self.binary = False
430 elif line.startswith('GIT binary patch') or \
431 line.startswith('Binary files'):
432 self.state = PATCH
433 self.binary = True
434 if self.is_newfile:
435 self.new_bin.append(self.filename)
436 elif line.startswith('new file mode 160000'):
437 #
438 # New submodule. Do not enforce CR/LF line endings
439 #
440 self.force_crlf = False
441 else:
442 ok = False
443 self.is_newfile = self.newfile_prefix_re.match(line)
444 for pfx in self.pre_patch_prefixes:
445 if line.startswith(pfx):
446 ok = True
447 if not ok:
448 self.format_error("didn't find diff hunk marker (@@)")
449 self.line_num += 1
450 elif self.state == PATCH:
451 if self.binary or self.filename.endswith(".rtf"):
452 pass
453 elif line.startswith('-'):
454 pass
455 elif line.startswith('+'):
456 self.check_added_line(line[1:])
457 elif line.startswith('\r\n'):
458 pass
459 elif line.startswith(r'\ No newline '):
460 pass
461 elif not line.startswith(' '):
462 self.format_error("unexpected patch line")
463 self.line_num += 1
464
465 pre_patch_prefixes = (
466 '--- ',
467 '+++ ',
468 'index ',
469 'new file ',
470 'deleted file ',
471 'old mode ',
472 'new mode ',
473 'similarity index ',
474 'copy from ',
475 'copy to ',
476 'rename ',
477 )
478
479 line_endings = ('\r\n', '\n\r', '\n', '\r')
480
481 newfile_prefix_re = \
482 re.compile(r'''^
483 index\ 0+\.\.
484 ''',
485 re.VERBOSE)
486
487 def added_line_error(self, msg, line):
488 lines = [ msg ]
489 if self.filename is not None:
490 lines.append('File: ' + self.filename)
491 lines.append('Line: ' + line)
492
493 self.error(*lines)
494
495 old_debug_re = \
496 re.compile(r'''
497 DEBUG \s* \( \s* \( \s*
498 (?: DEBUG_[A-Z_]+ \s* \| \s*)*
499 EFI_D_ ([A-Z_]+)
500 ''',
501 re.VERBOSE)
502
503 def check_added_line(self, line):
504 eol = ''
505 for an_eol in self.line_endings:
506 if line.endswith(an_eol):
507 eol = an_eol
508 line = line[:-len(eol)]
509
510 stripped = line.rstrip()
511
512 if self.force_crlf and eol != '\r\n' and (line.find('Subproject commit') == -1):
513 self.added_line_error('Line ending (%s) is not CRLF' % repr(eol),
514 line)
515 if self.force_notabs and '\t' in line:
516 self.added_line_error('Tab character used', line)
517 if len(stripped) < len(line):
518 self.added_line_error('Trailing whitespace found', line)
519
520 mo = self.old_debug_re.search(line)
521 if mo is not None:
522 self.added_line_error('EFI_D_' + mo.group(1) + ' was used, '
523 'but DEBUG_' + mo.group(1) +
524 ' is now recommended', line)
525
526 rp_file = os.path.realpath(self.filename)
527 rp_script = os.path.realpath(__file__)
528 if line.find('__FUNCTION__') != -1 and rp_file != rp_script:
529 self.added_line_error('__FUNCTION__ was used, but __func__ '
530 'is now recommended', line)
531
532 split_diff_re = re.compile(r'''
533 (?P<cmd>
534 ^ diff \s+ --git \s+ a/.+ \s+ b/.+ $
535 )
536 (?P<index>
537 ^ index \s+ .+ $
538 )
539 ''',
540 re.IGNORECASE | re.VERBOSE | re.MULTILINE)
541
542 def format_error(self, err):
543 self.format_ok = False
544 err = 'Patch format error: ' + err
545 err2 = 'Line: ' + self.lines[self.line_num].rstrip()
546 self.error(err, err2)
547
548 def error(self, *err):
549 if self.ok and Verbose.level > Verbose.ONELINE:
550 print('Code format is not valid:')
551 self.ok = False
552 if Verbose.level < Verbose.NORMAL:
553 return
554 count = 0
555 for line in err:
556 prefix = (' *', ' ')[count > 0]
557 print(prefix, line)
558 count += 1
559
560class CheckOnePatch:
561 """Checks the contents of a git email formatted patch.
562
563 Various checks are performed on both the commit message and the
564 patch content.
565 """
566
567 def __init__(self, name, patch):
568 self.patch = patch
569 self.find_patch_pieces()
570
571 email_check = EmailAddressCheck(self.author_email, 'Author')
572 email_ok = email_check.ok
573
574 msg_check = CommitMessageCheck(self.commit_subject, self.commit_msg, self.author_email)
575 msg_ok = msg_check.ok
576 self.ignore_multi_package = msg_check.ignore_multi_package
577
578 diff_ok = True
579 if self.diff is not None:
580 diff_check = GitDiffCheck(self.diff)
581 diff_ok = diff_check.ok
582
583 self.ok = email_ok and msg_ok and diff_ok
584
585 if Verbose.level == Verbose.ONELINE:
586 if self.ok:
587 result = 'ok'
588 else:
589 result = list()
590 if not msg_ok:
591 result.append('commit message')
592 if not diff_ok:
593 result.append('diff content')
594 result = 'bad ' + ' and '.join(result)
595 print(name, result)
596
597
598 git_diff_re = re.compile(r'''
599 ^ diff \s+ --git \s+ a/.+ \s+ b/.+ $
600 ''',
601 re.IGNORECASE | re.VERBOSE | re.MULTILINE)
602
603 stat_re = \
604 re.compile(r'''
605 (?P<commit_message> [\s\S\r\n]* )
606 (?P<stat>
607 ^ --- $ [\r\n]+
608 (?: ^ \s+ .+ \s+ \| \s+ \d+ \s+ \+* \-*
609 $ [\r\n]+ )+
610 [\s\S\r\n]+
611 )
612 ''',
613 re.IGNORECASE | re.VERBOSE | re.MULTILINE)
614
615 subject_prefix_re = \
616 re.compile(r'''^
617 \s* (\[
618 [^\[\]]* # Allow all non-brackets
619 \])* \s*
620 ''',
621 re.VERBOSE)
622
623 def find_patch_pieces(self):
624 if sys.version_info < (3, 0):
625 patch = self.patch.encode('ascii', 'ignore')
626 else:
627 patch = self.patch
628
629 self.commit_msg = None
630 self.stat = None
631 self.commit_subject = None
632 self.commit_prefix = None
633 self.diff = None
634
635 if patch.startswith('diff --git'):
636 self.diff = patch
637 return
638
639 pmail = email.message_from_string(patch)
640 parts = list(pmail.walk())
641 assert(len(parts) == 1)
642 assert(parts[0].get_content_type() == 'text/plain')
643 content = parts[0].get_payload(decode=True).decode('utf-8', 'ignore')
644
645 mo = self.git_diff_re.search(content)
646 if mo is not None:
647 self.diff = content[mo.start():]
648 content = content[:mo.start()]
649
650 mo = self.stat_re.search(content)
651 if mo is None:
652 self.commit_msg = content
653 else:
654 self.stat = mo.group('stat')
655 self.commit_msg = mo.group('commit_message')
656 #
657 # Parse subject line from email header. The subject line may be
658 # composed of multiple parts with different encodings. Decode and
659 # combine all the parts to produce a single string with the contents of
660 # the decoded subject line.
661 #
662 parts = email.header.decode_header(pmail.get('subject'))
663 subject = ''
664 for (part, encoding) in parts:
665 if encoding:
666 part = part.decode(encoding)
667 else:
668 try:
669 part = part.decode()
670 except:
671 pass
672 subject = subject + part
673
674 self.commit_subject = subject.replace('\r\n', '')
675 self.commit_subject = self.commit_subject.replace('\n', '')
676 self.commit_subject = self.subject_prefix_re.sub('', self.commit_subject, 1)
677
678 self.author_email = pmail['from']
679
680class CheckGitCommits:
681 """Reads patches from git based on the specified git revision range.
682
683 The patches are read from git, and then checked.
684 """
685
686 def __init__(self, rev_spec, max_count):
687 dec_files = self.read_dec_files_from_git()
688 commits = self.read_commit_list_from_git(rev_spec, max_count)
689 if len(commits) == 1 and Verbose.level > Verbose.ONELINE:
690 commits = [ rev_spec ]
691 self.ok = True
692 blank_line = False
693 for commit in commits:
694 if Verbose.level > Verbose.ONELINE:
695 if blank_line:
696 print()
697 else:
698 blank_line = True
699 print('Checking git commit:', commit)
700 email = self.read_committer_email_address_from_git(commit)
701 self.ok &= EmailAddressCheck(email, 'Committer').ok
702 patch = self.read_patch_from_git(commit)
703 check_patch = CheckOnePatch(commit, patch)
704 self.ok &= check_patch.ok
705 ignore_multi_package = check_patch.ignore_multi_package
706 if PatchCheckConf.ignore_multi_package:
707 ignore_multi_package = True
708 prefix = 'WARNING: ' if ignore_multi_package else ''
709 check_parent = self.check_parent_packages (dec_files, commit, prefix)
710 if not ignore_multi_package:
711 self.ok &= check_parent
712
713 if not commits:
714 print("Couldn't find commit matching: '{}'".format(rev_spec))
715
716 def check_parent_packages(self, dec_files, commit, prefix):
717 ok = True
718 modified = self.get_parent_packages (dec_files, commit, 'AM')
719 if len (modified) > 1:
720 print("{}The commit adds/modifies files in multiple packages:".format(prefix))
721 print(" *", '\n * '.join(modified))
722 ok = False
723 deleted = self.get_parent_packages (dec_files, commit, 'D')
724 if len (deleted) > 1:
725 print("{}The commit deletes files from multiple packages:".format(prefix))
726 print(" *", '\n * '.join(deleted))
727 ok = False
728 return ok
729
730 def get_parent_packages(self, dec_files, commit, filter):
731 filelist = self.read_files_modified_from_git (commit, filter)
732 parents = set()
733 for file in filelist:
734 dec_found = False
735 for dec_file in dec_files:
736 if os.path.commonpath([dec_file, file]):
737 dec_found = True
738 parents.add(dec_file)
739 if not dec_found and os.path.dirname (file):
740 # No DEC file found and file is in a subdir
741 # Covers BaseTools, .github, .azurepipelines, .pytool
742 parents.add(file.split('/')[0])
743 return list(parents)
744
745 def read_dec_files_from_git(self):
746 # run git ls-files *.dec
747 out = self.run_git('ls-files', '*.dec')
748 # return list of .dec files
749 try:
750 return out.split()
751 except:
752 return []
753
754 def read_files_modified_from_git(self, commit, filter):
755 # run git diff-tree --no-commit-id --name-only -r <commit>
756 out = self.run_git('diff-tree', '--no-commit-id', '--name-only',
757 '--diff-filter=' + filter, '-r', commit)
758 try:
759 return out.split()
760 except:
761 return []
762
763 def read_commit_list_from_git(self, rev_spec, max_count):
764 # Run git to get the commit patch
765 cmd = [ 'rev-list', '--abbrev-commit', '--no-walk' ]
766 if max_count is not None:
767 cmd.append('--max-count=' + str(max_count))
768 cmd.append(rev_spec)
769 out = self.run_git(*cmd)
770 return out.split() if out else []
771
772 def read_patch_from_git(self, commit):
773 # Run git to get the commit patch
774 return self.run_git('show', '--pretty=email', '--no-textconv',
775 '--no-use-mailmap', commit)
776
777 def read_committer_email_address_from_git(self, commit):
778 # Run git to get the committer email
779 return self.run_git('show', '--pretty=%cn <%ce>', '--no-patch',
780 '--no-use-mailmap', commit)
781
782 def run_git(self, *args):
783 cmd = [ 'git' ]
784 cmd += args
785 p = subprocess.Popen(cmd,
786 stdout=subprocess.PIPE,
787 stderr=subprocess.STDOUT)
788 Result = p.communicate()
789 return Result[0].decode('utf-8', 'ignore') if Result[0] and Result[0].find(b"fatal")!=0 else None
790
791class CheckOnePatchFile:
792 """Performs a patch check for a single file.
793
794 stdin is used when the filename is '-'.
795 """
796
797 def __init__(self, patch_filename):
798 if patch_filename == '-':
799 patch = sys.stdin.read()
800 patch_filename = 'stdin'
801 else:
802 f = open(patch_filename, 'rb')
803 patch = f.read().decode('utf-8', 'ignore')
804 f.close()
805 if Verbose.level > Verbose.ONELINE:
806 print('Checking patch file:', patch_filename)
807 self.ok = CheckOnePatch(patch_filename, patch).ok
808
809class CheckOneArg:
810 """Performs a patch check for a single command line argument.
811
812 The argument will be handed off to a file or git-commit based
813 checker.
814 """
815
816 def __init__(self, param, max_count=None):
817 self.ok = True
818 if param == '-' or os.path.exists(param):
819 checker = CheckOnePatchFile(param)
820 else:
821 checker = CheckGitCommits(param, max_count)
822 self.ok = checker.ok
823
824class PatchCheckApp:
825 """Checks patches based on the command line arguments."""
826
827 def __init__(self):
828 self.parse_options()
829 patches = self.args.patches
830
831 if len(patches) == 0:
832 patches = [ 'HEAD' ]
833
834 self.ok = True
835 self.count = None
836 for patch in patches:
837 self.process_one_arg(patch)
838
839 if self.count is not None:
840 self.process_one_arg('HEAD')
841
842 if self.ok:
843 self.retval = 0
844 else:
845 self.retval = -1
846
847 def process_one_arg(self, arg):
848 if len(arg) >= 2 and arg[0] == '-':
849 try:
850 self.count = int(arg[1:])
851 return
852 except ValueError:
853 pass
854 self.ok &= CheckOneArg(arg, self.count).ok
855 self.count = None
856
857 def parse_options(self):
858 parser = argparse.ArgumentParser(description=__copyright__)
859 parser.add_argument('--version', action='version',
860 version='%(prog)s ' + VersionNumber)
861 parser.add_argument('patches', nargs='*',
862 help='[patch file | git rev list]')
863 group = parser.add_mutually_exclusive_group()
864 group.add_argument("--oneline",
865 action="store_true",
866 help="Print one result per line")
867 group.add_argument("--silent",
868 action="store_true",
869 help="Print nothing")
870 group.add_argument("--ignore-change-id",
871 action="store_true",
872 help="Ignore the presence of 'Change-Id:' tags in commit message")
873 group.add_argument("--ignore-multi-package",
874 action="store_true",
875 help="Ignore if commit modifies files in multiple packages")
876 self.args = parser.parse_args()
877 if self.args.oneline:
878 Verbose.level = Verbose.ONELINE
879 if self.args.silent:
880 Verbose.level = Verbose.SILENT
881 if self.args.ignore_change_id:
882 PatchCheckConf.ignore_change_id = True
883 if self.args.ignore_multi_package:
884 PatchCheckConf.ignore_multi_package = True
885
886if __name__ == "__main__":
887 sys.exit(PatchCheckApp().retval)
注意: 瀏覽 TracBrowser 來幫助您使用儲存庫瀏覽器

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette