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 |