| 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 482 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 493 msgs.extend(pool.map_async(CallCommand, tests).get(99999)) | 493 msgs.extend(pool.map_async(CallCommand, tests).get(99999)) |
| 494 pool.close() | 494 pool.close() |
| 495 pool.join() | 495 pool.join() |
| 496 else: | 496 else: |
| 497 msgs.extend(map(CallCommand, tests)) | 497 msgs.extend(map(CallCommand, tests)) |
| 498 return [m for m in msgs if m] | 498 return [m for m in msgs if m] |
| 499 | 499 |
| 500 | 500 |
| 501 class _DiffCache(object): | 501 class _DiffCache(object): |
| 502 """Caches diffs retrieved from a particular SCM.""" | 502 """Caches diffs retrieved from a particular SCM.""" |
| 503 def __init__(self, upstream=None): |
| 504 """Stores the upstream revision against which all diffs will be computed.""" |
| 505 self._upstream = upstream |
| 503 | 506 |
| 504 def GetDiff(self, path, local_root): | 507 def GetDiff(self, path, local_root): |
| 505 """Get the diff for a particular path.""" | 508 """Get the diff for a particular path.""" |
| 506 raise NotImplementedError() | 509 raise NotImplementedError() |
| 507 | 510 |
| 508 | 511 |
| 509 class _SvnDiffCache(_DiffCache): | 512 class _SvnDiffCache(_DiffCache): |
| 510 """DiffCache implementation for subversion.""" | 513 """DiffCache implementation for subversion.""" |
| 511 def __init__(self): | 514 def __init__(self, *args, **kwargs): |
| 512 super(_SvnDiffCache, self).__init__() | 515 super(_SvnDiffCache, self).__init__(*args, **kwargs) |
| 513 self._diffs_by_file = {} | 516 self._diffs_by_file = {} |
| 514 | 517 |
| 515 def GetDiff(self, path, local_root): | 518 def GetDiff(self, path, local_root): |
| 516 if path not in self._diffs_by_file: | 519 if path not in self._diffs_by_file: |
| 517 self._diffs_by_file[path] = scm.SVN.GenerateDiff([path], local_root, | 520 self._diffs_by_file[path] = scm.SVN.GenerateDiff([path], local_root, |
| 518 False, None) | 521 False, None) |
| 519 return self._diffs_by_file[path] | 522 return self._diffs_by_file[path] |
| 520 | 523 |
| 521 | 524 |
| 522 class _GitDiffCache(_DiffCache): | 525 class _GitDiffCache(_DiffCache): |
| 523 """DiffCache implementation for git; gets all file diffs at once.""" | 526 """DiffCache implementation for git; gets all file diffs at once.""" |
| 524 def __init__(self): | 527 def __init__(self, upstream): |
| 525 super(_GitDiffCache, self).__init__() | 528 super(_GitDiffCache, self).__init__(upstream=upstream) |
| 526 self._diffs_by_file = None | 529 self._diffs_by_file = None |
| 527 | 530 |
| 528 def GetDiff(self, path, local_root): | 531 def GetDiff(self, path, local_root): |
| 529 if not self._diffs_by_file: | 532 if not self._diffs_by_file: |
| 530 # Compute a single diff for all files and parse the output; should | 533 # Compute a single diff for all files and parse the output; should |
| 531 # with git this is much faster than computing one diff for each file. | 534 # with git this is much faster than computing one diff for each file. |
| 532 diffs = {} | 535 diffs = {} |
| 533 | 536 |
| 534 # Don't specify any filenames below, because there are command line length | 537 # Don't specify any filenames below, because there are command line length |
| 535 # limits on some platforms and GenerateDiff would fail. | 538 # limits on some platforms and GenerateDiff would fail. |
| 536 unified_diff = scm.GIT.GenerateDiff(local_root, files=[], full_move=True) | 539 unified_diff = scm.GIT.GenerateDiff(local_root, files=[], full_move=True, |
| 540 branch=self._upstream) |
| 537 | 541 |
| 538 # This regex matches the path twice, separated by a space. Note that | 542 # This regex matches the path twice, separated by a space. Note that |
| 539 # filename itself may contain spaces. | 543 # filename itself may contain spaces. |
| 540 file_marker = re.compile('^diff --git (?P<filename>.*) (?P=filename)$') | 544 file_marker = re.compile('^diff --git (?P<filename>.*) (?P=filename)$') |
| 541 current_diff = [] | 545 current_diff = [] |
| 542 keep_line_endings = True | 546 keep_line_endings = True |
| 543 for x in unified_diff.splitlines(keep_line_endings): | 547 for x in unified_diff.splitlines(keep_line_endings): |
| 544 match = file_marker.match(x) | 548 match = file_marker.match(x) |
| 545 if match: | 549 if match: |
| 546 # Marks the start of a new per-file section. | 550 # Marks the start of a new per-file section. |
| (...skipping 13 matching lines...) Expand all Loading... |
| 560 return self._diffs_by_file[path] | 564 return self._diffs_by_file[path] |
| 561 | 565 |
| 562 | 566 |
| 563 class AffectedFile(object): | 567 class AffectedFile(object): |
| 564 """Representation of a file in a change.""" | 568 """Representation of a file in a change.""" |
| 565 | 569 |
| 566 DIFF_CACHE = _DiffCache | 570 DIFF_CACHE = _DiffCache |
| 567 | 571 |
| 568 # Method could be a function | 572 # Method could be a function |
| 569 # pylint: disable=R0201 | 573 # pylint: disable=R0201 |
| 570 def __init__(self, path, action, repository_root, diff_cache=None): | 574 def __init__(self, path, action, repository_root, diff_cache): |
| 571 self._path = path | 575 self._path = path |
| 572 self._action = action | 576 self._action = action |
| 573 self._local_root = repository_root | 577 self._local_root = repository_root |
| 574 self._is_directory = None | 578 self._is_directory = None |
| 575 self._properties = {} | 579 self._properties = {} |
| 576 self._cached_changed_contents = None | 580 self._cached_changed_contents = None |
| 577 self._cached_new_contents = None | 581 self._cached_new_contents = None |
| 578 if diff_cache: | 582 self._diff_cache = diff_cache |
| 579 self._diff_cache = diff_cache | |
| 580 else: | |
| 581 self._diff_cache = self.DIFF_CACHE() | |
| 582 logging.debug('%s(%s)' % (self.__class__.__name__, self._path)) | 583 logging.debug('%s(%s)' % (self.__class__.__name__, self._path)) |
| 583 | 584 |
| 584 def ServerPath(self): | 585 def ServerPath(self): |
| 585 """Returns a path string that identifies the file in the SCM system. | 586 """Returns a path string that identifies the file in the SCM system. |
| 586 | 587 |
| 587 Returns the empty string if the file does not exist in SCM. | 588 Returns the empty string if the file does not exist in SCM. |
| 588 """ | 589 """ |
| 589 return '' | 590 return '' |
| 590 | 591 |
| 591 def LocalPath(self): | 592 def LocalPath(self): |
| (...skipping 193 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 785 """ | 786 """ |
| 786 | 787 |
| 787 _AFFECTED_FILES = AffectedFile | 788 _AFFECTED_FILES = AffectedFile |
| 788 | 789 |
| 789 # Matches key/value (or "tag") lines in changelist descriptions. | 790 # Matches key/value (or "tag") lines in changelist descriptions. |
| 790 TAG_LINE_RE = re.compile( | 791 TAG_LINE_RE = re.compile( |
| 791 '^[ \t]*(?P<key>[A-Z][A-Z_0-9]*)[ \t]*=[ \t]*(?P<value>.*?)[ \t]*$') | 792 '^[ \t]*(?P<key>[A-Z][A-Z_0-9]*)[ \t]*=[ \t]*(?P<value>.*?)[ \t]*$') |
| 792 scm = '' | 793 scm = '' |
| 793 | 794 |
| 794 def __init__( | 795 def __init__( |
| 795 self, name, description, local_root, files, issue, patchset, author): | 796 self, name, description, local_root, files, issue, patchset, author, |
| 797 upstream=None): |
| 796 if files is None: | 798 if files is None: |
| 797 files = [] | 799 files = [] |
| 798 self._name = name | 800 self._name = name |
| 799 # Convert root into an absolute path. | 801 # Convert root into an absolute path. |
| 800 self._local_root = os.path.abspath(local_root) | 802 self._local_root = os.path.abspath(local_root) |
| 803 self._upstream = upstream |
| 801 self.issue = issue | 804 self.issue = issue |
| 802 self.patchset = patchset | 805 self.patchset = patchset |
| 803 self.author_email = author | 806 self.author_email = author |
| 804 | 807 |
| 805 self._full_description = '' | 808 self._full_description = '' |
| 806 self.tags = {} | 809 self.tags = {} |
| 807 self._description_without_tags = '' | 810 self._description_without_tags = '' |
| 808 self.SetDescriptionText(description) | 811 self.SetDescriptionText(description) |
| 809 | 812 |
| 810 assert all( | 813 assert all( |
| 811 (isinstance(f, (list, tuple)) and len(f) == 2) for f in files), files | 814 (isinstance(f, (list, tuple)) and len(f) == 2) for f in files), files |
| 812 | 815 |
| 813 diff_cache = self._AFFECTED_FILES.DIFF_CACHE() | 816 diff_cache = self._AFFECTED_FILES.DIFF_CACHE(self._upstream) |
| 814 self._affected_files = [ | 817 self._affected_files = [ |
| 815 self._AFFECTED_FILES(path, action.strip(), self._local_root, diff_cache) | 818 self._AFFECTED_FILES(path, action.strip(), self._local_root, diff_cache) |
| 816 for action, path in files | 819 for action, path in files |
| 817 ] | 820 ] |
| 818 | 821 |
| 819 def Name(self): | 822 def Name(self): |
| 820 """Returns the change name.""" | 823 """Returns the change name.""" |
| 821 return self._name | 824 return self._name |
| 822 | 825 |
| 823 def DescriptionText(self): | 826 def DescriptionText(self): |
| (...skipping 781 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1605 def default(self, obj): | 1608 def default(self, obj): |
| 1606 if isinstance(obj, set): | 1609 if isinstance(obj, set): |
| 1607 return sorted(obj) | 1610 return sorted(obj) |
| 1608 return json.JSONEncoder.default(self, obj) | 1611 return json.JSONEncoder.default(self, obj) |
| 1609 change = change_class(options.name, | 1612 change = change_class(options.name, |
| 1610 options.description, | 1613 options.description, |
| 1611 options.root, | 1614 options.root, |
| 1612 files, | 1615 files, |
| 1613 options.issue, | 1616 options.issue, |
| 1614 options.patchset, | 1617 options.patchset, |
| 1615 options.author) | 1618 options.author, |
| 1619 upstream=options.upstream) |
| 1616 trybots = DoGetTrySlaves( | 1620 trybots = DoGetTrySlaves( |
| 1617 change, | 1621 change, |
| 1618 change.LocalPaths(), | 1622 change.LocalPaths(), |
| 1619 change.RepositoryRoot(), | 1623 change.RepositoryRoot(), |
| 1620 None, | 1624 None, |
| 1621 None, | 1625 None, |
| 1622 options.verbose, | 1626 options.verbose, |
| 1623 sys.stdout) | 1627 sys.stdout) |
| 1624 json.dump(trybots, f, cls=SetEncoder) | 1628 json.dump(trybots, f, cls=SetEncoder) |
| 1625 try: | 1629 try: |
| 1626 with canned_check_filter(options.skip_canned): | 1630 with canned_check_filter(options.skip_canned): |
| 1627 results = DoPresubmitChecks( | 1631 results = DoPresubmitChecks( |
| 1628 change_class(options.name, | 1632 change_class(options.name, |
| 1629 options.description, | 1633 options.description, |
| 1630 options.root, | 1634 options.root, |
| 1631 files, | 1635 files, |
| 1632 options.issue, | 1636 options.issue, |
| 1633 options.patchset, | 1637 options.patchset, |
| 1634 options.author), | 1638 options.author, |
| 1639 upstream=options.upstream), |
| 1635 options.commit, | 1640 options.commit, |
| 1636 options.verbose, | 1641 options.verbose, |
| 1637 sys.stdout, | 1642 sys.stdout, |
| 1638 sys.stdin, | 1643 sys.stdin, |
| 1639 options.default_presubmit, | 1644 options.default_presubmit, |
| 1640 options.may_prompt, | 1645 options.may_prompt, |
| 1641 rietveld_obj) | 1646 rietveld_obj) |
| 1642 return not results.should_continue() | 1647 return not results.should_continue() |
| 1643 except NonexistantCannedCheckFilter, e: | 1648 except NonexistantCannedCheckFilter, e: |
| 1644 print >> sys.stderr, ( | 1649 print >> sys.stderr, ( |
| 1645 'Attempted to skip nonexistent canned presubmit check: %s' % e.message) | 1650 'Attempted to skip nonexistent canned presubmit check: %s' % e.message) |
| 1646 return 2 | 1651 return 2 |
| 1647 except PresubmitFailure, e: | 1652 except PresubmitFailure, e: |
| 1648 print >> sys.stderr, e | 1653 print >> sys.stderr, e |
| 1649 print >> sys.stderr, 'Maybe your depot_tools is out of date?' | 1654 print >> sys.stderr, 'Maybe your depot_tools is out of date?' |
| 1650 print >> sys.stderr, 'If all fails, contact maruel@' | 1655 print >> sys.stderr, 'If all fails, contact maruel@' |
| 1651 return 2 | 1656 return 2 |
| 1652 | 1657 |
| 1653 | 1658 |
| 1654 if __name__ == '__main__': | 1659 if __name__ == '__main__': |
| 1655 fix_encoding.fix_encoding() | 1660 fix_encoding.fix_encoding() |
| 1656 sys.exit(Main(None)) | 1661 sys.exit(Main(None)) |
| OLD | NEW |