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 |