| 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 |