| 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 863 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 874 self.issue = issue or None | 874 self.issue = issue or None |
| 875 self.has_description = False | 875 self.has_description = False |
| 876 self.description = None | 876 self.description = None |
| 877 self.lookedup_patchset = False | 877 self.lookedup_patchset = False |
| 878 self.patchset = None | 878 self.patchset = None |
| 879 self.cc = None | 879 self.cc = None |
| 880 self.watchers = () | 880 self.watchers = () |
| 881 self._remote = None | 881 self._remote = None |
| 882 | 882 |
| 883 self._codereview_impl = None | 883 self._codereview_impl = None |
| 884 self._codereview = None |
| 884 self._load_codereview_impl(codereview, **kwargs) | 885 self._load_codereview_impl(codereview, **kwargs) |
| 886 assert self._codereview_impl |
| 887 assert self._codereview in _CODEREVIEW_IMPLEMENTATIONS |
| 885 | 888 |
| 886 def _load_codereview_impl(self, codereview=None, **kwargs): | 889 def _load_codereview_impl(self, codereview=None, **kwargs): |
| 887 if codereview: | 890 if codereview: |
| 888 codereview = codereview.lower() | 891 assert codereview in _CODEREVIEW_IMPLEMENTATIONS |
| 889 if codereview == 'gerrit': | 892 cls = _CODEREVIEW_IMPLEMENTATIONS[codereview] |
| 890 self._codereview_impl = _GerritChangelistImpl(self, **kwargs) | 893 self._codereview = codereview |
| 891 elif codereview == 'rietveld': | 894 self._codereview_impl = cls(self, **kwargs) |
| 892 self._codereview_impl = _RietveldChangelistImpl(self, **kwargs) | |
| 893 else: | |
| 894 assert codereview in ('rietveld', 'gerrit') | |
| 895 return | 895 return |
| 896 | 896 |
| 897 # Automatic selection based on issue number set for a current branch. | 897 # Automatic selection based on issue number set for a current branch. |
| 898 # Rietveld takes precedence over Gerrit. | 898 # Rietveld takes precedence over Gerrit. |
| 899 assert not self.issue | 899 assert not self.issue |
| 900 # Whether we find issue or not, we are doing the lookup. | 900 # Whether we find issue or not, we are doing the lookup. |
| 901 self.lookedup_issue = True | 901 self.lookedup_issue = True |
| 902 for cls in [_RietveldChangelistImpl, _GerritChangelistImpl]: | 902 for codereview, cls in _CODEREVIEW_IMPLEMENTATIONS.iteritems(): |
| 903 setting = cls.IssueSetting(self.GetBranch()) | 903 setting = cls.IssueSetting(self.GetBranch()) |
| 904 issue = RunGit(['config', setting], error_ok=True).strip() | 904 issue = RunGit(['config', setting], error_ok=True).strip() |
| 905 if issue: | 905 if issue: |
| 906 self._codereview = codereview |
| 906 self._codereview_impl = cls(self, **kwargs) | 907 self._codereview_impl = cls(self, **kwargs) |
| 907 self.issue = int(issue) | 908 self.issue = int(issue) |
| 908 return | 909 return |
| 909 | 910 |
| 910 # No issue is set for this branch, so decide based on repo-wide settings. | 911 # No issue is set for this branch, so decide based on repo-wide settings. |
| 911 return self._load_codereview_impl( | 912 return self._load_codereview_impl( |
| 912 codereview='gerrit' if settings.GetIsGerrit() else 'rietveld', | 913 codereview='gerrit' if settings.GetIsGerrit() else 'rietveld', |
| 913 **kwargs) | 914 **kwargs) |
| 914 | 915 |
| 916 def IsGerrit(self): |
| 917 return self._codereview == 'gerrit' |
| 915 | 918 |
| 916 def GetCCList(self): | 919 def GetCCList(self): |
| 917 """Return the users cc'd on this CL. | 920 """Return the users cc'd on this CL. |
| 918 | 921 |
| 919 Return is a string suitable for passing to gcl with the --cc flag. | 922 Return is a string suitable for passing to gcl with the --cc flag. |
| 920 """ | 923 """ |
| 921 if self.cc is None: | 924 if self.cc is None: |
| 922 base_cc = settings.GetDefaultCCList() | 925 base_cc = settings.GetDefaultCCList() |
| 923 more_cc = ','.join(self.watchers) | 926 more_cc = ','.join(self.watchers) |
| 924 self.cc = ','.join(filter(None, (base_cc, more_cc))) or '' | 927 self.cc = ','.join(filter(None, (base_cc, more_cc))) or '' |
| (...skipping 718 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1643 data = self._GetChangeDetail(['COMMIT_FOOTERS', 'CURRENT_REVISION']) | 1646 data = self._GetChangeDetail(['COMMIT_FOOTERS', 'CURRENT_REVISION']) |
| 1644 return data['revisions'][data['current_revision']]['commit_with_footers'] | 1647 return data['revisions'][data['current_revision']]['commit_with_footers'] |
| 1645 | 1648 |
| 1646 def UpdateDescriptionRemote(self, description): | 1649 def UpdateDescriptionRemote(self, description): |
| 1647 # TODO(tandrii) | 1650 # TODO(tandrii) |
| 1648 raise NotImplementedError() | 1651 raise NotImplementedError() |
| 1649 | 1652 |
| 1650 def CloseIssue(self): | 1653 def CloseIssue(self): |
| 1651 gerrit_util.AbandonChange(self._GetGerritHost(), self.GetIssue(), msg='') | 1654 gerrit_util.AbandonChange(self._GetGerritHost(), self.GetIssue(), msg='') |
| 1652 | 1655 |
| 1656 def SubmitIssue(self, wait_for_merge=True): |
| 1657 gerrit_util.SubmitChange(self._GetGerritHost(), self.GetIssue(), |
| 1658 wait_for_merge=wait_for_merge) |
| 1653 | 1659 |
| 1654 def _GetChangeDetail(self, options): | 1660 def _GetChangeDetail(self, options): |
| 1655 return gerrit_util.GetChangeDetail(self._GetGerritHost(), self.GetIssue(), | 1661 return gerrit_util.GetChangeDetail(self._GetGerritHost(), self.GetIssue(), |
| 1656 options) | 1662 options) |
| 1657 | 1663 |
| 1664 def CMDLand(self, force, bypass_hooks, verbose): |
| 1665 if git_common.is_dirty_git_tree('land'): |
| 1666 return 1 |
| 1667 differs = True |
| 1668 last_upload = RunGit(['config', |
| 1669 'branch.%s.gerritsquashhash' % self.GetBranch()], |
| 1670 error_ok=True).strip() |
| 1671 # Note: git diff outputs nothing if there is no diff. |
| 1672 if not last_upload or RunGit(['diff', last_upload]).strip(): |
| 1673 print('WARNING: some changes from local branch haven\'t been uploaded') |
| 1674 else: |
| 1675 detail = self._GetChangeDetail(['CURRENT_REVISION']) |
| 1676 if detail['current_revision'] == last_upload: |
| 1677 differs = False |
| 1678 else: |
| 1679 print('WARNING: local branch contents differ from latest uploaded ' |
| 1680 'patchset') |
| 1681 if differs: |
| 1682 if not force: |
| 1683 ask_for_data( |
| 1684 'Do you want to submit latest Gerrit patchset and bypass hooks?') |
| 1685 print('WARNING: bypassing hooks and submitting latest uploaded patchset') |
| 1686 elif not bypass_hooks: |
| 1687 hook_results = self.RunHook( |
| 1688 committing=True, |
| 1689 may_prompt=not force, |
| 1690 verbose=verbose, |
| 1691 change=self.GetChange(self.GetCommonAncestorWithUpstream(), None)) |
| 1692 if not hook_results.should_continue(): |
| 1693 return 1 |
| 1694 |
| 1695 self.SubmitIssue(wait_for_merge=True) |
| 1696 print('Issue %s has been submitted.' % self.GetIssueURL()) |
| 1697 return 0 |
| 1698 |
| 1699 |
| 1700 _CODEREVIEW_IMPLEMENTATIONS = { |
| 1701 'rietveld': _RietveldChangelistImpl, |
| 1702 'gerrit': _GerritChangelistImpl, |
| 1703 } |
| 1704 |
| 1658 | 1705 |
| 1659 class ChangeDescription(object): | 1706 class ChangeDescription(object): |
| 1660 """Contains a parsed form of the change description.""" | 1707 """Contains a parsed form of the change description.""" |
| 1661 R_LINE = r'^[ \t]*(TBR|R)[ \t]*=[ \t]*(.*?)[ \t]*$' | 1708 R_LINE = r'^[ \t]*(TBR|R)[ \t]*=[ \t]*(.*?)[ \t]*$' |
| 1662 BUG_LINE = r'^[ \t]*(BUG)[ \t]*=[ \t]*(.*?)[ \t]*$' | 1709 BUG_LINE = r'^[ \t]*(BUG)[ \t]*=[ \t]*(.*?)[ \t]*$' |
| 1663 | 1710 |
| 1664 def __init__(self, description): | 1711 def __init__(self, description): |
| 1665 self._description_lines = (description or '').strip().splitlines() | 1712 self._description_lines = (description or '').strip().splitlines() |
| 1666 | 1713 |
| 1667 @property # www.logilab.org/ticket/89786 | 1714 @property # www.logilab.org/ticket/89786 |
| (...skipping 1435 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 3103 # When submodules are added to the repo, we expect there to be a single | 3150 # When submodules are added to the repo, we expect there to be a single |
| 3104 # non-git-svn merge commit at remote HEAD with a signature comment. | 3151 # non-git-svn merge commit at remote HEAD with a signature comment. |
| 3105 pattern = '^SVN changes up to revision [0-9]*$' | 3152 pattern = '^SVN changes up to revision [0-9]*$' |
| 3106 cmd = ['rev-list', '--merges', '--grep=%s' % pattern, '%s^!' % ref] | 3153 cmd = ['rev-list', '--merges', '--grep=%s' % pattern, '%s^!' % ref] |
| 3107 return RunGit(cmd) != '' | 3154 return RunGit(cmd) != '' |
| 3108 | 3155 |
| 3109 | 3156 |
| 3110 def SendUpstream(parser, args, cmd): | 3157 def SendUpstream(parser, args, cmd): |
| 3111 """Common code for CMDland and CmdDCommit | 3158 """Common code for CMDland and CmdDCommit |
| 3112 | 3159 |
| 3113 Squashes branch into a single commit. | 3160 In case of Gerrit, uses Gerrit REST api to "submit" the issue, which pushes |
| 3114 Updates changelog with metadata (e.g. pointer to review). | 3161 upstream and closes the issue automatically and atomically. |
| 3115 Pushes/dcommits the code upstream. | 3162 |
| 3116 Updates review and closes. | 3163 Otherwise (in case of Rietveld): |
| 3164 Squashes branch into a single commit. |
| 3165 Updates changelog with metadata (e.g. pointer to review). |
| 3166 Pushes/dcommits the code upstream. |
| 3167 Updates review and closes. |
| 3117 """ | 3168 """ |
| 3118 parser.add_option('--bypass-hooks', action='store_true', dest='bypass_hooks', | 3169 parser.add_option('--bypass-hooks', action='store_true', dest='bypass_hooks', |
| 3119 help='bypass upload presubmit hook') | 3170 help='bypass upload presubmit hook') |
| 3120 parser.add_option('-m', dest='message', | 3171 parser.add_option('-m', dest='message', |
| 3121 help="override review description") | 3172 help="override review description") |
| 3122 parser.add_option('-f', action='store_true', dest='force', | 3173 parser.add_option('-f', action='store_true', dest='force', |
| 3123 help="force yes to questions (don't prompt)") | 3174 help="force yes to questions (don't prompt)") |
| 3124 parser.add_option('-c', dest='contributor', | 3175 parser.add_option('-c', dest='contributor', |
| 3125 help="external contributor for patch (appended to " + | 3176 help="external contributor for patch (appended to " + |
| 3126 "description and used as author for git). Should be " + | 3177 "description and used as author for git). Should be " + |
| 3127 "formatted as 'First Last <email@example.com>'") | 3178 "formatted as 'First Last <email@example.com>'") |
| 3128 add_git_similarity(parser) | 3179 add_git_similarity(parser) |
| 3129 auth.add_auth_options(parser) | 3180 auth.add_auth_options(parser) |
| 3130 (options, args) = parser.parse_args(args) | 3181 (options, args) = parser.parse_args(args) |
| 3131 auth_config = auth.extract_auth_config_from_options(options) | 3182 auth_config = auth.extract_auth_config_from_options(options) |
| 3132 | 3183 |
| 3133 cl = Changelist(auth_config=auth_config) | 3184 cl = Changelist(auth_config=auth_config) |
| 3134 | 3185 |
| 3186 # TODO(tandrii): refactor this into _RietveldChangelistImpl method. |
| 3187 if cl.IsGerrit(): |
| 3188 if options.message: |
| 3189 # This could be implemented, but it requires sending a new patch to |
| 3190 # Gerrit, as Gerrit unlike Rietveld versions messages with patchsets. |
| 3191 # Besides, Gerrit has the ability to change the commit message on submit |
| 3192 # automatically, thus there is no need to support this option (so far?). |
| 3193 parser.error('-m MESSAGE option is not supported for Gerrit.') |
| 3194 if options.contributor: |
| 3195 parser.error( |
| 3196 '-c CONTRIBUTOR option is not supported for Gerrit.\n' |
| 3197 'Before uploading a commit to Gerrit, ensure it\'s author field is ' |
| 3198 'the contributor\'s "name <email>". If you can\'t upload such a ' |
| 3199 'commit for review, contact your repository admin and request' |
| 3200 '"Forge-Author" permission.') |
| 3201 return cl._codereview_impl.CMDLand(options.force, options.bypass_hooks, |
| 3202 options.verbose) |
| 3203 |
| 3135 current = cl.GetBranch() | 3204 current = cl.GetBranch() |
| 3136 remote, upstream_branch = cl.FetchUpstreamTuple(cl.GetBranch()) | 3205 remote, upstream_branch = cl.FetchUpstreamTuple(cl.GetBranch()) |
| 3137 if not settings.GetIsGitSvn() and remote == '.': | 3206 if not settings.GetIsGitSvn() and remote == '.': |
| 3138 print | 3207 print |
| 3139 print 'Attempting to push branch %r into another local branch!' % current | 3208 print 'Attempting to push branch %r into another local branch!' % current |
| 3140 print | 3209 print |
| 3141 print 'Either reparent this branch on top of origin/master:' | 3210 print 'Either reparent this branch on top of origin/master:' |
| 3142 print ' git reparent-branch --root' | 3211 print ' git reparent-branch --root' |
| 3143 print | 3212 print |
| 3144 print 'OR run `git rebase-update` if you think the parent branch is already' | 3213 print 'OR run `git rebase-update` if you think the parent branch is already' |
| (...skipping 1263 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 4408 if __name__ == '__main__': | 4477 if __name__ == '__main__': |
| 4409 # These affect sys.stdout so do it outside of main() to simplify mocks in | 4478 # These affect sys.stdout so do it outside of main() to simplify mocks in |
| 4410 # unit testing. | 4479 # unit testing. |
| 4411 fix_encoding.fix_encoding() | 4480 fix_encoding.fix_encoding() |
| 4412 colorama.init() | 4481 colorama.init() |
| 4413 try: | 4482 try: |
| 4414 sys.exit(main(sys.argv[1:])) | 4483 sys.exit(main(sys.argv[1:])) |
| 4415 except KeyboardInterrupt: | 4484 except KeyboardInterrupt: |
| 4416 sys.stderr.write('interrupted\n') | 4485 sys.stderr.write('interrupted\n') |
| 4417 sys.exit(1) | 4486 sys.exit(1) |
| OLD | NEW |