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 distutils.version import LooseVersion | 10 from distutils.version import LooseVersion |
| (...skipping 817 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 828 """Returns current branch or None. | 828 """Returns current branch or None. |
| 829 | 829 |
| 830 For refs/heads/* branches, returns just last part. For others, full ref. | 830 For refs/heads/* branches, returns just last part. For others, full ref. |
| 831 """ | 831 """ |
| 832 branchref = GetCurrentBranchRef() | 832 branchref = GetCurrentBranchRef() |
| 833 if branchref: | 833 if branchref: |
| 834 return ShortBranchName(branchref) | 834 return ShortBranchName(branchref) |
| 835 return None | 835 return None |
| 836 | 836 |
| 837 | 837 |
| 838 class _CQState(object): | |
| 839 """Enum for states of CL with respect to Commit Queue.""" | |
| 840 NONE = 'none' | |
| 841 DRY_RUN = 'dry_run' | |
| 842 COMMIT = 'commit' | |
| 843 | |
| 844 ALL_STATES = [NONE, DRY_RUN, COMMIT] | |
| 845 | |
| 846 | |
| 838 class _ParsedIssueNumberArgument(object): | 847 class _ParsedIssueNumberArgument(object): |
| 839 def __init__(self, issue=None, patchset=None, hostname=None): | 848 def __init__(self, issue=None, patchset=None, hostname=None): |
| 840 self.issue = issue | 849 self.issue = issue |
| 841 self.patchset = patchset | 850 self.patchset = patchset |
| 842 self.hostname = hostname | 851 self.hostname = hostname |
| 843 | 852 |
| 844 @property | 853 @property |
| 845 def valid(self): | 854 def valid(self): |
| 846 return self.issue is not None | 855 return self.issue is not None |
| 847 | 856 |
| (...skipping 542 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1390 print | 1399 print |
| 1391 print '--dependencies has been specified.' | 1400 print '--dependencies has been specified.' |
| 1392 print 'All dependent local branches will be re-uploaded.' | 1401 print 'All dependent local branches will be re-uploaded.' |
| 1393 print | 1402 print |
| 1394 # Remove the dependencies flag from args so that we do not end up in a | 1403 # Remove the dependencies flag from args so that we do not end up in a |
| 1395 # loop. | 1404 # loop. |
| 1396 orig_args.remove('--dependencies') | 1405 orig_args.remove('--dependencies') |
| 1397 ret = upload_branch_deps(self, orig_args) | 1406 ret = upload_branch_deps(self, orig_args) |
| 1398 return ret | 1407 return ret |
| 1399 | 1408 |
| 1409 def SetCQState(self, new_state): | |
| 1410 """Update the CQ state for latest patchset. | |
| 1411 | |
| 1412 Issue must have been already uploaded and known. | |
| 1413 """ | |
| 1414 assert new_state in _CQState.ALL_STATES | |
| 1415 assert self.GetIssue() | |
| 1416 return self._codereview_impl.SetCQState(new_state) | |
| 1417 | |
| 1400 # Forward methods to codereview specific implementation. | 1418 # Forward methods to codereview specific implementation. |
| 1401 | 1419 |
| 1402 def CloseIssue(self): | 1420 def CloseIssue(self): |
| 1403 return self._codereview_impl.CloseIssue() | 1421 return self._codereview_impl.CloseIssue() |
| 1404 | 1422 |
| 1405 def GetStatus(self): | 1423 def GetStatus(self): |
| 1406 return self._codereview_impl.GetStatus() | 1424 return self._codereview_impl.GetStatus() |
| 1407 | 1425 |
| 1408 def GetCodereviewServer(self): | 1426 def GetCodereviewServer(self): |
| 1409 return self._codereview_impl.GetCodereviewServer() | 1427 return self._codereview_impl.GetCodereviewServer() |
| (...skipping 105 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1515 | 1533 |
| 1516 Arguments: | 1534 Arguments: |
| 1517 force: whether to skip confirmation questions. | 1535 force: whether to skip confirmation questions. |
| 1518 """ | 1536 """ |
| 1519 raise NotImplementedError() | 1537 raise NotImplementedError() |
| 1520 | 1538 |
| 1521 def CMDUploadChange(self, options, args, change): | 1539 def CMDUploadChange(self, options, args, change): |
| 1522 """Uploads a change to codereview.""" | 1540 """Uploads a change to codereview.""" |
| 1523 raise NotImplementedError() | 1541 raise NotImplementedError() |
| 1524 | 1542 |
| 1543 def SetCQState(self, new_state): | |
| 1544 """Update the CQ state for latest patchset. | |
| 1545 | |
| 1546 Issue must have been already uploaded and known. | |
| 1547 """ | |
| 1548 raise NotImplementedError() | |
| 1549 | |
| 1525 | 1550 |
| 1526 class _RietveldChangelistImpl(_ChangelistCodereviewBase): | 1551 class _RietveldChangelistImpl(_ChangelistCodereviewBase): |
| 1527 def __init__(self, changelist, auth_config=None, rietveld_server=None): | 1552 def __init__(self, changelist, auth_config=None, rietveld_server=None): |
| 1528 super(_RietveldChangelistImpl, self).__init__(changelist) | 1553 super(_RietveldChangelistImpl, self).__init__(changelist) |
| 1529 assert settings, 'must be initialized in _ChangelistCodereviewBase' | 1554 assert settings, 'must be initialized in _ChangelistCodereviewBase' |
| 1530 settings.GetDefaultServerUrl() | 1555 settings.GetDefaultServerUrl() |
| 1531 | 1556 |
| 1532 self._rietveld_server = rietveld_server | 1557 self._rietveld_server = rietveld_server |
| 1533 self._auth_config = auth_config | 1558 self._auth_config = auth_config |
| 1534 self._props = None | 1559 self._props = None |
| (...skipping 157 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1692 def GetCodereviewServerSetting(self): | 1717 def GetCodereviewServerSetting(self): |
| 1693 """Returns the git setting that stores this change's rietveld server.""" | 1718 """Returns the git setting that stores this change's rietveld server.""" |
| 1694 branch = self.GetBranch() | 1719 branch = self.GetBranch() |
| 1695 if branch: | 1720 if branch: |
| 1696 return 'branch.%s.rietveldserver' % branch | 1721 return 'branch.%s.rietveldserver' % branch |
| 1697 return None | 1722 return None |
| 1698 | 1723 |
| 1699 def GetRieveldObjForPresubmit(self): | 1724 def GetRieveldObjForPresubmit(self): |
| 1700 return self.RpcServer() | 1725 return self.RpcServer() |
| 1701 | 1726 |
| 1727 def SetCQState(self, new_state): | |
| 1728 props = self.GetIssueProperties() | |
| 1729 if props.get('private'): | |
| 1730 DieWithError('Cannot set-commit on private issue') | |
| 1731 | |
| 1732 if new_state == _CQState.COMMIT: | |
| 1733 self.SetFlag('commit', '1') | |
| 1734 elif new_state == _CQState.NONE: | |
| 1735 self.SetFlag('commit', '0') | |
| 1736 else: | |
| 1737 raise NotImplementedError() | |
|
Sergiy Byelozyorov
2016/04/13 17:15:02
What about DRY_RUN?
tandrii(chromium)
2016/04/13 17:17:16
wasn't supported before, so whatever.
| |
| 1738 | |
| 1739 | |
| 1702 def CMDPatchWithParsedIssue(self, parsed_issue_arg, reject, nocommit, | 1740 def CMDPatchWithParsedIssue(self, parsed_issue_arg, reject, nocommit, |
| 1703 directory): | 1741 directory): |
| 1704 # TODO(maruel): Use apply_issue.py | 1742 # TODO(maruel): Use apply_issue.py |
| 1705 | 1743 |
| 1706 # PatchIssue should never be called with a dirty tree. It is up to the | 1744 # PatchIssue should never be called with a dirty tree. It is up to the |
| 1707 # caller to check this, but just in case we assert here since the | 1745 # caller to check this, but just in case we assert here since the |
| 1708 # consequences of the caller not checking this could be dire. | 1746 # consequences of the caller not checking this could be dire. |
| 1709 assert(not git_common.is_dirty_git_tree('apply')) | 1747 assert(not git_common.is_dirty_git_tree('apply')) |
| 1710 assert(parsed_issue_arg.valid) | 1748 assert(parsed_issue_arg.valid) |
| 1711 self._changelist.issue = parsed_issue_arg.issue | 1749 self._changelist.issue = parsed_issue_arg.issue |
| (...skipping 217 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1929 backup_file = open(backup_path, 'w') | 1967 backup_file = open(backup_path, 'w') |
| 1930 backup_file.write(change_desc.description) | 1968 backup_file.write(change_desc.description) |
| 1931 backup_file.close() | 1969 backup_file.close() |
| 1932 raise | 1970 raise |
| 1933 | 1971 |
| 1934 if not self.GetIssue(): | 1972 if not self.GetIssue(): |
| 1935 self.SetIssue(issue) | 1973 self.SetIssue(issue) |
| 1936 self.SetPatchset(patchset) | 1974 self.SetPatchset(patchset) |
| 1937 | 1975 |
| 1938 if options.use_commit_queue: | 1976 if options.use_commit_queue: |
| 1939 self.SetFlag('commit', '1') | 1977 self.SetCQState(_CQState.COMMIT) |
| 1940 return 0 | 1978 return 0 |
| 1941 | 1979 |
| 1942 | 1980 |
| 1943 class _GerritChangelistImpl(_ChangelistCodereviewBase): | 1981 class _GerritChangelistImpl(_ChangelistCodereviewBase): |
| 1944 def __init__(self, changelist, auth_config=None): | 1982 def __init__(self, changelist, auth_config=None): |
| 1945 # auth_config is Rietveld thing, kept here to preserve interface only. | 1983 # auth_config is Rietveld thing, kept here to preserve interface only. |
| 1946 super(_GerritChangelistImpl, self).__init__(changelist) | 1984 super(_GerritChangelistImpl, self).__init__(changelist) |
| 1947 self._change_id = None | 1985 self._change_id = None |
| 1948 # Lazily cached values. | 1986 # Lazily cached values. |
| 1949 self._gerrit_server = None # e.g. https://chromium-review.googlesource.com | 1987 self._gerrit_server = None # e.g. https://chromium-review.googlesource.com |
| (...skipping 493 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 2443 log_desc = options.message or CreateDescriptionFromLog(args) | 2481 log_desc = options.message or CreateDescriptionFromLog(args) |
| 2444 git_command = ['commit', '--amend', '-m', log_desc] | 2482 git_command = ['commit', '--amend', '-m', log_desc] |
| 2445 RunGit(git_command) | 2483 RunGit(git_command) |
| 2446 new_log_desc = CreateDescriptionFromLog(args) | 2484 new_log_desc = CreateDescriptionFromLog(args) |
| 2447 if git_footers.get_footer_change_id(new_log_desc): | 2485 if git_footers.get_footer_change_id(new_log_desc): |
| 2448 print 'git-cl: Added Change-Id to commit message.' | 2486 print 'git-cl: Added Change-Id to commit message.' |
| 2449 return new_log_desc | 2487 return new_log_desc |
| 2450 else: | 2488 else: |
| 2451 print >> sys.stderr, 'ERROR: Gerrit commit-msg hook not available.' | 2489 print >> sys.stderr, 'ERROR: Gerrit commit-msg hook not available.' |
| 2452 | 2490 |
| 2491 def SetCQState(self, new_state): | |
| 2492 """Sets the Commit-Queue label assuming canonical CQ config for Gerrit.""" | |
| 2493 # TODO(tandrii): maybe allow configurability in codereview.settings or by | |
| 2494 # self-discovery of label config for this CL using REST API. | |
| 2495 vote_map = { | |
| 2496 _CQState.NONE: 0, | |
| 2497 _CQState.DRY_RUN: 1, | |
| 2498 _CQState.COMMIT : 2, | |
| 2499 } | |
| 2500 gerrit_util.SetReview(self._GetGerritHost(), self.GetIssue(), | |
| 2501 labels={'Commit-Queue': vote_map[new_state]}) | |
| 2502 | |
| 2453 | 2503 |
| 2454 _CODEREVIEW_IMPLEMENTATIONS = { | 2504 _CODEREVIEW_IMPLEMENTATIONS = { |
| 2455 'rietveld': _RietveldChangelistImpl, | 2505 'rietveld': _RietveldChangelistImpl, |
| 2456 'gerrit': _GerritChangelistImpl, | 2506 'gerrit': _GerritChangelistImpl, |
| 2457 } | 2507 } |
| 2458 | 2508 |
| 2459 | 2509 |
| 2460 class ChangeDescription(object): | 2510 class ChangeDescription(object): |
| 2461 """Contains a parsed form of the change description.""" | 2511 """Contains a parsed form of the change description.""" |
| 2462 R_LINE = r'^[ \t]*(TBR|R)[ \t]*=[ \t]*(.*?)[ \t]*$' | 2512 R_LINE = r'^[ \t]*(TBR|R)[ \t]*=[ \t]*(.*?)[ \t]*$' |
| (...skipping 1596 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 4059 | 4109 |
| 4060 print "The tree is %s" % status | 4110 print "The tree is %s" % status |
| 4061 print | 4111 print |
| 4062 print GetTreeStatusReason() | 4112 print GetTreeStatusReason() |
| 4063 if status != 'open': | 4113 if status != 'open': |
| 4064 return 1 | 4114 return 1 |
| 4065 return 0 | 4115 return 0 |
| 4066 | 4116 |
| 4067 | 4117 |
| 4068 def CMDtry(parser, args): | 4118 def CMDtry(parser, args): |
| 4069 """Triggers a try job through BuildBucket.""" | 4119 """Triggers try jobs through BuildBucket.""" |
| 4070 group = optparse.OptionGroup(parser, "Try job options") | 4120 group = optparse.OptionGroup(parser, "Try job options") |
| 4071 group.add_option( | 4121 group.add_option( |
| 4072 "-b", "--bot", action="append", | 4122 "-b", "--bot", action="append", |
| 4073 help=("IMPORTANT: specify ONE builder per --bot flag. Use it multiple " | 4123 help=("IMPORTANT: specify ONE builder per --bot flag. Use it multiple " |
| 4074 "times to specify multiple builders. ex: " | 4124 "times to specify multiple builders. ex: " |
| 4075 "'-b win_rel -b win_layout'. See " | 4125 "'-b win_rel -b win_layout'. See " |
| 4076 "the try server waterfall for the builders name and the tests " | 4126 "the try server waterfall for the builders name and the tests " |
| 4077 "available.")) | 4127 "available.")) |
| 4078 group.add_option( | 4128 group.add_option( |
| 4079 "-m", "--master", default='', | 4129 "-m", "--master", default='', |
| (...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 4118 if bad_params: | 4168 if bad_params: |
| 4119 parser.error('Got properties with missing "=": %s' % bad_params) | 4169 parser.error('Got properties with missing "=": %s' % bad_params) |
| 4120 | 4170 |
| 4121 if args: | 4171 if args: |
| 4122 parser.error('Unknown arguments: %s' % args) | 4172 parser.error('Unknown arguments: %s' % args) |
| 4123 | 4173 |
| 4124 cl = Changelist(auth_config=auth_config) | 4174 cl = Changelist(auth_config=auth_config) |
| 4125 if not cl.GetIssue(): | 4175 if not cl.GetIssue(): |
| 4126 parser.error('Need to upload first') | 4176 parser.error('Need to upload first') |
| 4127 | 4177 |
| 4178 if cl.IsGerrit(): | |
| 4179 parser.error( | |
| 4180 'Not yet supported for Gerrit (http://crbug.com/599931).\n' | |
| 4181 'If your project has Commit Queue, dry run is a workaround:\n' | |
| 4182 ' git cl set-commit --dry-run') | |
| 4183 # Code below assumes Rietveld issue. | |
| 4184 # TODO(tandrii): actually implement for Gerrit http://crbug.com/599931. | |
| 4185 | |
| 4128 props = cl.GetIssueProperties() | 4186 props = cl.GetIssueProperties() |
| 4129 if props.get('closed'): | 4187 if props.get('closed'): |
| 4130 parser.error('Cannot send tryjobs for a closed CL') | 4188 parser.error('Cannot send tryjobs for a closed CL') |
| 4131 | 4189 |
| 4132 if props.get('private'): | 4190 if props.get('private'): |
| 4133 parser.error('Cannot use trybots with private issue') | 4191 parser.error('Cannot use trybots with private issue') |
| 4134 | 4192 |
| 4135 if not options.name: | 4193 if not options.name: |
| 4136 options.name = cl.GetBranch() | 4194 options.name = cl.GetBranch() |
| 4137 | 4195 |
| (...skipping 201 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 4339 if not issue_url: | 4397 if not issue_url: |
| 4340 print >> sys.stderr, 'ERROR No issue to open' | 4398 print >> sys.stderr, 'ERROR No issue to open' |
| 4341 return 1 | 4399 return 1 |
| 4342 | 4400 |
| 4343 webbrowser.open(issue_url) | 4401 webbrowser.open(issue_url) |
| 4344 return 0 | 4402 return 0 |
| 4345 | 4403 |
| 4346 | 4404 |
| 4347 def CMDset_commit(parser, args): | 4405 def CMDset_commit(parser, args): |
| 4348 """Sets the commit bit to trigger the Commit Queue.""" | 4406 """Sets the commit bit to trigger the Commit Queue.""" |
| 4407 parser.add_option('-d', '--dry-run', action='store_true', | |
| 4408 help='trigger in dry run mode') | |
| 4409 parser.add_option('-c', '--clear', action='store_true', | |
| 4410 help='stop CQ run, if any') | |
| 4349 auth.add_auth_options(parser) | 4411 auth.add_auth_options(parser) |
| 4350 options, args = parser.parse_args(args) | 4412 options, args = parser.parse_args(args) |
| 4351 auth_config = auth.extract_auth_config_from_options(options) | 4413 auth_config = auth.extract_auth_config_from_options(options) |
| 4352 if args: | 4414 if args: |
| 4353 parser.error('Unrecognized args: %s' % ' '.join(args)) | 4415 parser.error('Unrecognized args: %s' % ' '.join(args)) |
| 4416 if options.dry_run and options.clear: | |
| 4417 parser.error('Make up your mind: both --dry-run and --clear not allowed') | |
| 4418 | |
| 4354 cl = Changelist(auth_config=auth_config) | 4419 cl = Changelist(auth_config=auth_config) |
| 4355 props = cl.GetIssueProperties() | 4420 if options.clear: |
| 4356 if props.get('private'): | 4421 state = _CQState.CLEAR |
| 4357 parser.error('Cannot set commit on private issue') | 4422 elif options.dry_run: |
| 4358 cl.SetFlag('commit', '1') | 4423 state = _CQState.DRY_RUN |
| 4424 else: | |
| 4425 state = _CQState.COMMIT | |
| 4426 if not cl.GetIssue(): | |
| 4427 parser.error('Must upload the issue first') | |
| 4428 cl.SetCQState(state) | |
| 4359 return 0 | 4429 return 0 |
| 4360 | 4430 |
| 4361 | 4431 |
| 4362 def CMDset_close(parser, args): | 4432 def CMDset_close(parser, args): |
| 4363 """Closes the issue.""" | 4433 """Closes the issue.""" |
| 4364 auth.add_auth_options(parser) | 4434 auth.add_auth_options(parser) |
| 4365 options, args = parser.parse_args(args) | 4435 options, args = parser.parse_args(args) |
| 4366 auth_config = auth.extract_auth_config_from_options(options) | 4436 auth_config = auth.extract_auth_config_from_options(options) |
| 4367 if args: | 4437 if args: |
| 4368 parser.error('Unrecognized args: %s' % ' '.join(args)) | 4438 parser.error('Unrecognized args: %s' % ' '.join(args)) |
| (...skipping 339 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 4708 if __name__ == '__main__': | 4778 if __name__ == '__main__': |
| 4709 # These affect sys.stdout so do it outside of main() to simplify mocks in | 4779 # These affect sys.stdout so do it outside of main() to simplify mocks in |
| 4710 # unit testing. | 4780 # unit testing. |
| 4711 fix_encoding.fix_encoding() | 4781 fix_encoding.fix_encoding() |
| 4712 setup_color.init() | 4782 setup_color.init() |
| 4713 try: | 4783 try: |
| 4714 sys.exit(main(sys.argv[1:])) | 4784 sys.exit(main(sys.argv[1:])) |
| 4715 except KeyboardInterrupt: | 4785 except KeyboardInterrupt: |
| 4716 sys.stderr.write('interrupted\n') | 4786 sys.stderr.write('interrupted\n') |
| 4717 sys.exit(1) | 4787 sys.exit(1) |
| OLD | NEW |