OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
6 """Enables directory-specific presubmit checks to run at upload and/or commit. | 6 """Enables directory-specific presubmit checks to run at upload and/or commit. |
7 """ | 7 """ |
8 | 8 |
9 __version__ = '1.8.0' | 9 __version__ = '1.8.0' |
10 | 10 |
(...skipping 476 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
487 | 487 |
488 The lists will be compiled as regular expression and | 488 The lists will be compiled as regular expression and |
489 AffectedFile.LocalPath() needs to pass both list. | 489 AffectedFile.LocalPath() needs to pass both list. |
490 | 490 |
491 Note: Copy-paste this function to suit your needs or use a lambda function. | 491 Note: Copy-paste this function to suit your needs or use a lambda function. |
492 """ | 492 """ |
493 def Find(affected_file, items): | 493 def Find(affected_file, items): |
494 local_path = affected_file.LocalPath() | 494 local_path = affected_file.LocalPath() |
495 for item in items: | 495 for item in items: |
496 if self.re.match(item, local_path): | 496 if self.re.match(item, local_path): |
497 logging.debug("%s matched %s" % (item, local_path)) | 497 logging.debug("%s matched %s", item, local_path) |
498 return True | 498 return True |
499 return False | 499 return False |
500 return (Find(affected_file, white_list or self.DEFAULT_WHITE_LIST) and | 500 return (Find(affected_file, white_list or self.DEFAULT_WHITE_LIST) and |
501 not Find(affected_file, black_list or self.DEFAULT_BLACK_LIST)) | 501 not Find(affected_file, black_list or self.DEFAULT_BLACK_LIST)) |
502 | 502 |
503 def AffectedSourceFiles(self, source_file): | 503 def AffectedSourceFiles(self, source_file): |
504 """Filter the list of AffectedTextFiles by the function source_file. | 504 """Filter the list of AffectedTextFiles by the function source_file. |
505 | 505 |
506 If source_file is None, InputApi.FilterSourceFile() is used. | 506 If source_file is None, InputApi.FilterSourceFile() is used. |
507 """ | 507 """ |
(...skipping 131 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
639 # pylint: disable=R0201 | 639 # pylint: disable=R0201 |
640 def __init__(self, path, action, repository_root, diff_cache): | 640 def __init__(self, path, action, repository_root, diff_cache): |
641 self._path = path | 641 self._path = path |
642 self._action = action | 642 self._action = action |
643 self._local_root = repository_root | 643 self._local_root = repository_root |
644 self._is_directory = None | 644 self._is_directory = None |
645 self._properties = {} | 645 self._properties = {} |
646 self._cached_changed_contents = None | 646 self._cached_changed_contents = None |
647 self._cached_new_contents = None | 647 self._cached_new_contents = None |
648 self._diff_cache = diff_cache | 648 self._diff_cache = diff_cache |
649 logging.debug('%s(%s)' % (self.__class__.__name__, self._path)) | 649 logging.debug('%s(%s)', self.__class__.__name__, self._path) |
650 | 650 |
651 def ServerPath(self): | 651 def ServerPath(self): |
652 """Returns a path string that identifies the file in the SCM system. | 652 """Returns a path string that identifies the file in the SCM system. |
653 | 653 |
654 Returns the empty string if the file does not exist in SCM. | 654 Returns the empty string if the file does not exist in SCM. |
655 """ | 655 """ |
656 return '' | 656 return '' |
657 | 657 |
658 def LocalPath(self): | 658 def LocalPath(self): |
659 """Returns the path of this file on the local disk relative to client root. | 659 """Returns the path of this file on the local disk relative to client root. |
(...skipping 426 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1086 break | 1086 break |
1087 parent_dir = os.path.dirname(directory) | 1087 parent_dir = os.path.dirname(directory) |
1088 if parent_dir == directory: | 1088 if parent_dir == directory: |
1089 # We hit the system root directory. | 1089 # We hit the system root directory. |
1090 break | 1090 break |
1091 directory = parent_dir | 1091 directory = parent_dir |
1092 | 1092 |
1093 # Look for PRESUBMIT.py in all candidate directories. | 1093 # Look for PRESUBMIT.py in all candidate directories. |
1094 results = [] | 1094 results = [] |
1095 for directory in sorted(list(candidates)): | 1095 for directory in sorted(list(candidates)): |
1096 p = os.path.join(directory, 'PRESUBMIT.py') | 1096 for f in os.listdir(directory): |
1097 if os.path.isfile(p): | 1097 p = os.path.join(directory, f) |
1098 results.append(p) | 1098 if os.path.isfile(p) and re.match( |
| 1099 r'PRESUBMIT.*\.py$', f) and not f.startswith('PRESUBMIT_test'): |
| 1100 results.append(p) |
1099 | 1101 |
1100 logging.debug('Presubmit files: %s' % ','.join(results)) | 1102 logging.debug('Presubmit files: %s', ','.join(results)) |
1101 return results | 1103 return results |
1102 | 1104 |
1103 | 1105 |
1104 class GetTrySlavesExecuter(object): | 1106 class GetTrySlavesExecuter(object): |
1105 @staticmethod | 1107 @staticmethod |
1106 def ExecPresubmitScript(script_text, presubmit_path, project, change): | 1108 def ExecPresubmitScript(script_text, presubmit_path, project, change): |
1107 """Executes GetPreferredTrySlaves() from a single presubmit script. | 1109 """Executes GetPreferredTrySlaves() from a single presubmit script. |
1108 | 1110 |
1109 This will soon be deprecated and replaced by GetPreferredTryMasters(). | 1111 This will soon be deprecated and replaced by GetPreferredTryMasters(). |
1110 | 1112 |
(...skipping 336 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1447 raise PresubmitFailure('"%s" had an exception.\n%s' % (presubmit_path, e)) | 1449 raise PresubmitFailure('"%s" had an exception.\n%s' % (presubmit_path, e)) |
1448 | 1450 |
1449 # These function names must change if we make substantial changes to | 1451 # These function names must change if we make substantial changes to |
1450 # the presubmit API that are not backwards compatible. | 1452 # the presubmit API that are not backwards compatible. |
1451 if self.committing: | 1453 if self.committing: |
1452 function_name = 'CheckChangeOnCommit' | 1454 function_name = 'CheckChangeOnCommit' |
1453 else: | 1455 else: |
1454 function_name = 'CheckChangeOnUpload' | 1456 function_name = 'CheckChangeOnUpload' |
1455 if function_name in context: | 1457 if function_name in context: |
1456 context['__args'] = (input_api, OutputApi(self.committing)) | 1458 context['__args'] = (input_api, OutputApi(self.committing)) |
1457 logging.debug('Running %s in %s' % (function_name, presubmit_path)) | 1459 logging.debug('Running %s in %s', function_name, presubmit_path) |
1458 result = eval(function_name + '(*__args)', context) | 1460 result = eval(function_name + '(*__args)', context) |
1459 logging.debug('Running %s done.' % function_name) | 1461 logging.debug('Running %s done.', function_name) |
1460 if not (isinstance(result, types.TupleType) or | 1462 if not (isinstance(result, types.TupleType) or |
1461 isinstance(result, types.ListType)): | 1463 isinstance(result, types.ListType)): |
1462 raise PresubmitFailure( | 1464 raise PresubmitFailure( |
1463 'Presubmit functions must return a tuple or list') | 1465 'Presubmit functions must return a tuple or list') |
1464 for item in result: | 1466 for item in result: |
1465 if not isinstance(item, OutputApi.PresubmitResult): | 1467 if not isinstance(item, OutputApi.PresubmitResult): |
1466 raise PresubmitFailure( | 1468 raise PresubmitFailure( |
1467 'All presubmit results must be of types derived from ' | 1469 'All presubmit results must be of types derived from ' |
1468 'output_api.PresubmitResult') | 1470 'output_api.PresubmitResult') |
1469 else: | 1471 else: |
(...skipping 132 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1602 dirs.remove('.svn') | 1604 dirs.remove('.svn') |
1603 if '.git' in dirs: | 1605 if '.git' in dirs: |
1604 dirs.remove('.git') | 1606 dirs.remove('.git') |
1605 for name in files: | 1607 for name in files: |
1606 if fnmatch.fnmatch(name, mask): | 1608 if fnmatch.fnmatch(name, mask): |
1607 results.append(os.path.join(root, name)) | 1609 results.append(os.path.join(root, name)) |
1608 return results | 1610 return results |
1609 | 1611 |
1610 | 1612 |
1611 def ParseFiles(args, recursive): | 1613 def ParseFiles(args, recursive): |
1612 logging.debug('Searching for %s' % args) | 1614 logging.debug('Searching for %s', args) |
1613 files = [] | 1615 files = [] |
1614 for arg in args: | 1616 for arg in args: |
1615 files.extend([('M', f) for f in ScanSubDirs(arg, recursive)]) | 1617 files.extend([('M', f) for f in ScanSubDirs(arg, recursive)]) |
1616 return files | 1618 return files |
1617 | 1619 |
1618 | 1620 |
1619 def load_files(options, args): | 1621 def load_files(options, args): |
1620 """Tries to determine the SCM.""" | 1622 """Tries to determine the SCM.""" |
1621 change_scm = scm.determine_scm(options.root) | 1623 change_scm = scm.determine_scm(options.root) |
1622 files = [] | 1624 files = [] |
1623 if args: | 1625 if args: |
1624 files = ParseFiles(args, options.recursive) | 1626 files = ParseFiles(args, options.recursive) |
1625 if change_scm == 'svn': | 1627 if change_scm == 'svn': |
1626 change_class = SvnChange | 1628 change_class = SvnChange |
1627 if not files: | 1629 if not files: |
1628 files = scm.SVN.CaptureStatus([], options.root) | 1630 files = scm.SVN.CaptureStatus([], options.root) |
1629 elif change_scm == 'git': | 1631 elif change_scm == 'git': |
1630 change_class = GitChange | 1632 change_class = GitChange |
1631 upstream = options.upstream or None | 1633 upstream = options.upstream or None |
1632 if not files: | 1634 if not files: |
1633 files = scm.GIT.CaptureStatus([], options.root, upstream) | 1635 files = scm.GIT.CaptureStatus([], options.root, upstream) |
1634 else: | 1636 else: |
1635 logging.info('Doesn\'t seem under source control. Got %d files' % len(args)) | 1637 logging.info('Doesn\'t seem under source control. Got %d files', len(args)) |
1636 if not files: | 1638 if not files: |
1637 return None, None | 1639 return None, None |
1638 change_class = Change | 1640 change_class = Change |
1639 return change_class, files | 1641 return change_class, files |
1640 | 1642 |
1641 | 1643 |
1642 class NonexistantCannedCheckFilter(Exception): | 1644 class NonexistantCannedCheckFilter(Exception): |
1643 pass | 1645 pass |
1644 | 1646 |
1645 | 1647 |
(...skipping 101 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1747 if options.rietveld_email and options.rietveld_email_file: | 1749 if options.rietveld_email and options.rietveld_email_file: |
1748 parser.error("Only one of --rietveld_email or --rietveld_email_file " | 1750 parser.error("Only one of --rietveld_email or --rietveld_email_file " |
1749 "can be passed to this program.") | 1751 "can be passed to this program.") |
1750 if options.rietveld_email_file: | 1752 if options.rietveld_email_file: |
1751 with open(options.rietveld_email_file, "rb") as f: | 1753 with open(options.rietveld_email_file, "rb") as f: |
1752 options.rietveld_email = f.read().strip() | 1754 options.rietveld_email = f.read().strip() |
1753 | 1755 |
1754 change_class, files = load_files(options, args) | 1756 change_class, files = load_files(options, args) |
1755 if not change_class: | 1757 if not change_class: |
1756 parser.error('For unversioned directory, <files> is not optional.') | 1758 parser.error('For unversioned directory, <files> is not optional.') |
1757 logging.info('Found %d file(s).' % len(files)) | 1759 logging.info('Found %d file(s).', len(files)) |
1758 | 1760 |
1759 rietveld_obj, gerrit_obj = None, None | 1761 rietveld_obj, gerrit_obj = None, None |
1760 | 1762 |
1761 if options.rietveld_url: | 1763 if options.rietveld_url: |
1762 # The empty password is permitted: '' is not None. | 1764 # The empty password is permitted: '' is not None. |
1763 if options.rietveld_private_key_file: | 1765 if options.rietveld_private_key_file: |
1764 rietveld_obj = rietveld.JwtOAuth2Rietveld( | 1766 rietveld_obj = rietveld.JwtOAuth2Rietveld( |
1765 options.rietveld_url, | 1767 options.rietveld_url, |
1766 options.rietveld_email, | 1768 options.rietveld_email, |
1767 options.rietveld_private_key_file) | 1769 options.rietveld_private_key_file) |
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1820 return 2 | 1822 return 2 |
1821 | 1823 |
1822 | 1824 |
1823 if __name__ == '__main__': | 1825 if __name__ == '__main__': |
1824 fix_encoding.fix_encoding() | 1826 fix_encoding.fix_encoding() |
1825 try: | 1827 try: |
1826 sys.exit(main()) | 1828 sys.exit(main()) |
1827 except KeyboardInterrupt: | 1829 except KeyboardInterrupt: |
1828 sys.stderr.write('interrupted\n') | 1830 sys.stderr.write('interrupted\n') |
1829 sys.exit(2) | 1831 sys.exit(2) |
OLD | NEW |