Chromium Code Reviews| 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: | 470 if options.clobber: |
|
Michael Achenbach
2016/11/04 14:44:58
Looks like this is double now.
tandrii(chromium)
2016/11/04 14:48:17
true.
| |
| 474 parameters['properties']['clobber'] = True | 471 parameters['properties']['clobber'] = True |
| 475 | 472 |
| 476 tags = [ | 473 tags = [ |
| 477 'builder:%s' % builder, | 474 'builder:%s' % builder, |
| 478 'buildset:%s' % buildset, | 475 'buildset:%s' % buildset, |
| 479 'user_agent:git_cl_try', | 476 'user_agent:git_cl_try', |
| 480 ] | 477 ] |
| 481 if master: | 478 if master: |
| 482 parameters['properties']['master'] = master | 479 parameters['properties']['master'] = master |
| 483 tags.append('master:%s' % master) | 480 tags.append('master:%s' % master) |
| (...skipping 1200 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1684 def GetStatus(self): | 1681 def GetStatus(self): |
| 1685 return self._codereview_impl.GetStatus() | 1682 return self._codereview_impl.GetStatus() |
| 1686 | 1683 |
| 1687 def GetCodereviewServer(self): | 1684 def GetCodereviewServer(self): |
| 1688 return self._codereview_impl.GetCodereviewServer() | 1685 return self._codereview_impl.GetCodereviewServer() |
| 1689 | 1686 |
| 1690 def GetIssueOwner(self): | 1687 def GetIssueOwner(self): |
| 1691 """Get owner from codereview, which may differ from this checkout.""" | 1688 """Get owner from codereview, which may differ from this checkout.""" |
| 1692 return self._codereview_impl.GetIssueOwner() | 1689 return self._codereview_impl.GetIssueOwner() |
| 1693 | 1690 |
| 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): | 1691 def GetApprovingReviewers(self): |
| 1701 return self._codereview_impl.GetApprovingReviewers() | 1692 return self._codereview_impl.GetApprovingReviewers() |
| 1702 | 1693 |
| 1703 def GetMostRecentPatchset(self): | 1694 def GetMostRecentPatchset(self): |
| 1704 return self._codereview_impl.GetMostRecentPatchset() | 1695 return self._codereview_impl.GetMostRecentPatchset() |
| 1705 | 1696 |
| 1706 def CannotTriggerTryJobReason(self): | 1697 def CannotTriggerTryJobReason(self): |
| 1707 """Returns reason (str) if unable trigger tryjobs on this CL or None.""" | 1698 """Returns reason (str) if unable trigger tryjobs on this CL or None.""" |
| 1708 return self._codereview_impl.CannotTriggerTryJobReason() | 1699 return self._codereview_impl.CannotTriggerTryJobReason() |
| 1709 | 1700 |
| 1701 def GetTryjobProperties(self, patchset=None): | |
| 1702 """Returns dictionary of properties to launch tryjob.""" | |
| 1703 return self._codereview_impl.GetTryjobProperties(patchset=patchset) | |
| 1704 | |
| 1710 def __getattr__(self, attr): | 1705 def __getattr__(self, attr): |
| 1711 # This is because lots of untested code accesses Rietveld-specific stuff | 1706 # 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 | 1707 # directly, and it's hard to fix for sure. So, just let it work, and fix |
| 1713 # on a case by case basis. | 1708 # on a case by case basis. |
| 1714 # Note that child method defines __getattr__ as well, and forwards it here, | 1709 # Note that child method defines __getattr__ as well, and forwards it here, |
| 1715 # because _RietveldChangelistImpl is not cleaned up yet, and given | 1710 # because _RietveldChangelistImpl is not cleaned up yet, and given |
| 1716 # deprecation of Rietveld, it should probably be just removed. | 1711 # deprecation of Rietveld, it should probably be just removed. |
| 1717 # Until that time, avoid infinite recursion by bypassing __getattr__ | 1712 # Until that time, avoid infinite recursion by bypassing __getattr__ |
| 1718 # of implementation class. | 1713 # of implementation class. |
| 1719 return self._codereview_impl.__getattribute__(attr) | 1714 return self._codereview_impl.__getattribute__(attr) |
| (...skipping 112 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1832 """ | 1827 """ |
| 1833 raise NotImplementedError() | 1828 raise NotImplementedError() |
| 1834 | 1829 |
| 1835 def CannotTriggerTryJobReason(self): | 1830 def CannotTriggerTryJobReason(self): |
| 1836 """Returns reason (str) if unable trigger tryjobs on this CL or None.""" | 1831 """Returns reason (str) if unable trigger tryjobs on this CL or None.""" |
| 1837 raise NotImplementedError() | 1832 raise NotImplementedError() |
| 1838 | 1833 |
| 1839 def GetIssueOwner(self): | 1834 def GetIssueOwner(self): |
| 1840 raise NotImplementedError() | 1835 raise NotImplementedError() |
| 1841 | 1836 |
| 1842 def GetIssueProject(self): | 1837 def GetTryjobProperties(self, patchset=None): |
| 1843 raise NotImplementedError() | 1838 raise NotImplementedError() |
| 1844 | 1839 |
| 1845 | 1840 |
| 1846 class _RietveldChangelistImpl(_ChangelistCodereviewBase): | 1841 class _RietveldChangelistImpl(_ChangelistCodereviewBase): |
| 1847 def __init__(self, changelist, auth_config=None, rietveld_server=None): | 1842 def __init__(self, changelist, auth_config=None, rietveld_server=None): |
| 1848 super(_RietveldChangelistImpl, self).__init__(changelist) | 1843 super(_RietveldChangelistImpl, self).__init__(changelist) |
| 1849 assert settings, 'must be initialized in _ChangelistCodereviewBase' | 1844 assert settings, 'must be initialized in _ChangelistCodereviewBase' |
| 1850 if not rietveld_server: | 1845 if not rietveld_server: |
| 1851 settings.GetDefaultServerUrl() | 1846 settings.GetDefaultServerUrl() |
| 1852 | 1847 |
| (...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1913 def CannotTriggerTryJobReason(self): | 1908 def CannotTriggerTryJobReason(self): |
| 1914 props = self.GetIssueProperties() | 1909 props = self.GetIssueProperties() |
| 1915 if not props: | 1910 if not props: |
| 1916 return 'Rietveld doesn\'t know about your issue %s' % self.GetIssue() | 1911 return 'Rietveld doesn\'t know about your issue %s' % self.GetIssue() |
| 1917 if props.get('closed'): | 1912 if props.get('closed'): |
| 1918 return 'CL %s is closed' % self.GetIssue() | 1913 return 'CL %s is closed' % self.GetIssue() |
| 1919 if props.get('private'): | 1914 if props.get('private'): |
| 1920 return 'CL %s is private' % self.GetIssue() | 1915 return 'CL %s is private' % self.GetIssue() |
| 1921 return None | 1916 return None |
| 1922 | 1917 |
| 1918 def GetTryjobProperties(self, patchset=None): | |
| 1919 """Returns dictionary of properties to launch tryjob.""" | |
| 1920 project = (self.GetIssueProperties() or {}).get('project') | |
| 1921 return { | |
| 1922 'issue': self.GetIssue(), | |
| 1923 'patch_project': project, | |
| 1924 'patch_storage': 'rietveld', | |
| 1925 'patchset': patchset or self.GetPatchset(), | |
| 1926 'rietveld': self.GetCodereviewServer(), | |
| 1927 } | |
| 1928 | |
| 1923 def GetApprovingReviewers(self): | 1929 def GetApprovingReviewers(self): |
| 1924 return get_approving_reviewers(self.GetIssueProperties()) | 1930 return get_approving_reviewers(self.GetIssueProperties()) |
| 1925 | 1931 |
| 1926 def GetIssueOwner(self): | 1932 def GetIssueOwner(self): |
| 1927 return (self.GetIssueProperties() or {}).get('owner_email') | 1933 return (self.GetIssueProperties() or {}).get('owner_email') |
| 1928 | 1934 |
| 1929 def GetIssueProject(self): | |
| 1930 return (self.GetIssueProperties() or {}).get('project') | |
| 1931 | |
| 1932 def AddComment(self, message): | 1935 def AddComment(self, message): |
| 1933 return self.RpcServer().add_comment(self.GetIssue(), message) | 1936 return self.RpcServer().add_comment(self.GetIssue(), message) |
| 1934 | 1937 |
| 1935 def GetStatus(self): | 1938 def GetStatus(self): |
| 1936 """Apply a rough heuristic to give a simple summary of an issue's review | 1939 """Apply a rough heuristic to give a simple summary of an issue's review |
| 1937 or CQ status, assuming adherence to a common workflow. | 1940 or CQ status, assuming adherence to a common workflow. |
| 1938 | 1941 |
| 1939 Returns None if no issue for this branch, or one of the following keywords: | 1942 Returns None if no issue for this branch, or one of the following keywords: |
| 1940 * 'error' - error from review tool (including deleted issues) | 1943 * 'error' - error from review tool (including deleted issues) |
| 1941 * 'unsent' - not sent for review | 1944 * '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.""" | 2864 """Sets the Commit-Queue label assuming canonical CQ config for Gerrit.""" |
| 2862 vote_map = { | 2865 vote_map = { |
| 2863 _CQState.NONE: 0, | 2866 _CQState.NONE: 0, |
| 2864 _CQState.DRY_RUN: 1, | 2867 _CQState.DRY_RUN: 1, |
| 2865 _CQState.COMMIT : 2, | 2868 _CQState.COMMIT : 2, |
| 2866 } | 2869 } |
| 2867 gerrit_util.SetReview(self._GetGerritHost(), self.GetIssue(), | 2870 gerrit_util.SetReview(self._GetGerritHost(), self.GetIssue(), |
| 2868 labels={'Commit-Queue': vote_map[new_state]}) | 2871 labels={'Commit-Queue': vote_map[new_state]}) |
| 2869 | 2872 |
| 2870 def CannotTriggerTryJobReason(self): | 2873 def CannotTriggerTryJobReason(self): |
| 2871 # TODO(tandrii): implement for Gerrit. | 2874 try: |
| 2872 raise NotImplementedError() | 2875 data = self._GetChangeDetail() |
| 2876 except GerritIssueNotExists: | |
| 2877 return 'Gerrit doesn\'t know about your issue %s' % self.GetIssue() | |
| 2878 | |
| 2879 if data['status'] in ('ABANDONED', 'MERGED'): | |
| 2880 return 'CL %s is closed' % (self.GetIssue()) | |
|
Michael Achenbach
2016/11/04 14:44:58
nit: no parentheses needed
tandrii(chromium)
2016/11/04 14:48:17
Done.
| |
| 2881 | |
| 2882 def GetTryjobProperties(self, patchset=None): | |
| 2883 """Returns dictionary of properties to launch tryjob.""" | |
| 2884 data = self._GetChangeDetail(['ALL_REVISIONS']) | |
| 2885 patchset = int(patchset or self.GetPatchset()) | |
| 2886 assert patchset | |
| 2887 revision_data = None # Pylint wants it to be defined. | |
| 2888 for revision_data in data['revisions'].itervalues(): | |
| 2889 if int(revision_data['_number']) == patchset: | |
| 2890 break | |
| 2891 else: | |
| 2892 raise Exception('Patchset %d is not known in Gerrit issue %d' % | |
| 2893 (patchset, self.GetIssue())) | |
| 2894 return { | |
| 2895 'patch_issue': self.GetIssue(), | |
| 2896 'patch_set': patchset or self.GetPatchset(), | |
| 2897 'patch_project': data['project'], | |
| 2898 'patch_storage': 'gerrit', | |
| 2899 'patch_ref': revision_data['fetch']['http']['ref'], | |
| 2900 'patch_repository_url': revision_data['fetch']['http']['url'], | |
| 2901 'patch_gerrit_url': self.GetCodereviewServer(), | |
| 2902 } | |
| 2873 | 2903 |
| 2874 def GetIssueOwner(self): | 2904 def GetIssueOwner(self): |
| 2875 # TODO(tandrii): implement for Gerrit. | 2905 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 | 2906 |
| 2882 | 2907 |
| 2883 _CODEREVIEW_IMPLEMENTATIONS = { | 2908 _CODEREVIEW_IMPLEMENTATIONS = { |
| 2884 'rietveld': _RietveldChangelistImpl, | 2909 'rietveld': _RietveldChangelistImpl, |
| 2885 'gerrit': _GerritChangelistImpl, | 2910 'gerrit': _GerritChangelistImpl, |
| 2886 } | 2911 } |
| 2887 | 2912 |
| 2888 | 2913 |
| 2889 def _add_codereview_issue_select_options(parser, extra=""): | 2914 def _add_codereview_issue_select_options(parser, extra=""): |
| 2890 _add_codereview_select_options(parser) | 2915 _add_codereview_select_options(parser) |
| (...skipping 1930 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 4821 if bad_params: | 4846 if bad_params: |
| 4822 parser.error('Got properties with missing "=": %s' % bad_params) | 4847 parser.error('Got properties with missing "=": %s' % bad_params) |
| 4823 | 4848 |
| 4824 if args: | 4849 if args: |
| 4825 parser.error('Unknown arguments: %s' % args) | 4850 parser.error('Unknown arguments: %s' % args) |
| 4826 | 4851 |
| 4827 cl = Changelist(auth_config=auth_config) | 4852 cl = Changelist(auth_config=auth_config) |
| 4828 if not cl.GetIssue(): | 4853 if not cl.GetIssue(): |
| 4829 parser.error('Need to upload first') | 4854 parser.error('Need to upload first') |
| 4830 | 4855 |
| 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() | 4856 error_message = cl.CannotTriggerTryJobReason() |
| 4838 if error_message: | 4857 if error_message: |
| 4839 parser.error('Can\'t trigger try jobs: %s' % error_message) | 4858 parser.error('Can\'t trigger try jobs: %s' % error_message) |
| 4840 | 4859 |
| 4841 if options.bucket and options.master: | 4860 if options.bucket and options.master: |
| 4842 parser.error('Only one of --bucket and --master may be used.') | 4861 parser.error('Only one of --bucket and --master may be used.') |
| 4843 | 4862 |
| 4844 buckets = _get_bucket_map(cl, options, parser) | 4863 buckets = _get_bucket_map(cl, options, parser) |
| 4845 | 4864 |
| 4846 # If no bots are listed and we couldn't get a list based on PRESUBMIT files, | 4865 # 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__': | 5388 if __name__ == '__main__': |
| 5370 # These affect sys.stdout so do it outside of main() to simplify mocks in | 5389 # These affect sys.stdout so do it outside of main() to simplify mocks in |
| 5371 # unit testing. | 5390 # unit testing. |
| 5372 fix_encoding.fix_encoding() | 5391 fix_encoding.fix_encoding() |
| 5373 setup_color.init() | 5392 setup_color.init() |
| 5374 try: | 5393 try: |
| 5375 sys.exit(main(sys.argv[1:])) | 5394 sys.exit(main(sys.argv[1:])) |
| 5376 except KeyboardInterrupt: | 5395 except KeyboardInterrupt: |
| 5377 sys.stderr.write('interrupted\n') | 5396 sys.stderr.write('interrupted\n') |
| 5378 sys.exit(1) | 5397 sys.exit(1) |
| OLD | NEW |