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 3740 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
3751 | 3751 |
3752 | 3752 |
3753 def IsSubmoduleMergeCommit(ref): | 3753 def IsSubmoduleMergeCommit(ref): |
3754 # When submodules are added to the repo, we expect there to be a single | 3754 # When submodules are added to the repo, we expect there to be a single |
3755 # non-git-svn merge commit at remote HEAD with a signature comment. | 3755 # non-git-svn merge commit at remote HEAD with a signature comment. |
3756 pattern = '^SVN changes up to revision [0-9]*$' | 3756 pattern = '^SVN changes up to revision [0-9]*$' |
3757 cmd = ['rev-list', '--merges', '--grep=%s' % pattern, '%s^!' % ref] | 3757 cmd = ['rev-list', '--merges', '--grep=%s' % pattern, '%s^!' % ref] |
3758 return RunGit(cmd) != '' | 3758 return RunGit(cmd) != '' |
3759 | 3759 |
3760 | 3760 |
3761 def SendUpstream(parser, args, cmd): | 3761 def SendUpstream(parser, args): |
3762 """Common code for CMDland and CmdDCommit | 3762 """Common code for CMDland |
3763 | 3763 |
3764 In case of Gerrit, uses Gerrit REST api to "submit" the issue, which pushes | 3764 In case of Gerrit, uses Gerrit REST api to "submit" the issue, which pushes |
3765 upstream and closes the issue automatically and atomically. | 3765 upstream and closes the issue automatically and atomically. |
3766 | 3766 |
3767 Otherwise (in case of Rietveld): | 3767 Otherwise (in case of Rietveld): |
3768 Squashes branch into a single commit. | 3768 Squashes branch into a single commit. |
3769 Updates changelog with metadata (e.g. pointer to review). | 3769 Updates changelog with metadata (e.g. pointer to review). |
3770 Pushes/dcommits the code upstream. | 3770 Pushes the code upstream. |
3771 Updates review and closes. | 3771 Updates review and closes. |
3772 """ | 3772 """ |
3773 parser.add_option('--bypass-hooks', action='store_true', dest='bypass_hooks', | 3773 parser.add_option('--bypass-hooks', action='store_true', dest='bypass_hooks', |
3774 help='bypass upload presubmit hook') | 3774 help='bypass upload presubmit hook') |
3775 parser.add_option('-m', dest='message', | 3775 parser.add_option('-m', dest='message', |
3776 help="override review description") | 3776 help="override review description") |
3777 parser.add_option('-f', action='store_true', dest='force', | 3777 parser.add_option('-f', action='store_true', dest='force', |
3778 help="force yes to questions (don't prompt)") | 3778 help="force yes to questions (don't prompt)") |
3779 parser.add_option('-c', dest='contributor', | 3779 parser.add_option('-c', dest='contributor', |
3780 help="external contributor for patch (appended to " + | 3780 help="external contributor for patch (appended to " + |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
3813 print() | 3813 print() |
3814 print('Either reparent this branch on top of origin/master:') | 3814 print('Either reparent this branch on top of origin/master:') |
3815 print(' git reparent-branch --root') | 3815 print(' git reparent-branch --root') |
3816 print() | 3816 print() |
3817 print('OR run `git rebase-update` if you think the parent branch is ') | 3817 print('OR run `git rebase-update` if you think the parent branch is ') |
3818 print('already committed.') | 3818 print('already committed.') |
3819 print() | 3819 print() |
3820 print(' Current parent: %r' % upstream_branch) | 3820 print(' Current parent: %r' % upstream_branch) |
3821 return 1 | 3821 return 1 |
3822 | 3822 |
3823 if not args or cmd == 'land': | 3823 if not args: |
3824 # Default to merging against our best guess of the upstream branch. | 3824 # Default to merging against our best guess of the upstream branch. |
3825 args = [cl.GetUpstreamBranch()] | 3825 args = [cl.GetUpstreamBranch()] |
3826 | 3826 |
3827 if options.contributor: | 3827 if options.contributor: |
3828 if not re.match('^.*\s<\S+@\S+>$', options.contributor): | 3828 if not re.match('^.*\s<\S+@\S+>$', options.contributor): |
3829 print("Please provide contibutor as 'First Last <email@example.com>'") | 3829 print("Please provide contibutor as 'First Last <email@example.com>'") |
3830 return 1 | 3830 return 1 |
3831 | 3831 |
3832 base_branch = args[0] | 3832 base_branch = args[0] |
3833 base_has_submodules = IsSubmoduleMergeCommit(base_branch) | 3833 base_has_submodules = IsSubmoduleMergeCommit(base_branch) |
3834 | 3834 |
3835 if git_common.is_dirty_git_tree(cmd): | 3835 if git_common.is_dirty_git_tree(cmd='land'): |
3836 return 1 | 3836 return 1 |
3837 | 3837 |
3838 # This rev-list syntax means "show all commits not in my branch that | 3838 # This rev-list syntax means "show all commits not in my branch that |
3839 # are in base_branch". | 3839 # are in base_branch". |
3840 upstream_commits = RunGit(['rev-list', '^' + cl.GetBranchRef(), | 3840 upstream_commits = RunGit(['rev-list', '^' + cl.GetBranchRef(), |
3841 base_branch]).splitlines() | 3841 base_branch]).splitlines() |
3842 if upstream_commits: | 3842 if upstream_commits: |
3843 print('Base branch "%s" has %d commits ' | 3843 print('Base branch "%s" has %d commits ' |
3844 'not in this branch.' % (base_branch, len(upstream_commits))) | 3844 'not in this branch.' % (base_branch, len(upstream_commits))) |
3845 print('Run "git merge %s" before attempting to %s.' % (base_branch, cmd)) | 3845 print('Run "git merge %s" before attempting to land.' % (base_branch,)) |
3846 return 1 | 3846 return 1 |
3847 | 3847 |
3848 # This is the revision `svn dcommit` will commit on top of. | 3848 # This is the revision `svn dcommit` will commit on top of. |
3849 svn_head = None | 3849 svn_head = None |
3850 if cmd == 'dcommit' or base_has_submodules: | 3850 if base_has_submodules: |
3851 svn_head = RunGit(['log', '--grep=^git-svn-id:', '-1', | 3851 svn_head = RunGit(['log', '--grep=^git-svn-id:', '-1', |
3852 '--pretty=format:%H']) | 3852 '--pretty=format:%H']) |
3853 | 3853 |
3854 if cmd == 'dcommit': | |
3855 # If the base_head is a submodule merge commit, the first parent of the | |
3856 # base_head should be a git-svn commit, which is what we're interested in. | |
3857 base_svn_head = base_branch | |
3858 if base_has_submodules: | |
3859 base_svn_head += '^1' | |
3860 | |
3861 extra_commits = RunGit(['rev-list', '^' + svn_head, base_svn_head]) | |
3862 if extra_commits: | |
3863 print('This branch has %d additional commits not upstreamed yet.' | |
3864 % len(extra_commits.splitlines())) | |
3865 print('Upstream "%s" or rebase this branch on top of the upstream trunk ' | |
3866 'before attempting to %s.' % (base_branch, cmd)) | |
3867 return 1 | |
3868 | |
3869 merge_base = RunGit(['merge-base', base_branch, 'HEAD']).strip() | 3854 merge_base = RunGit(['merge-base', base_branch, 'HEAD']).strip() |
3870 if not options.bypass_hooks: | 3855 if not options.bypass_hooks: |
3871 author = None | 3856 author = None |
3872 if options.contributor: | 3857 if options.contributor: |
3873 author = re.search(r'\<(.*)\>', options.contributor).group(1) | 3858 author = re.search(r'\<(.*)\>', options.contributor).group(1) |
3874 hook_results = cl.RunHook( | 3859 hook_results = cl.RunHook( |
3875 committing=True, | 3860 committing=True, |
3876 may_prompt=not options.force, | 3861 may_prompt=not options.force, |
3877 verbose=options.verbose, | 3862 verbose=options.verbose, |
3878 change=cl.GetChange(merge_base, author)) | 3863 change=cl.GetChange(merge_base, author)) |
3879 if not hook_results.should_continue(): | 3864 if not hook_results.should_continue(): |
3880 return 1 | 3865 return 1 |
3881 | 3866 |
3882 # Check the tree status if the tree status URL is set. | 3867 # Check the tree status if the tree status URL is set. |
3883 status = GetTreeStatus() | 3868 status = GetTreeStatus() |
3884 if 'closed' == status: | 3869 if 'closed' == status: |
3885 print('The tree is closed. Please wait for it to reopen. Use ' | 3870 print('The tree is closed. Please wait for it to reopen. Use ' |
3886 '"git cl %s --bypass-hooks" to commit on a closed tree.' % cmd) | 3871 '"git cl land --bypass-hooks" to commit on a closed tree.') |
3887 return 1 | 3872 return 1 |
3888 elif 'unknown' == status: | 3873 elif 'unknown' == status: |
3889 print('Unable to determine tree status. Please verify manually and ' | 3874 print('Unable to determine tree status. Please verify manually and ' |
3890 'use "git cl %s --bypass-hooks" to commit on a closed tree.' % cmd) | 3875 'use "git cl land --bypass-hooks" to commit on a closed tree.') |
3891 return 1 | 3876 return 1 |
3892 | 3877 |
3893 change_desc = ChangeDescription(options.message) | 3878 change_desc = ChangeDescription(options.message) |
3894 if not change_desc.description and cl.GetIssue(): | 3879 if not change_desc.description and cl.GetIssue(): |
3895 change_desc = ChangeDescription(cl.GetDescription()) | 3880 change_desc = ChangeDescription(cl.GetDescription()) |
3896 | 3881 |
3897 if not change_desc.description: | 3882 if not change_desc.description: |
3898 if not cl.GetIssue() and options.bypass_hooks: | 3883 if not cl.GetIssue() and options.bypass_hooks: |
3899 change_desc = ChangeDescription(CreateDescriptionFromLog([merge_base])) | 3884 change_desc = ChangeDescription(CreateDescriptionFromLog([merge_base])) |
3900 else: | 3885 else: |
(...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
3963 'commit', '--author', options.contributor, | 3948 'commit', '--author', options.contributor, |
3964 '-m', commit_desc.description, | 3949 '-m', commit_desc.description, |
3965 ]) | 3950 ]) |
3966 else: | 3951 else: |
3967 RunGit(['commit', '-m', commit_desc.description]) | 3952 RunGit(['commit', '-m', commit_desc.description]) |
3968 if base_has_submodules: | 3953 if base_has_submodules: |
3969 cherry_pick_commit = RunGit(['rev-list', 'HEAD^!']).rstrip() | 3954 cherry_pick_commit = RunGit(['rev-list', 'HEAD^!']).rstrip() |
3970 RunGit(['branch', CHERRY_PICK_BRANCH, svn_head]) | 3955 RunGit(['branch', CHERRY_PICK_BRANCH, svn_head]) |
3971 RunGit(['checkout', CHERRY_PICK_BRANCH]) | 3956 RunGit(['checkout', CHERRY_PICK_BRANCH]) |
3972 RunGit(['cherry-pick', cherry_pick_commit]) | 3957 RunGit(['cherry-pick', cherry_pick_commit]) |
3973 if cmd == 'land': | 3958 remote, branch = cl.FetchUpstreamTuple(cl.GetBranch()) |
3974 remote, branch = cl.FetchUpstreamTuple(cl.GetBranch()) | 3959 mirror = settings.GetGitMirror(remote) |
3975 mirror = settings.GetGitMirror(remote) | 3960 pushurl = mirror.url if mirror else remote |
3976 pushurl = mirror.url if mirror else remote | 3961 pending_prefix = settings.GetPendingRefPrefix() |
3977 pending_prefix = settings.GetPendingRefPrefix() | 3962 if not pending_prefix or branch.startswith(pending_prefix): |
3978 if not pending_prefix or branch.startswith(pending_prefix): | 3963 # If not using refs/pending/heads/* at all, or target ref is already set |
3979 # If not using refs/pending/heads/* at all, or target ref is already set | 3964 # to pending, then push to the target ref directly. |
3980 # to pending, then push to the target ref directly. | 3965 retcode, output = RunGitWithCode( |
3981 retcode, output = RunGitWithCode( | 3966 ['push', '--porcelain', pushurl, 'HEAD:%s' % branch]) |
3982 ['push', '--porcelain', pushurl, 'HEAD:%s' % branch]) | 3967 pushed_to_pending = pending_prefix and branch.startswith(pending_prefix) |
3983 pushed_to_pending = pending_prefix and branch.startswith(pending_prefix) | |
3984 else: | |
3985 # Cherry-pick the change on top of pending ref and then push it. | |
3986 assert branch.startswith('refs/'), branch | |
3987 assert pending_prefix[-1] == '/', pending_prefix | |
3988 pending_ref = pending_prefix + branch[len('refs/'):] | |
3989 retcode, output = PushToGitPending(pushurl, pending_ref, branch) | |
3990 pushed_to_pending = (retcode == 0) | |
3991 if retcode == 0: | |
3992 revision = RunGit(['rev-parse', 'HEAD']).strip() | |
3993 else: | 3968 else: |
3994 # dcommit the merge branch. | 3969 # Cherry-pick the change on top of pending ref and then push it. |
3995 cmd_args = [ | 3970 assert branch.startswith('refs/'), branch |
3996 'svn', 'dcommit', | 3971 assert pending_prefix[-1] == '/', pending_prefix |
3997 '-C%s' % options.similarity, | 3972 pending_ref = pending_prefix + branch[len('refs/'):] |
3998 '--no-rebase', '--rmdir', | 3973 retcode, output = PushToGitPending(pushurl, pending_ref, branch) |
3999 ] | 3974 pushed_to_pending = (retcode == 0) |
4000 if settings.GetForceHttpsCommitUrl(): | 3975 if retcode == 0: |
4001 # Allow forcing https commit URLs for some projects that don't allow | 3976 revision = RunGit(['rev-parse', 'HEAD']).strip() |
4002 # committing to http URLs (like Google Code). | |
4003 remote_url = cl.GetGitSvnRemoteUrl() | |
4004 if urlparse.urlparse(remote_url).scheme == 'http': | |
4005 remote_url = remote_url.replace('http://', 'https://') | |
4006 cmd_args.append('--commit-url=%s' % remote_url) | |
4007 _, output = RunGitWithCode(cmd_args) | |
4008 if 'Committed r' in output: | |
4009 revision = re.match( | |
4010 '.*?\nCommitted r(\\d+)', output, re.DOTALL).group(1) | |
4011 logging.debug(output) | 3977 logging.debug(output) |
4012 finally: | 3978 finally: |
4013 # And then swap back to the original branch and clean up. | 3979 # And then swap back to the original branch and clean up. |
4014 RunGit(['checkout', '-q', cl.GetBranch()]) | 3980 RunGit(['checkout', '-q', cl.GetBranch()]) |
4015 RunGit(['branch', '-D', MERGE_BRANCH]) | 3981 RunGit(['branch', '-D', MERGE_BRANCH]) |
4016 if base_has_submodules: | 3982 if base_has_submodules: |
4017 RunGit(['branch', '-D', CHERRY_PICK_BRANCH]) | 3983 RunGit(['branch', '-D', CHERRY_PICK_BRANCH]) |
4018 | 3984 |
4019 if not revision: | 3985 if not revision: |
4020 print('Failed to push. If this persists, please file a bug.') | 3986 print('Failed to push. If this persists, please file a bug.') |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
4053 comment += ' (presubmit successful).' | 4019 comment += ' (presubmit successful).' |
4054 cl.RpcServer().add_comment(cl.GetIssue(), comment) | 4020 cl.RpcServer().add_comment(cl.GetIssue(), comment) |
4055 cl.SetIssue(None) | 4021 cl.SetIssue(None) |
4056 | 4022 |
4057 if pushed_to_pending: | 4023 if pushed_to_pending: |
4058 _, branch = cl.FetchUpstreamTuple(cl.GetBranch()) | 4024 _, branch = cl.FetchUpstreamTuple(cl.GetBranch()) |
4059 print('The commit is in the pending queue (%s).' % pending_ref) | 4025 print('The commit is in the pending queue (%s).' % pending_ref) |
4060 print('It will show up on %s in ~1 min, once it gets a Cr-Commit-Position ' | 4026 print('It will show up on %s in ~1 min, once it gets a Cr-Commit-Position ' |
4061 'footer.' % branch) | 4027 'footer.' % branch) |
4062 | 4028 |
4063 hook = POSTUPSTREAM_HOOK_PATTERN % cmd | 4029 hook = POSTUPSTREAM_HOOK_PATTERN % 'land' |
4064 if os.path.isfile(hook): | 4030 if os.path.isfile(hook): |
4065 RunCommand([hook, merge_base], error_ok=True) | 4031 RunCommand([hook, merge_base], error_ok=True) |
4066 | 4032 |
4067 return 1 if killed else 0 | 4033 return 1 if killed else 0 |
4068 | 4034 |
4069 | 4035 |
4070 def WaitForRealCommit(remote, pushed_commit, local_base_ref, real_ref): | 4036 def WaitForRealCommit(remote, pushed_commit, local_base_ref, real_ref): |
4071 print() | 4037 print() |
4072 print('Waiting for commit to be landed on %s...' % real_ref) | 4038 print('Waiting for commit to be landed on %s...' % real_ref) |
4073 print('(If you are impatient, you may Ctrl-C once without harm)') | 4039 print('(If you are impatient, you may Ctrl-C once without harm)') |
(...skipping 80 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
4154 print('All attempts to push to pending ref failed.') | 4120 print('All attempts to push to pending ref failed.') |
4155 return code, out | 4121 return code, out |
4156 | 4122 |
4157 | 4123 |
4158 def IsFatalPushFailure(push_stdout): | 4124 def IsFatalPushFailure(push_stdout): |
4159 """True if retrying push won't help.""" | 4125 """True if retrying push won't help.""" |
4160 return '(prohibited by Gerrit)' in push_stdout | 4126 return '(prohibited by Gerrit)' in push_stdout |
4161 | 4127 |
4162 | 4128 |
4163 @subcommand.usage('[upstream branch to apply against]') | 4129 @subcommand.usage('[upstream branch to apply against]') |
4164 def CMDdcommit(parser, args): | |
4165 """Commits the current changelist via git-svn.""" | |
4166 if not settings.GetIsGitSvn(): | |
4167 if git_footers.get_footer_svn_id(): | |
4168 # If it looks like previous commits were mirrored with git-svn. | |
4169 message = """This repository appears to be a git-svn mirror, but no | |
4170 upstream SVN master is set. You probably need to run 'git auto-svn' once.""" | |
4171 else: | |
4172 message = """This doesn't appear to be an SVN repository. | |
4173 If your project has a true, writeable git repository, you probably want to run | |
4174 'git cl land' instead. | |
4175 If your project has a git mirror of an upstream SVN master, you probably need | |
4176 to run 'git svn init'. | |
4177 | |
4178 Using the wrong command might cause your commit to appear to succeed, and the | |
4179 review to be closed, without actually landing upstream. If you choose to | |
4180 proceed, please verify that the commit lands upstream as expected.""" | |
4181 print(message) | |
4182 ask_for_data('[Press enter to dcommit or ctrl-C to quit]') | |
4183 # TODO(tandrii): kill this post SVN migration with | |
4184 # https://codereview.chromium.org/2076683002 | |
4185 print('WARNING: chrome infrastructure is migrating SVN repos to Git.\n' | |
4186 'Please let us know of this project you are committing to:' | |
4187 ' http://crbug.com/600451') | |
4188 return SendUpstream(parser, args, 'dcommit') | |
4189 | |
4190 | |
4191 @subcommand.usage('[upstream branch to apply against]') | |
4192 def CMDland(parser, args): | 4130 def CMDland(parser, args): |
4193 """Commits the current changelist via git.""" | 4131 """Commits the current changelist via git.""" |
4194 if settings.GetIsGitSvn() or git_footers.get_footer_svn_id(): | 4132 if settings.GetIsGitSvn() or git_footers.get_footer_svn_id(): |
4195 print('This appears to be an SVN repository.') | 4133 print('This appears to be an SVN repository.') |
4196 print('Are you sure you didn\'t mean \'git cl dcommit\'?') | 4134 print('Are you sure you didn\'t mean \'git cl dcommit\'?') |
Michael Moss
2016/08/15 15:26:32
Change this too, maybe remove the get_footer_svn_i
| |
4197 print('(Ignore if this is the first commit after migrating from svn->git)') | 4135 print('(Ignore if this is the first commit after migrating from svn->git)') |
4198 ask_for_data('[Press enter to push or ctrl-C to quit]') | 4136 ask_for_data('[Press enter to push or ctrl-C to quit]') |
4199 return SendUpstream(parser, args, 'land') | 4137 return SendUpstream(parser, args) |
4200 | 4138 |
4201 | 4139 |
4202 @subcommand.usage('<patch url or issue id or issue url>') | 4140 @subcommand.usage('<patch url or issue id or issue url>') |
4203 def CMDpatch(parser, args): | 4141 def CMDpatch(parser, args): |
4204 """Patches in a code review.""" | 4142 """Patches in a code review.""" |
4205 parser.add_option('-b', dest='newbranch', | 4143 parser.add_option('-b', dest='newbranch', |
4206 help='create a new branch off trunk for the patch') | 4144 help='create a new branch off trunk for the patch') |
4207 parser.add_option('-f', '--force', action='store_true', | 4145 parser.add_option('-f', '--force', action='store_true', |
4208 help='with -b, clobber any existing branch') | 4146 help='with -b, clobber any existing branch') |
4209 parser.add_option('-d', '--directory', action='store', metavar='DIR', | 4147 parser.add_option('-d', '--directory', action='store', metavar='DIR', |
(...skipping 824 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
5034 if __name__ == '__main__': | 4972 if __name__ == '__main__': |
5035 # These affect sys.stdout so do it outside of main() to simplify mocks in | 4973 # These affect sys.stdout so do it outside of main() to simplify mocks in |
5036 # unit testing. | 4974 # unit testing. |
5037 fix_encoding.fix_encoding() | 4975 fix_encoding.fix_encoding() |
5038 setup_color.init() | 4976 setup_color.init() |
5039 try: | 4977 try: |
5040 sys.exit(main(sys.argv[1:])) | 4978 sys.exit(main(sys.argv[1:])) |
5041 except KeyboardInterrupt: | 4979 except KeyboardInterrupt: |
5042 sys.stderr.write('interrupted\n') | 4980 sys.stderr.write('interrupted\n') |
5043 sys.exit(1) | 4981 sys.exit(1) |
OLD | NEW |