| 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 # Copyright (C) 2008 Evan Martin <martine@danga.com> | 6 # Copyright (C) 2008 Evan Martin <martine@danga.com> |
| 7 | 7 |
| 8 """A git-command for integrating reviews on Rietveld and Gerrit.""" | 8 """A git-command for integrating reviews on Rietveld and Gerrit.""" |
| 9 | 9 |
| 10 from __future__ import print_function | 10 from __future__ import print_function |
| (...skipping 409 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 420 | 420 |
| 421 codereview_host = urlparse.urlparse(codereview_url).hostname | 421 codereview_host = urlparse.urlparse(codereview_url).hostname |
| 422 authenticator = auth.get_authenticator_for_host(codereview_host, auth_config) | 422 authenticator = auth.get_authenticator_for_host(codereview_host, auth_config) |
| 423 http = authenticator.authorize(httplib2.Http()) | 423 http = authenticator.authorize(httplib2.Http()) |
| 424 http.force_exception_to_status_code = True | 424 http.force_exception_to_status_code = True |
| 425 | 425 |
| 426 # TODO(tandrii): consider caching Gerrit CL details just like | 426 # TODO(tandrii): consider caching Gerrit CL details just like |
| 427 # _RietveldChangelistImpl does, then caching values in these two variables | 427 # _RietveldChangelistImpl does, then caching values in these two variables |
| 428 # won't be necessary. | 428 # won't be necessary. |
| 429 owner_email = changelist.GetIssueOwner() | 429 owner_email = changelist.GetIssueOwner() |
| 430 project = changelist.GetIssueProject() | |
| 431 | 430 |
| 432 buildbucket_put_url = ( | 431 buildbucket_put_url = ( |
| 433 'https://{hostname}/_ah/api/buildbucket/v1/builds/batch'.format( | 432 'https://{hostname}/_ah/api/buildbucket/v1/builds/batch'.format( |
| 434 hostname=options.buildbucket_host)) | 433 hostname=options.buildbucket_host)) |
| 435 buildset = 'patch/{codereview}/{hostname}/{issue}/{patch}'.format( | 434 buildset = 'patch/{codereview}/{hostname}/{issue}/{patch}'.format( |
| 436 codereview='gerrit' if changelist.IsGerrit() else 'rietveld', | 435 codereview='gerrit' if changelist.IsGerrit() else 'rietveld', |
| 437 hostname=codereview_host, | 436 hostname=codereview_host, |
| 438 issue=changelist.GetIssue(), | 437 issue=changelist.GetIssue(), |
| 439 patch=patchset) | 438 patch=patchset) |
| 439 |
| 440 shared_parameters_properties = changelist.GetTryjobProperties(patchset) |
| 441 shared_parameters_properties['category'] = category |
| 442 if options.clobber: |
| 443 shared_parameters_properties['clobber'] = True |
| 440 extra_properties = _get_properties_from_options(options) | 444 extra_properties = _get_properties_from_options(options) |
| 445 if extra_properties: |
| 446 shared_parameters_properties.update(extra_properties) |
| 441 | 447 |
| 442 batch_req_body = {'builds': []} | 448 batch_req_body = {'builds': []} |
| 443 print_text = [] | 449 print_text = [] |
| 444 print_text.append('Tried jobs on:') | 450 print_text.append('Tried jobs on:') |
| 445 for bucket, builders_and_tests in sorted(buckets.iteritems()): | 451 for bucket, builders_and_tests in sorted(buckets.iteritems()): |
| 446 print_text.append('Bucket: %s' % bucket) | 452 print_text.append('Bucket: %s' % bucket) |
| 447 master = None | 453 master = None |
| 448 if bucket.startswith(MASTER_PREFIX): | 454 if bucket.startswith(MASTER_PREFIX): |
| 449 master = _unprefix_master(bucket) | 455 master = _unprefix_master(bucket) |
| 450 for builder, tests in sorted(builders_and_tests.iteritems()): | 456 for builder, tests in sorted(builders_and_tests.iteritems()): |
| 451 print_text.append(' %s: %s' % (builder, tests)) | 457 print_text.append(' %s: %s' % (builder, tests)) |
| 452 parameters = { | 458 parameters = { |
| 453 'builder_name': builder, | 459 'builder_name': builder, |
| 454 'changes': [{ | 460 'changes': [{ |
| 455 'author': {'email': owner_email}, | 461 'author': {'email': owner_email}, |
| 456 'revision': options.revision, | 462 'revision': options.revision, |
| 457 }], | 463 }], |
| 458 'properties': { | 464 'properties': shared_parameters_properties.copy(), |
| 459 'category': category, | |
| 460 'issue': changelist.GetIssue(), | |
| 461 'patch_project': project, | |
| 462 'patch_storage': 'rietveld', | |
| 463 'patchset': patchset, | |
| 464 'rietveld': codereview_url, | |
| 465 }, | |
| 466 } | 465 } |
| 467 if 'presubmit' in builder.lower(): | 466 if 'presubmit' in builder.lower(): |
| 468 parameters['properties']['dry_run'] = 'true' | 467 parameters['properties']['dry_run'] = 'true' |
| 469 if tests: | 468 if tests: |
| 470 parameters['properties']['testfilter'] = tests | 469 parameters['properties']['testfilter'] = tests |
| 471 if extra_properties: | |
| 472 parameters['properties'].update(extra_properties) | |
| 473 if options.clobber: | |
| 474 parameters['properties']['clobber'] = True | |
| 475 | 470 |
| 476 tags = [ | 471 tags = [ |
| 477 'builder:%s' % builder, | 472 'builder:%s' % builder, |
| 478 'buildset:%s' % buildset, | 473 'buildset:%s' % buildset, |
| 479 'user_agent:git_cl_try', | 474 'user_agent:git_cl_try', |
| 480 ] | 475 ] |
| 481 if master: | 476 if master: |
| 482 parameters['properties']['master'] = master | 477 parameters['properties']['master'] = master |
| 483 tags.append('master:%s' % master) | 478 tags.append('master:%s' % master) |
| 484 | 479 |
| (...skipping 1199 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1684 def GetStatus(self): | 1679 def GetStatus(self): |
| 1685 return self._codereview_impl.GetStatus() | 1680 return self._codereview_impl.GetStatus() |
| 1686 | 1681 |
| 1687 def GetCodereviewServer(self): | 1682 def GetCodereviewServer(self): |
| 1688 return self._codereview_impl.GetCodereviewServer() | 1683 return self._codereview_impl.GetCodereviewServer() |
| 1689 | 1684 |
| 1690 def GetIssueOwner(self): | 1685 def GetIssueOwner(self): |
| 1691 """Get owner from codereview, which may differ from this checkout.""" | 1686 """Get owner from codereview, which may differ from this checkout.""" |
| 1692 return self._codereview_impl.GetIssueOwner() | 1687 return self._codereview_impl.GetIssueOwner() |
| 1693 | 1688 |
| 1694 def GetIssueProject(self): | |
| 1695 """Get project from codereview, which may differ from what this | |
| 1696 checkout's codereview.settings or gerrit project URL say. | |
| 1697 """ | |
| 1698 return self._codereview_impl.GetIssueProject() | |
| 1699 | |
| 1700 def GetApprovingReviewers(self): | 1689 def GetApprovingReviewers(self): |
| 1701 return self._codereview_impl.GetApprovingReviewers() | 1690 return self._codereview_impl.GetApprovingReviewers() |
| 1702 | 1691 |
| 1703 def GetMostRecentPatchset(self): | 1692 def GetMostRecentPatchset(self): |
| 1704 return self._codereview_impl.GetMostRecentPatchset() | 1693 return self._codereview_impl.GetMostRecentPatchset() |
| 1705 | 1694 |
| 1706 def CannotTriggerTryJobReason(self): | 1695 def CannotTriggerTryJobReason(self): |
| 1707 """Returns reason (str) if unable trigger tryjobs on this CL or None.""" | 1696 """Returns reason (str) if unable trigger tryjobs on this CL or None.""" |
| 1708 return self._codereview_impl.CannotTriggerTryJobReason() | 1697 return self._codereview_impl.CannotTriggerTryJobReason() |
| 1709 | 1698 |
| 1699 def GetTryjobProperties(self, patchset=None): |
| 1700 """Returns dictionary of properties to launch tryjob.""" |
| 1701 return self._codereview_impl.GetTryjobProperties(patchset=patchset) |
| 1702 |
| 1710 def __getattr__(self, attr): | 1703 def __getattr__(self, attr): |
| 1711 # This is because lots of untested code accesses Rietveld-specific stuff | 1704 # This is because lots of untested code accesses Rietveld-specific stuff |
| 1712 # directly, and it's hard to fix for sure. So, just let it work, and fix | 1705 # directly, and it's hard to fix for sure. So, just let it work, and fix |
| 1713 # on a case by case basis. | 1706 # on a case by case basis. |
| 1714 # Note that child method defines __getattr__ as well, and forwards it here, | 1707 # Note that child method defines __getattr__ as well, and forwards it here, |
| 1715 # because _RietveldChangelistImpl is not cleaned up yet, and given | 1708 # because _RietveldChangelistImpl is not cleaned up yet, and given |
| 1716 # deprecation of Rietveld, it should probably be just removed. | 1709 # deprecation of Rietveld, it should probably be just removed. |
| 1717 # Until that time, avoid infinite recursion by bypassing __getattr__ | 1710 # Until that time, avoid infinite recursion by bypassing __getattr__ |
| 1718 # of implementation class. | 1711 # of implementation class. |
| 1719 return self._codereview_impl.__getattribute__(attr) | 1712 return self._codereview_impl.__getattribute__(attr) |
| (...skipping 112 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1832 """ | 1825 """ |
| 1833 raise NotImplementedError() | 1826 raise NotImplementedError() |
| 1834 | 1827 |
| 1835 def CannotTriggerTryJobReason(self): | 1828 def CannotTriggerTryJobReason(self): |
| 1836 """Returns reason (str) if unable trigger tryjobs on this CL or None.""" | 1829 """Returns reason (str) if unable trigger tryjobs on this CL or None.""" |
| 1837 raise NotImplementedError() | 1830 raise NotImplementedError() |
| 1838 | 1831 |
| 1839 def GetIssueOwner(self): | 1832 def GetIssueOwner(self): |
| 1840 raise NotImplementedError() | 1833 raise NotImplementedError() |
| 1841 | 1834 |
| 1842 def GetIssueProject(self): | 1835 def GetTryjobProperties(self, patchset=None): |
| 1843 raise NotImplementedError() | 1836 raise NotImplementedError() |
| 1844 | 1837 |
| 1845 | 1838 |
| 1846 class _RietveldChangelistImpl(_ChangelistCodereviewBase): | 1839 class _RietveldChangelistImpl(_ChangelistCodereviewBase): |
| 1847 def __init__(self, changelist, auth_config=None, rietveld_server=None): | 1840 def __init__(self, changelist, auth_config=None, rietveld_server=None): |
| 1848 super(_RietveldChangelistImpl, self).__init__(changelist) | 1841 super(_RietveldChangelistImpl, self).__init__(changelist) |
| 1849 assert settings, 'must be initialized in _ChangelistCodereviewBase' | 1842 assert settings, 'must be initialized in _ChangelistCodereviewBase' |
| 1850 if not rietveld_server: | 1843 if not rietveld_server: |
| 1851 settings.GetDefaultServerUrl() | 1844 settings.GetDefaultServerUrl() |
| 1852 | 1845 |
| (...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1913 def CannotTriggerTryJobReason(self): | 1906 def CannotTriggerTryJobReason(self): |
| 1914 props = self.GetIssueProperties() | 1907 props = self.GetIssueProperties() |
| 1915 if not props: | 1908 if not props: |
| 1916 return 'Rietveld doesn\'t know about your issue %s' % self.GetIssue() | 1909 return 'Rietveld doesn\'t know about your issue %s' % self.GetIssue() |
| 1917 if props.get('closed'): | 1910 if props.get('closed'): |
| 1918 return 'CL %s is closed' % self.GetIssue() | 1911 return 'CL %s is closed' % self.GetIssue() |
| 1919 if props.get('private'): | 1912 if props.get('private'): |
| 1920 return 'CL %s is private' % self.GetIssue() | 1913 return 'CL %s is private' % self.GetIssue() |
| 1921 return None | 1914 return None |
| 1922 | 1915 |
| 1916 def GetTryjobProperties(self, patchset=None): |
| 1917 """Returns dictionary of properties to launch tryjob.""" |
| 1918 project = (self.GetIssueProperties() or {}).get('project') |
| 1919 return { |
| 1920 'issue': self.GetIssue(), |
| 1921 'patch_project': project, |
| 1922 'patch_storage': 'rietveld', |
| 1923 'patchset': patchset or self.GetPatchset(), |
| 1924 'rietveld': self.GetCodereviewServer(), |
| 1925 } |
| 1926 |
| 1923 def GetApprovingReviewers(self): | 1927 def GetApprovingReviewers(self): |
| 1924 return get_approving_reviewers(self.GetIssueProperties()) | 1928 return get_approving_reviewers(self.GetIssueProperties()) |
| 1925 | 1929 |
| 1926 def GetIssueOwner(self): | 1930 def GetIssueOwner(self): |
| 1927 return (self.GetIssueProperties() or {}).get('owner_email') | 1931 return (self.GetIssueProperties() or {}).get('owner_email') |
| 1928 | 1932 |
| 1929 def GetIssueProject(self): | |
| 1930 return (self.GetIssueProperties() or {}).get('project') | |
| 1931 | |
| 1932 def AddComment(self, message): | 1933 def AddComment(self, message): |
| 1933 return self.RpcServer().add_comment(self.GetIssue(), message) | 1934 return self.RpcServer().add_comment(self.GetIssue(), message) |
| 1934 | 1935 |
| 1935 def GetStatus(self): | 1936 def GetStatus(self): |
| 1936 """Apply a rough heuristic to give a simple summary of an issue's review | 1937 """Apply a rough heuristic to give a simple summary of an issue's review |
| 1937 or CQ status, assuming adherence to a common workflow. | 1938 or CQ status, assuming adherence to a common workflow. |
| 1938 | 1939 |
| 1939 Returns None if no issue for this branch, or one of the following keywords: | 1940 Returns None if no issue for this branch, or one of the following keywords: |
| 1940 * 'error' - error from review tool (including deleted issues) | 1941 * 'error' - error from review tool (including deleted issues) |
| 1941 * 'unsent' - not sent for review | 1942 * 'unsent' - not sent for review |
| (...skipping 919 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 2861 """Sets the Commit-Queue label assuming canonical CQ config for Gerrit.""" | 2862 """Sets the Commit-Queue label assuming canonical CQ config for Gerrit.""" |
| 2862 vote_map = { | 2863 vote_map = { |
| 2863 _CQState.NONE: 0, | 2864 _CQState.NONE: 0, |
| 2864 _CQState.DRY_RUN: 1, | 2865 _CQState.DRY_RUN: 1, |
| 2865 _CQState.COMMIT : 2, | 2866 _CQState.COMMIT : 2, |
| 2866 } | 2867 } |
| 2867 gerrit_util.SetReview(self._GetGerritHost(), self.GetIssue(), | 2868 gerrit_util.SetReview(self._GetGerritHost(), self.GetIssue(), |
| 2868 labels={'Commit-Queue': vote_map[new_state]}) | 2869 labels={'Commit-Queue': vote_map[new_state]}) |
| 2869 | 2870 |
| 2870 def CannotTriggerTryJobReason(self): | 2871 def CannotTriggerTryJobReason(self): |
| 2871 # TODO(tandrii): implement for Gerrit. | 2872 try: |
| 2872 raise NotImplementedError() | 2873 data = self._GetChangeDetail() |
| 2874 except GerritIssueNotExists: |
| 2875 return 'Gerrit doesn\'t know about your issue %s' % self.GetIssue() |
| 2876 |
| 2877 if data['status'] in ('ABANDONED', 'MERGED'): |
| 2878 return 'CL %s is closed' % self.GetIssue() |
| 2879 |
| 2880 def GetTryjobProperties(self, patchset=None): |
| 2881 """Returns dictionary of properties to launch tryjob.""" |
| 2882 data = self._GetChangeDetail(['ALL_REVISIONS']) |
| 2883 patchset = int(patchset or self.GetPatchset()) |
| 2884 assert patchset |
| 2885 revision_data = None # Pylint wants it to be defined. |
| 2886 for revision_data in data['revisions'].itervalues(): |
| 2887 if int(revision_data['_number']) == patchset: |
| 2888 break |
| 2889 else: |
| 2890 raise Exception('Patchset %d is not known in Gerrit issue %d' % |
| 2891 (patchset, self.GetIssue())) |
| 2892 return { |
| 2893 'patch_issue': self.GetIssue(), |
| 2894 'patch_set': patchset or self.GetPatchset(), |
| 2895 'patch_project': data['project'], |
| 2896 'patch_storage': 'gerrit', |
| 2897 'patch_ref': revision_data['fetch']['http']['ref'], |
| 2898 'patch_repository_url': revision_data['fetch']['http']['url'], |
| 2899 'patch_gerrit_url': self.GetCodereviewServer(), |
| 2900 } |
| 2873 | 2901 |
| 2874 def GetIssueOwner(self): | 2902 def GetIssueOwner(self): |
| 2875 # TODO(tandrii): implement for Gerrit. | 2903 return self._GetChangeDetail(['DETAILED_ACCOUNTS'])['owner']['email'] |
| 2876 raise NotImplementedError() | |
| 2877 | |
| 2878 def GetIssueProject(self): | |
| 2879 # TODO(tandrii): implement for Gerrit. | |
| 2880 raise NotImplementedError() | |
| 2881 | 2904 |
| 2882 | 2905 |
| 2883 _CODEREVIEW_IMPLEMENTATIONS = { | 2906 _CODEREVIEW_IMPLEMENTATIONS = { |
| 2884 'rietveld': _RietveldChangelistImpl, | 2907 'rietveld': _RietveldChangelistImpl, |
| 2885 'gerrit': _GerritChangelistImpl, | 2908 'gerrit': _GerritChangelistImpl, |
| 2886 } | 2909 } |
| 2887 | 2910 |
| 2888 | 2911 |
| 2889 def _add_codereview_issue_select_options(parser, extra=""): | 2912 def _add_codereview_issue_select_options(parser, extra=""): |
| 2890 _add_codereview_select_options(parser) | 2913 _add_codereview_select_options(parser) |
| (...skipping 1930 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 4821 if bad_params: | 4844 if bad_params: |
| 4822 parser.error('Got properties with missing "=": %s' % bad_params) | 4845 parser.error('Got properties with missing "=": %s' % bad_params) |
| 4823 | 4846 |
| 4824 if args: | 4847 if args: |
| 4825 parser.error('Unknown arguments: %s' % args) | 4848 parser.error('Unknown arguments: %s' % args) |
| 4826 | 4849 |
| 4827 cl = Changelist(auth_config=auth_config) | 4850 cl = Changelist(auth_config=auth_config) |
| 4828 if not cl.GetIssue(): | 4851 if not cl.GetIssue(): |
| 4829 parser.error('Need to upload first') | 4852 parser.error('Need to upload first') |
| 4830 | 4853 |
| 4831 if cl.IsGerrit(): | |
| 4832 parser.error( | |
| 4833 'Not yet supported for Gerrit (http://crbug.com/599931).\n' | |
| 4834 'If your project has Commit Queue, dry run is a workaround:\n' | |
| 4835 ' git cl set-commit --dry-run') | |
| 4836 | |
| 4837 error_message = cl.CannotTriggerTryJobReason() | 4854 error_message = cl.CannotTriggerTryJobReason() |
| 4838 if error_message: | 4855 if error_message: |
| 4839 parser.error('Can\'t trigger try jobs: %s' % error_message) | 4856 parser.error('Can\'t trigger try jobs: %s' % error_message) |
| 4840 | 4857 |
| 4841 if options.bucket and options.master: | 4858 if options.bucket and options.master: |
| 4842 parser.error('Only one of --bucket and --master may be used.') | 4859 parser.error('Only one of --bucket and --master may be used.') |
| 4843 | 4860 |
| 4844 buckets = _get_bucket_map(cl, options, parser) | 4861 buckets = _get_bucket_map(cl, options, parser) |
| 4845 | 4862 |
| 4846 # If no bots are listed and we couldn't get a list based on PRESUBMIT files, | 4863 # If no bots are listed and we couldn't get a list based on PRESUBMIT files, |
| (...skipping 522 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 5369 if __name__ == '__main__': | 5386 if __name__ == '__main__': |
| 5370 # These affect sys.stdout so do it outside of main() to simplify mocks in | 5387 # These affect sys.stdout so do it outside of main() to simplify mocks in |
| 5371 # unit testing. | 5388 # unit testing. |
| 5372 fix_encoding.fix_encoding() | 5389 fix_encoding.fix_encoding() |
| 5373 setup_color.init() | 5390 setup_color.init() |
| 5374 try: | 5391 try: |
| 5375 sys.exit(main(sys.argv[1:])) | 5392 sys.exit(main(sys.argv[1:])) |
| 5376 except KeyboardInterrupt: | 5393 except KeyboardInterrupt: |
| 5377 sys.stderr.write('interrupted\n') | 5394 sys.stderr.write('interrupted\n') |
| 5378 sys.exit(1) | 5395 sys.exit(1) |
| OLD | NEW |