blob: 62c1fb8cf1dca9690d1dfade4617ed1e12e0a79b [file] [log] [blame]
Chris Sosaefc35722012-09-11 18:55:37 -07001#!/usr/bin/python
2
3# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7""" This simple program takes changes from gerrit/gerrit-int and creates new
8changes for them on the desired branch using your gerrit/ssh credentials. To
9specify a change on gerrit-int, you must prefix the change with a *.
10
11Note that this script is best used from within an existing checkout of
12Chromium OS that already has the changes you want merged to the branch in it
13i.e. if you want to push changes to crosutils.git, you must have src/scripts
14checked out. If this isn't true e.g. you are running this script from a
15minilayout or trying to upload an internal change from a non internal checkout,
16you must specify some extra options: use the --nomirror option and use -e to
17specify your email address. This tool will then checkout the git repo fresh
18using the credentials for the -e/email you specified and upload the change. Note
19you can always use this method but it's slower than the "mirrored" method and
20requires more typing :(.
21
22Examples:
23 cros_merge_to_branch 32027 32030 32031 release-R22.2723.B
24
25This will create changes for 32027, 32030 and 32031 on the R22 branch. To look
26up the name of a branch, go into a git sub-dir and type 'git branch -a' and the
27find the branch you want to merge to. If you want to upload internal changes
28from gerrit-int, you must prefix the gerrit change number with a * e.g.
29
30 cros_merge_to_branch *26108 release-R22.2723.B
31
32For more information on how to do this yourself you can go here:
33http://dev.chromium.org/chromium-os/how-tos-and-troubleshooting/working-on-a-br\
34anch
35"""
36
37import logging
38import os
39import shutil
40import sys
41import tempfile
42
43from chromite.buildbot import constants
Chris Sosaefc35722012-09-11 18:55:37 -070044from chromite.buildbot import repository
45from chromite.lib import commandline
46from chromite.lib import cros_build_lib
Brian Harring511055e2012-10-10 02:58:59 -070047from chromite.lib import gerrit
48from chromite.lib import patch as cros_patch
Chris Sosaefc35722012-09-11 18:55:37 -070049
50
51_USAGE = """
52cros_merge_to_branch [*]change_number1 [[*]change_number2 ...] branch\n\n %s
53""" % __doc__
54
55
56def _GetParser():
57 """Returns the parser to use for this module."""
58 parser = commandline.OptionParser(usage=_USAGE)
59 parser.add_option('-d', '--draft', default=False, action='store_true',
60 help='Upload a draft to Gerrit rather than a change.')
61 parser.add_option('--dryrun', default=False, action='store_true',
62 help='Apply changes locally but do not upload them.')
63 parser.add_option('-e', '--email',
64 help='If specified, use this email instead of '
65 'the email you would upload changes as. Must be set if '
66 'nomirror is set.')
67 parser.add_option('--nomirror', default=True, dest='mirror',
68 action='store_false', help='Disable mirroring -- requires '
69 'email to be set.')
70 parser.add_option('--nowipe', default=True, dest='wipe', action='store_false',
71 help='Do not wipe the work directory after finishing.')
72 return parser
73
74
75def _UploadChangeToBranch(work_dir, patch, branch, draft, dryrun):
76 """Creates a new change from GerritPatch |patch| to |branch| from |work_dir|.
77
78 Args:
79 patch: Instance of GerritPatch to upload.
80 branch: Branch to upload to.
81 work_dir: Local directory where repository is checked out in.
82 draft: If True, upload to refs/draft/|branch| rather than refs/for/|branch|.
83 dryrun: Don't actually upload a change but go through all the steps up to
84 and including git push --dryrun.
85 """
86 upload_type = 'drafts' if draft else 'for'
87 # Apply the actual change.
88 patch.CherryPick(work_dir, inflight=True, leave_dirty=True)
89
90 # Get the new sha1 after apply.
91 new_sha1 = cros_build_lib.GetGitRepoRevision(work_dir)
92
93 # Create and use a LocalPatch to Upload the change to Gerrit.
94 local_patch = cros_patch.LocalPatch(
95 work_dir, patch.project_url, constants.PATCH_BRANCH,
96 patch.tracking_branch, patch.remote, new_sha1)
97 local_patch.Upload(
98 patch.project_url, 'refs/%s/%s' % (upload_type, branch),
99 carbon_copy=False, dryrun=dryrun)
100
101
102def _SetupWorkDirectoryForPatch(work_dir, patch, branch, manifest, email):
103 """Set up local dir for uploading changes to the given patch's project."""
104 logging.info('Setting up dir %s for uploading changes to %s', work_dir,
105 patch.project_url)
106
107 # Clone the git repo from reference if we have a pointer to a
108 # ManifestCheckout object.
109 reference = None
110 if manifest:
111 reference = os.path.join(constants.SOURCE_ROOT,
112 manifest.GetProjectPath(patch.project))
113 # Use the email if email wasn't specified.
114 if not email:
115 email = cros_build_lib.GetProjectUserEmail(reference)
116
117 repository.CloneGitRepo(work_dir, patch.project_url, reference=reference)
118
119 # Set the git committer.
120 cros_build_lib.RunGitCommand(work_dir,
121 ['config', '--replace-all', 'user.email', email])
122
123 # Finally, create a local branch for uploading changes to the given remote
124 # branch.
125 cros_build_lib.CreatePushBranch(
126 constants.PATCH_BRANCH, work_dir, sync=False,
127 remote_push_branch=('ignore', 'origin/%s' % branch))
128
129
130def _ManifestContainsAllPatches(manifest, patches):
131 """Returns true if the given manifest contains all the patches.
132
133 Args:
134 manifest - an instance of cros_build_lib.Manifest
135 patches - a collection GerritPatch objects.
136 """
137 for patch in patches:
138 project_path = None
139 if manifest.ProjectExists(patch.project):
140 project_path = manifest.GetProjectPath(patch.project)
141
142 if not project_path:
143 logging.error('Your manifest does not have the repository %s for '
144 'change %s. Please re-run with --nomirror and '
145 '--email set', patch.project, patch.gerrit_number)
146 return False
147
148 return True
149
150
151def main(argv):
152 parser = _GetParser()
153 options, args = parser.parse_args(argv)
154
155 if len(args) < 2:
156 parser.error('Not enough arguments specified')
157
158 changes = args[0:-1]
Brian Harring511055e2012-10-10 02:58:59 -0700159 patches = gerrit.GetGerritPatchInfo(changes)
Chris Sosaefc35722012-09-11 18:55:37 -0700160 branch = args[-1]
161
162 # Suppress all cros_build_lib info output unless we're running debug.
163 if not options.debug:
164 cros_build_lib.logger.setLevel(logging.ERROR)
165
166 # Get a pointer to your repo checkout to look up the local project paths for
167 # both email addresses and for using your checkout as a git mirror.
168 manifest = None
169 if options.mirror:
170 manifest = cros_build_lib.ManifestCheckout.Cached(constants.SOURCE_ROOT)
171 if not _ManifestContainsAllPatches(manifest, patches):
172 return 1
173 else:
174 if not options.email:
175 chromium_email = '%s@chromium.org' % os.environ['USER']
176 logging.info('--nomirror set without email, using %s', chromium_email)
177 options.email = chromium_email
178
179 index = 0
180 work_dir = None
181 root_work_dir = tempfile.mkdtemp(prefix='cros_merge_to_branch')
182 try:
183 for index, (change, patch) in enumerate(zip(changes, patches)):
184 # We only clone the project and set the committer the first time.
185 work_dir = os.path.join(root_work_dir, patch.project)
186 if not os.path.isdir(work_dir):
187 _SetupWorkDirectoryForPatch(work_dir, patch, branch, manifest,
188 options.email)
189
190 # Now that we have the project checked out, let's apply our change and
191 # create a new change on Gerrit.
192 logging.info('Uploading change %s to branch %s', change, branch)
193 _UploadChangeToBranch(work_dir, patch, branch, options.draft,
194 options.dryrun)
195 logging.info('Successfully uploaded %s to %s', change, branch)
196
197 except (cros_build_lib.RunCommandError, cros_patch.ApplyPatchException) as e:
198 # Tell the user how far we got.
199 good_changes = changes[:index]
200 bad_changes = changes[index:]
201
202 logging.warning('############## SOME CHANGES FAILED TO UPLOAD ############')
203
204 if good_changes:
205 logging.info('Successfully uploaded change(s) %s', ' '.join(good_changes))
206
207 # Printing out the error here so that we can see exactly what failed. This
208 # is especially useful to debug without using --debug.
209 logging.error('Upload failed with %s', str(e).strip())
210 if not options.wipe:
211 logging.info('Not wiping the directory. You can inspect the failed '
Mike Frysinger9f606d72012-10-12 00:55:57 -0400212 'change at %s; After fixing the change (if trivial) you can '
Chris Sosaefc35722012-09-11 18:55:37 -0700213 'try to upload the change by running:\n'
214 'git push %s HEAD:refs/for/%s', work_dir, patch.project_url,
215 branch)
216 else:
217 logging.error('--nowipe not set thus deleting the work directory. If you '
218 'wish to debug this, re-run the script with change(s) '
219 '%s and --nowipe by running: \n %s %s --nowipe',
220 ' '.join(bad_changes), sys.argv[0], ' '.join(bad_changes))
221
222 # Suppress the stack trace if we're not debugging.
223 if options.debug:
224 raise
225 else:
226 return 1
227
228 finally:
229 if options.wipe:
230 shutil.rmtree(root_work_dir)
231
232 if options.dryrun:
233 logging.info('Success! To actually upload changes re-run without --dryrun.')
234 else:
235 logging.info('Successfully uploaded all changes requested.')
236
237 return 0