blob: 153b3ebec3cbe6861352b675b49e7e276910ddbc [file] [log] [blame]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001#
2# Copyright (C) 2008 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
Ben Komalo08a3f682010-07-15 16:03:02 -070016import copy
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070017import re
18import sys
19
20from command import InteractiveCommand
21from editor import Editor
22from error import UploadError
23
Dan Morrillf0a9a1a2010-05-05 08:18:35 -070024UNUSUAL_COMMIT_THRESHOLD = 5
Dan Morrill879a9a52010-05-04 16:56:07 -070025
26def _ConfirmManyUploads(multiple_branches=False):
27 if multiple_branches:
28 print "ATTENTION: One or more branches has an unusually high number of commits."
29 else:
30 print "ATTENTION: You are uploading an unusually high number of commits."
31 print "YOU PROBABLY DO NOT MEAN TO DO THIS. (Did you rebase across branches?)"
32 answer = raw_input("If you are sure you intend to do this, type 'yes': ").strip()
33 return answer == "yes"
34
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070035def _die(fmt, *args):
36 msg = fmt % args
37 print >>sys.stderr, 'error: %s' % msg
38 sys.exit(1)
39
Joe Onorato2896a792008-11-17 16:56:36 -050040def _SplitEmails(values):
41 result = []
42 for str in values:
43 result.extend([s.strip() for s in str.split(',')])
44 return result
45
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070046class Upload(InteractiveCommand):
47 common = True
48 helpSummary = "Upload changes for code review"
49 helpUsage="""
Joe Onorato2896a792008-11-17 16:56:36 -050050%prog [--re --cc] {[<project>]... | --replace <project>}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070051"""
52 helpDescription = """
Shawn O. Pearce337fb9c2009-04-18 10:59:33 -070053The '%prog' command is used to send changes to the Gerrit Code
54Review system. It searches for topic branches in local projects
55that have not yet been published for review. If multiple topic
56branches are found, '%prog' opens an editor to allow the user to
57select which branches to upload.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070058
Shawn O. Pearce337fb9c2009-04-18 10:59:33 -070059'%prog' searches for uploadable changes in all projects listed at
60the command line. Projects can be specified either by name, or by
61a relative or absolute path to the project's local directory. If no
62projects are specified, '%prog' will search for uploadable changes
63in all projects listed in the manifest.
Joe Onorato2896a792008-11-17 16:56:36 -050064
65If the --reviewers or --cc options are passed, those emails are
66added to the respective list of users, and emails are sent to any
Shawn O. Pearce337fb9c2009-04-18 10:59:33 -070067new users. Users passed as --reviewers must already be registered
Joe Onorato2896a792008-11-17 16:56:36 -050068with the code review system, or the upload will fail.
Shawn O. Pearcea6df7d22008-12-12 08:04:07 -080069
70If the --replace option is passed the user can designate which
71existing change(s) in Gerrit match up to the commits in the branch
72being uploaded. For each matched pair of change,commit the commit
73will be added as a new patch set, completely replacing the set of
74files and description associated with the change in Gerrit.
Shawn O. Pearcea608fb02009-04-17 12:11:24 -070075
76Configuration
77-------------
78
79review.URL.autoupload:
80
81To disable the "Upload ... (y/n)?" prompt, you can set a per-project
82or global Git configuration option. If review.URL.autoupload is set
83to "true" then repo will assume you always answer "y" at the prompt,
84and will not prompt you further. If it is set to "false" then repo
85will assume you always answer "n", and will abort.
86
Ben Komalo08a3f682010-07-15 16:03:02 -070087review.URL.autocopy:
88
89To automatically copy a user or mailing list to all uploaded reviews,
90you can set a per-project or global Git option to do so. Specifically,
91review.URL.autocopy can be set to a comma separated list of reviewers
92who you always want copied on all uploads with a non-empty --re
93argument.
94
Shawn O. Pearce3575b8f2010-07-15 17:00:14 -070095review.URL.username:
96
97Override the username used to connect to Gerrit Code Review.
98By default the local part of the email address is used.
99
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700100The URL must match the review URL listed in the manifest XML file,
101or in the .git/config within the project. For example:
102
103 [remote "origin"]
104 url = git://git.example.com/project.git
105 review = http://review.example.com/
106
107 [review "http://review.example.com/"]
108 autoupload = true
Ben Komalo08a3f682010-07-15 16:03:02 -0700109 autocopy = johndoe@company.com,my-team-alias@company.com
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700110
Shawn O. Pearce337fb9c2009-04-18 10:59:33 -0700111References
112----------
113
114Gerrit Code Review: http://code.google.com/p/gerrit/
115
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700116"""
117
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800118 def _Options(self, p):
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700119 p.add_option('-t',
120 dest='auto_topic', action='store_true',
121 help='Send local branch name to Gerrit Code Review')
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800122 p.add_option('--replace',
123 dest='replace', action='store_true',
124 help='Upload replacement patchesets from this branch')
Joe Onorato2896a792008-11-17 16:56:36 -0500125 p.add_option('--re', '--reviewers',
126 type='string', action='append', dest='reviewers',
127 help='Request reviews from these people.')
128 p.add_option('--cc',
129 type='string', action='append', dest='cc',
130 help='Also send email to these email addresses.')
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800131
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700132 def _SingleBranch(self, opt, branch, people):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700133 project = branch.project
134 name = branch.name
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700135 remote = project.GetBranch(name).remote
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700136
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700137 key = 'review.%s.autoupload' % remote.review
138 answer = project.config.GetBoolean(key)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700139
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700140 if answer is False:
141 _die("upload blocked by %s = false" % key)
142
143 if answer is None:
Shawn O. Pearce66bdd462009-04-17 18:47:22 -0700144 date = branch.date
145 list = branch.commits
146
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700147 print 'Upload project %s/:' % project.relpath
148 print ' branch %s (%2d commit%s, %s):' % (
149 name,
150 len(list),
151 len(list) != 1 and 's' or '',
152 date)
153 for commit in list:
154 print ' %s' % commit
155
Shawn O. Pearce8225cdc2009-04-18 11:00:35 -0700156 sys.stdout.write('to %s (y/n)? ' % remote.review)
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700157 answer = sys.stdin.readline().strip()
158 answer = answer in ('y', 'Y', 'yes', '1', 'true', 't')
159
160 if answer:
Dan Morrill879a9a52010-05-04 16:56:07 -0700161 if len(branch.commits) > UNUSUAL_COMMIT_THRESHOLD:
162 answer = _ConfirmManyUploads()
163
164 if answer:
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700165 self._UploadAndReport(opt, [branch], people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700166 else:
167 _die("upload aborted by user")
168
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700169 def _MultipleBranches(self, opt, pending, people):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700170 projects = {}
171 branches = {}
172
173 script = []
174 script.append('# Uncomment the branches to upload:')
175 for project, avail in pending:
176 script.append('#')
177 script.append('# project %s/:' % project.relpath)
178
179 b = {}
180 for branch in avail:
181 name = branch.name
182 date = branch.date
183 list = branch.commits
184
185 if b:
186 script.append('#')
187 script.append('# branch %s (%2d commit%s, %s):' % (
188 name,
189 len(list),
190 len(list) != 1 and 's' or '',
191 date))
192 for commit in list:
193 script.append('# %s' % commit)
194 b[name] = branch
195
196 projects[project.relpath] = project
197 branches[project.name] = b
198 script.append('')
199
200 script = Editor.EditString("\n".join(script)).split("\n")
201
202 project_re = re.compile(r'^#?\s*project\s*([^\s]+)/:$')
203 branch_re = re.compile(r'^\s*branch\s*([^\s(]+)\s*\(.*')
204
205 project = None
206 todo = []
207
208 for line in script:
209 m = project_re.match(line)
210 if m:
211 name = m.group(1)
212 project = projects.get(name)
213 if not project:
214 _die('project %s not available for upload', name)
215 continue
216
217 m = branch_re.match(line)
218 if m:
219 name = m.group(1)
220 if not project:
221 _die('project for branch %s not in script', name)
222 branch = branches[project.name].get(name)
223 if not branch:
224 _die('branch %s not in %s', name, project.relpath)
225 todo.append(branch)
226 if not todo:
227 _die("nothing uncommented for upload")
Dan Morrill879a9a52010-05-04 16:56:07 -0700228
229 many_commits = False
230 for branch in todo:
231 if len(branch.commits) > UNUSUAL_COMMIT_THRESHOLD:
232 many_commits = True
233 break
234 if many_commits:
235 if not _ConfirmManyUploads(multiple_branches=True):
236 _die("upload aborted by user")
237
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700238 self._UploadAndReport(opt, todo, people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700239
Ben Komalo08a3f682010-07-15 16:03:02 -0700240 def _AppendAutoCcList(self, branch, people):
241 """
242 Appends the list of users in the CC list in the git project's config if a
243 non-empty reviewer list was found.
244 """
245
246 name = branch.name
247 project = branch.project
248 key = 'review.%s.autocopy' % project.GetBranch(name).remote.review
249 raw_list = project.config.GetString(key)
250 if not raw_list is None and len(people[0]) > 0:
251 people[1].extend([entry.strip() for entry in raw_list.split(',')])
252
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700253 def _FindGerritChange(self, branch):
254 last_pub = branch.project.WasPublished(branch.name)
255 if last_pub is None:
256 return ""
257
258 refs = branch.GetPublishedRefs()
259 try:
260 # refs/changes/XYZ/N --> XYZ
261 return refs.get(last_pub).split('/')[-2]
262 except:
263 return ""
264
Shawn O. Pearce60829ba2010-07-16 07:42:45 -0700265 def _ReplaceBranch(self, opt, project, people):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800266 branch = project.CurrentBranch
267 if not branch:
268 print >>sys.stdout, "no branches ready for upload"
269 return
270 branch = project.GetUploadableBranch(branch)
271 if not branch:
272 print >>sys.stdout, "no branches ready for upload"
273 return
274
275 script = []
276 script.append('# Replacing from branch %s' % branch.name)
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700277
278 if len(branch.commits) == 1:
279 change = self._FindGerritChange(branch)
280 script.append('[%-6s] %s' % (change, branch.commits[0]))
281 else:
282 for commit in branch.commits:
283 script.append('[ ] %s' % commit)
284
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800285 script.append('')
286 script.append('# Insert change numbers in the brackets to add a new patch set.')
287 script.append('# To create a new change record, leave the brackets empty.')
288
289 script = Editor.EditString("\n".join(script)).split("\n")
290
291 change_re = re.compile(r'^\[\s*(\d{1,})\s*\]\s*([0-9a-f]{1,}) .*$')
292 to_replace = dict()
293 full_hashes = branch.unabbrev_commits
294
295 for line in script:
296 m = change_re.match(line)
297 if m:
Shawn O. Pearce67092442008-12-12 08:01:12 -0800298 c = m.group(1)
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800299 f = m.group(2)
300 try:
301 f = full_hashes[f]
302 except KeyError:
303 print 'fh = %s' % full_hashes
304 print >>sys.stderr, "error: commit %s not found" % f
305 sys.exit(1)
Shawn O. Pearce67092442008-12-12 08:01:12 -0800306 if c in to_replace:
307 print >>sys.stderr,\
308 "error: change %s cannot accept multiple commits" % c
309 sys.exit(1)
310 to_replace[c] = f
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800311
312 if not to_replace:
313 print >>sys.stderr, "error: no replacements specified"
314 print >>sys.stderr, " use 'repo upload' without --replace"
315 sys.exit(1)
316
Dan Morrill879a9a52010-05-04 16:56:07 -0700317 if len(branch.commits) > UNUSUAL_COMMIT_THRESHOLD:
318 if not _ConfirmManyUploads(multiple_branches=True):
319 _die("upload aborted by user")
320
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800321 branch.replace_changes = to_replace
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700322 self._UploadAndReport(opt, [branch], people)
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800323
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700324 def _UploadAndReport(self, opt, todo, original_people):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700325 have_errors = False
326 for branch in todo:
327 try:
Ben Komalo08a3f682010-07-15 16:03:02 -0700328 people = copy.deepcopy(original_people)
329 self._AppendAutoCcList(branch, people)
330
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500331 # Check if there are local changes that may have been forgotten
332 if branch.project.HasChanges():
333 key = 'review.%s.autoupload' % branch.project.remote.review
334 answer = branch.project.config.GetBoolean(key)
335
336 # if they want to auto upload, let's not ask because it could be automated
337 if answer is None:
338 sys.stdout.write('Uncommitted changes in ' + branch.project.name + ' (did you forget to amend?). Continue uploading? (y/n) ')
339 a = sys.stdin.readline().strip().lower()
340 if a not in ('y', 'yes', 't', 'true', 'on'):
341 print >>sys.stderr, "skipping upload"
342 branch.uploaded = False
343 branch.error = 'User aborted'
344 continue
345
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700346 branch.UploadForReview(people, auto_topic=opt.auto_topic)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700347 branch.uploaded = True
348 except UploadError, e:
349 branch.error = e
350 branch.uploaded = False
351 have_errors = True
352
353 print >>sys.stderr, ''
354 print >>sys.stderr, '--------------------------------------------'
355
356 if have_errors:
357 for branch in todo:
358 if not branch.uploaded:
359 print >>sys.stderr, '[FAILED] %-15s %-15s (%s)' % (
360 branch.project.relpath + '/', \
361 branch.name, \
362 branch.error)
363 print >>sys.stderr, ''
364
365 for branch in todo:
366 if branch.uploaded:
367 print >>sys.stderr, '[OK ] %-15s %s' % (
368 branch.project.relpath + '/',
369 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700370
371 if have_errors:
372 sys.exit(1)
373
374 def Execute(self, opt, args):
375 project_list = self.GetProjects(args)
376 pending = []
Joe Onorato2896a792008-11-17 16:56:36 -0500377 reviewers = []
378 cc = []
379
380 if opt.reviewers:
381 reviewers = _SplitEmails(opt.reviewers)
382 if opt.cc:
383 cc = _SplitEmails(opt.cc)
384 people = (reviewers,cc)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700385
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800386 if opt.replace:
387 if len(project_list) != 1:
388 print >>sys.stderr, \
389 'error: --replace requires exactly one project'
390 sys.exit(1)
Shawn O. Pearce60829ba2010-07-16 07:42:45 -0700391 self._ReplaceBranch(opt, project_list[0], people)
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800392 return
393
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700394 for project in project_list:
395 avail = project.GetUploadableBranches()
396 if avail:
397 pending.append((project, avail))
398
399 if not pending:
400 print >>sys.stdout, "no branches ready for upload"
401 elif len(pending) == 1 and len(pending[0][1]) == 1:
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700402 self._SingleBranch(opt, pending[0][1][0], people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700403 else:
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700404 self._MultipleBranches(opt, pending, people)