blob: 569e31c1c8047d97de0be7f509fd1f1fc45fd7d7 [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. Pearcea608fb02009-04-17 12:11:24 -070095The URL must match the review URL listed in the manifest XML file,
96or in the .git/config within the project. For example:
97
98 [remote "origin"]
99 url = git://git.example.com/project.git
100 review = http://review.example.com/
101
102 [review "http://review.example.com/"]
103 autoupload = true
Ben Komalo08a3f682010-07-15 16:03:02 -0700104 autocopy = johndoe@company.com,my-team-alias@company.com
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700105
Shawn O. Pearce337fb9c2009-04-18 10:59:33 -0700106References
107----------
108
109Gerrit Code Review: http://code.google.com/p/gerrit/
110
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700111"""
112
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800113 def _Options(self, p):
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700114 p.add_option('-t',
115 dest='auto_topic', action='store_true',
116 help='Send local branch name to Gerrit Code Review')
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800117 p.add_option('--replace',
118 dest='replace', action='store_true',
119 help='Upload replacement patchesets from this branch')
Joe Onorato2896a792008-11-17 16:56:36 -0500120 p.add_option('--re', '--reviewers',
121 type='string', action='append', dest='reviewers',
122 help='Request reviews from these people.')
123 p.add_option('--cc',
124 type='string', action='append', dest='cc',
125 help='Also send email to these email addresses.')
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800126
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700127 def _SingleBranch(self, opt, branch, people):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700128 project = branch.project
129 name = branch.name
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700130 remote = project.GetBranch(name).remote
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700131
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700132 key = 'review.%s.autoupload' % remote.review
133 answer = project.config.GetBoolean(key)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700134
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700135 if answer is False:
136 _die("upload blocked by %s = false" % key)
137
138 if answer is None:
Shawn O. Pearce66bdd462009-04-17 18:47:22 -0700139 date = branch.date
140 list = branch.commits
141
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700142 print 'Upload project %s/:' % project.relpath
143 print ' branch %s (%2d commit%s, %s):' % (
144 name,
145 len(list),
146 len(list) != 1 and 's' or '',
147 date)
148 for commit in list:
149 print ' %s' % commit
150
Shawn O. Pearce8225cdc2009-04-18 11:00:35 -0700151 sys.stdout.write('to %s (y/n)? ' % remote.review)
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700152 answer = sys.stdin.readline().strip()
153 answer = answer in ('y', 'Y', 'yes', '1', 'true', 't')
154
155 if answer:
Dan Morrill879a9a52010-05-04 16:56:07 -0700156 if len(branch.commits) > UNUSUAL_COMMIT_THRESHOLD:
157 answer = _ConfirmManyUploads()
158
159 if answer:
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700160 self._UploadAndReport(opt, [branch], people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700161 else:
162 _die("upload aborted by user")
163
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700164 def _MultipleBranches(self, opt, pending, people):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700165 projects = {}
166 branches = {}
167
168 script = []
169 script.append('# Uncomment the branches to upload:')
170 for project, avail in pending:
171 script.append('#')
172 script.append('# project %s/:' % project.relpath)
173
174 b = {}
175 for branch in avail:
176 name = branch.name
177 date = branch.date
178 list = branch.commits
179
180 if b:
181 script.append('#')
182 script.append('# branch %s (%2d commit%s, %s):' % (
183 name,
184 len(list),
185 len(list) != 1 and 's' or '',
186 date))
187 for commit in list:
188 script.append('# %s' % commit)
189 b[name] = branch
190
191 projects[project.relpath] = project
192 branches[project.name] = b
193 script.append('')
194
195 script = Editor.EditString("\n".join(script)).split("\n")
196
197 project_re = re.compile(r'^#?\s*project\s*([^\s]+)/:$')
198 branch_re = re.compile(r'^\s*branch\s*([^\s(]+)\s*\(.*')
199
200 project = None
201 todo = []
202
203 for line in script:
204 m = project_re.match(line)
205 if m:
206 name = m.group(1)
207 project = projects.get(name)
208 if not project:
209 _die('project %s not available for upload', name)
210 continue
211
212 m = branch_re.match(line)
213 if m:
214 name = m.group(1)
215 if not project:
216 _die('project for branch %s not in script', name)
217 branch = branches[project.name].get(name)
218 if not branch:
219 _die('branch %s not in %s', name, project.relpath)
220 todo.append(branch)
221 if not todo:
222 _die("nothing uncommented for upload")
Dan Morrill879a9a52010-05-04 16:56:07 -0700223
224 many_commits = False
225 for branch in todo:
226 if len(branch.commits) > UNUSUAL_COMMIT_THRESHOLD:
227 many_commits = True
228 break
229 if many_commits:
230 if not _ConfirmManyUploads(multiple_branches=True):
231 _die("upload aborted by user")
232
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700233 self._UploadAndReport(opt, todo, people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700234
Ben Komalo08a3f682010-07-15 16:03:02 -0700235 def _AppendAutoCcList(self, branch, people):
236 """
237 Appends the list of users in the CC list in the git project's config if a
238 non-empty reviewer list was found.
239 """
240
241 name = branch.name
242 project = branch.project
243 key = 'review.%s.autocopy' % project.GetBranch(name).remote.review
244 raw_list = project.config.GetString(key)
245 if not raw_list is None and len(people[0]) > 0:
246 people[1].extend([entry.strip() for entry in raw_list.split(',')])
247
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700248 def _FindGerritChange(self, branch):
249 last_pub = branch.project.WasPublished(branch.name)
250 if last_pub is None:
251 return ""
252
253 refs = branch.GetPublishedRefs()
254 try:
255 # refs/changes/XYZ/N --> XYZ
256 return refs.get(last_pub).split('/')[-2]
257 except:
258 return ""
259
Shawn O. Pearcee92ceeb2008-11-24 15:51:25 -0800260 def _ReplaceBranch(self, project, people):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800261 branch = project.CurrentBranch
262 if not branch:
263 print >>sys.stdout, "no branches ready for upload"
264 return
265 branch = project.GetUploadableBranch(branch)
266 if not branch:
267 print >>sys.stdout, "no branches ready for upload"
268 return
269
270 script = []
271 script.append('# Replacing from branch %s' % branch.name)
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700272
273 if len(branch.commits) == 1:
274 change = self._FindGerritChange(branch)
275 script.append('[%-6s] %s' % (change, branch.commits[0]))
276 else:
277 for commit in branch.commits:
278 script.append('[ ] %s' % commit)
279
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800280 script.append('')
281 script.append('# Insert change numbers in the brackets to add a new patch set.')
282 script.append('# To create a new change record, leave the brackets empty.')
283
284 script = Editor.EditString("\n".join(script)).split("\n")
285
286 change_re = re.compile(r'^\[\s*(\d{1,})\s*\]\s*([0-9a-f]{1,}) .*$')
287 to_replace = dict()
288 full_hashes = branch.unabbrev_commits
289
290 for line in script:
291 m = change_re.match(line)
292 if m:
Shawn O. Pearce67092442008-12-12 08:01:12 -0800293 c = m.group(1)
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800294 f = m.group(2)
295 try:
296 f = full_hashes[f]
297 except KeyError:
298 print 'fh = %s' % full_hashes
299 print >>sys.stderr, "error: commit %s not found" % f
300 sys.exit(1)
Shawn O. Pearce67092442008-12-12 08:01:12 -0800301 if c in to_replace:
302 print >>sys.stderr,\
303 "error: change %s cannot accept multiple commits" % c
304 sys.exit(1)
305 to_replace[c] = f
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800306
307 if not to_replace:
308 print >>sys.stderr, "error: no replacements specified"
309 print >>sys.stderr, " use 'repo upload' without --replace"
310 sys.exit(1)
311
Dan Morrill879a9a52010-05-04 16:56:07 -0700312 if len(branch.commits) > UNUSUAL_COMMIT_THRESHOLD:
313 if not _ConfirmManyUploads(multiple_branches=True):
314 _die("upload aborted by user")
315
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800316 branch.replace_changes = to_replace
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700317 self._UploadAndReport(opt, [branch], people)
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800318
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700319 def _UploadAndReport(self, opt, todo, original_people):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700320 have_errors = False
321 for branch in todo:
322 try:
Ben Komalo08a3f682010-07-15 16:03:02 -0700323 people = copy.deepcopy(original_people)
324 self._AppendAutoCcList(branch, people)
325
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500326 # Check if there are local changes that may have been forgotten
327 if branch.project.HasChanges():
328 key = 'review.%s.autoupload' % branch.project.remote.review
329 answer = branch.project.config.GetBoolean(key)
330
331 # if they want to auto upload, let's not ask because it could be automated
332 if answer is None:
333 sys.stdout.write('Uncommitted changes in ' + branch.project.name + ' (did you forget to amend?). Continue uploading? (y/n) ')
334 a = sys.stdin.readline().strip().lower()
335 if a not in ('y', 'yes', 't', 'true', 'on'):
336 print >>sys.stderr, "skipping upload"
337 branch.uploaded = False
338 branch.error = 'User aborted'
339 continue
340
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700341 branch.UploadForReview(people, auto_topic=opt.auto_topic)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700342 branch.uploaded = True
343 except UploadError, e:
344 branch.error = e
345 branch.uploaded = False
346 have_errors = True
347
348 print >>sys.stderr, ''
349 print >>sys.stderr, '--------------------------------------------'
350
351 if have_errors:
352 for branch in todo:
353 if not branch.uploaded:
354 print >>sys.stderr, '[FAILED] %-15s %-15s (%s)' % (
355 branch.project.relpath + '/', \
356 branch.name, \
357 branch.error)
358 print >>sys.stderr, ''
359
360 for branch in todo:
361 if branch.uploaded:
362 print >>sys.stderr, '[OK ] %-15s %s' % (
363 branch.project.relpath + '/',
364 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700365
366 if have_errors:
367 sys.exit(1)
368
369 def Execute(self, opt, args):
370 project_list = self.GetProjects(args)
371 pending = []
Joe Onorato2896a792008-11-17 16:56:36 -0500372 reviewers = []
373 cc = []
374
375 if opt.reviewers:
376 reviewers = _SplitEmails(opt.reviewers)
377 if opt.cc:
378 cc = _SplitEmails(opt.cc)
379 people = (reviewers,cc)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700380
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800381 if opt.replace:
382 if len(project_list) != 1:
383 print >>sys.stderr, \
384 'error: --replace requires exactly one project'
385 sys.exit(1)
Shawn O. Pearcee92ceeb2008-11-24 15:51:25 -0800386 self._ReplaceBranch(project_list[0], people)
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800387 return
388
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700389 for project in project_list:
390 avail = project.GetUploadableBranches()
391 if avail:
392 pending.append((project, avail))
393
394 if not pending:
395 print >>sys.stdout, "no branches ready for upload"
396 elif len(pending) == 1 and len(pending[0][1]) == 1:
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700397 self._SingleBranch(opt, pending[0][1][0], people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700398 else:
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700399 self._MultipleBranches(opt, pending, people)