blob: a34938e56ec7e02590235e4e2c13a69208380a6a [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
Sarah Owenscecd1d82012-11-01 22:59:27 -070016from __future__ import print_function
Ben Komalo08a3f682010-07-15 16:03:02 -070017import copy
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070018import re
19import sys
20
21from command import InteractiveCommand
22from editor import Editor
Doug Anderson37282b42011-03-04 11:54:18 -080023from error import HookError, UploadError
24from project import RepoHook
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070025
Chirayu Desai217ea7d2013-03-01 19:14:38 +053026try:
27 input = raw_input
28except NameError:
29 pass
30
Dan Morrillf0a9a1a2010-05-05 08:18:35 -070031UNUSUAL_COMMIT_THRESHOLD = 5
Dan Morrill879a9a52010-05-04 16:56:07 -070032
33def _ConfirmManyUploads(multiple_branches=False):
34 if multiple_branches:
David Pursehouse2f9e7e42013-03-05 17:26:46 +090035 print('ATTENTION: One or more branches has an unusually high number '
Sarah Owenscecd1d82012-11-01 22:59:27 -070036 'of commits.')
Dan Morrill879a9a52010-05-04 16:56:07 -070037 else:
Sarah Owenscecd1d82012-11-01 22:59:27 -070038 print('ATTENTION: You are uploading an unusually high number of commits.')
David Pursehouse2f9e7e42013-03-05 17:26:46 +090039 print('YOU PROBABLY DO NOT MEAN TO DO THIS. (Did you rebase across '
Sarah Owenscecd1d82012-11-01 22:59:27 -070040 'branches?)')
Chirayu Desai217ea7d2013-03-01 19:14:38 +053041 answer = input("If you are sure you intend to do this, type 'yes': ").strip()
Dan Morrill879a9a52010-05-04 16:56:07 -070042 return answer == "yes"
43
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070044def _die(fmt, *args):
45 msg = fmt % args
Sarah Owenscecd1d82012-11-01 22:59:27 -070046 print('error: %s' % msg, file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070047 sys.exit(1)
48
Joe Onorato2896a792008-11-17 16:56:36 -050049def _SplitEmails(values):
50 result = []
David Pursehouse8a68ff92012-09-24 12:15:13 +090051 for value in values:
52 result.extend([s.strip() for s in value.split(',')])
Joe Onorato2896a792008-11-17 16:56:36 -050053 return result
54
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070055class Upload(InteractiveCommand):
56 common = True
57 helpSummary = "Upload changes for code review"
David Pursehouse8f62fb72012-11-14 12:09:38 +090058 helpUsage = """
Ficus Kirkpatricka0de6e82010-10-22 13:06:47 -070059%prog [--re --cc] [<project>]...
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070060"""
61 helpDescription = """
Shawn O. Pearce337fb9c2009-04-18 10:59:33 -070062The '%prog' command is used to send changes to the Gerrit Code
63Review system. It searches for topic branches in local projects
64that have not yet been published for review. If multiple topic
65branches are found, '%prog' opens an editor to allow the user to
66select which branches to upload.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070067
Shawn O. Pearce337fb9c2009-04-18 10:59:33 -070068'%prog' searches for uploadable changes in all projects listed at
69the command line. Projects can be specified either by name, or by
70a relative or absolute path to the project's local directory. If no
71projects are specified, '%prog' will search for uploadable changes
72in all projects listed in the manifest.
Joe Onorato2896a792008-11-17 16:56:36 -050073
74If the --reviewers or --cc options are passed, those emails are
75added to the respective list of users, and emails are sent to any
Shawn O. Pearce337fb9c2009-04-18 10:59:33 -070076new users. Users passed as --reviewers must already be registered
Joe Onorato2896a792008-11-17 16:56:36 -050077with the code review system, or the upload will fail.
Shawn O. Pearcea6df7d22008-12-12 08:04:07 -080078
Shawn O. Pearcea608fb02009-04-17 12:11:24 -070079Configuration
80-------------
81
82review.URL.autoupload:
83
Mike Frysingere9311272011-08-11 15:46:43 -040084To disable the "Upload ... (y/N)?" prompt, you can set a per-project
Shawn O. Pearcea608fb02009-04-17 12:11:24 -070085or global Git configuration option. If review.URL.autoupload is set
86to "true" then repo will assume you always answer "y" at the prompt,
87and will not prompt you further. If it is set to "false" then repo
88will assume you always answer "n", and will abort.
89
Ben Komalo08a3f682010-07-15 16:03:02 -070090review.URL.autocopy:
91
92To automatically copy a user or mailing list to all uploaded reviews,
93you can set a per-project or global Git option to do so. Specifically,
94review.URL.autocopy can be set to a comma separated list of reviewers
95who you always want copied on all uploads with a non-empty --re
96argument.
97
Shawn O. Pearce3575b8f2010-07-15 17:00:14 -070098review.URL.username:
99
100Override the username used to connect to Gerrit Code Review.
101By default the local part of the email address is used.
102
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700103The URL must match the review URL listed in the manifest XML file,
104or in the .git/config within the project. For example:
105
106 [remote "origin"]
107 url = git://git.example.com/project.git
108 review = http://review.example.com/
109
110 [review "http://review.example.com/"]
111 autoupload = true
Ben Komalo08a3f682010-07-15 16:03:02 -0700112 autocopy = johndoe@company.com,my-team-alias@company.com
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700113
Anthony Russellod666e932012-06-01 00:48:22 -0400114review.URL.uploadtopic:
115
116To add a topic branch whenever uploading a commit, you can set a
117per-project or global Git option to do so. If review.URL.uploadtopic
118is set to "true" then repo will assume you always want the equivalent
119of the -t option to the repo command. If unset or set to "false" then
120repo will make use of only the command line option.
121
Shawn O. Pearce337fb9c2009-04-18 10:59:33 -0700122References
123----------
124
125Gerrit Code Review: http://code.google.com/p/gerrit/
126
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700127"""
128
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800129 def _Options(self, p):
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700130 p.add_option('-t',
131 dest='auto_topic', action='store_true',
132 help='Send local branch name to Gerrit Code Review')
Joe Onorato2896a792008-11-17 16:56:36 -0500133 p.add_option('--re', '--reviewers',
134 type='string', action='append', dest='reviewers',
135 help='Request reviews from these people.')
136 p.add_option('--cc',
137 type='string', action='append', dest='cc',
138 help='Also send email to these email addresses.')
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700139 p.add_option('--br',
140 type='string', action='store', dest='branch',
141 help='Branch to upload.')
Daniel Sandlere9d6b612012-04-06 10:39:32 -0400142 p.add_option('--cbr', '--current-branch',
143 dest='current_branch', action='store_true',
144 help='Upload current git branch.')
Brian Harring435370c2012-07-28 15:37:04 -0700145 p.add_option('-d', '--draft',
146 action='store_true', dest='draft', default=False,
147 help='If specified, upload as a draft.')
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800148
Doug Anderson37282b42011-03-04 11:54:18 -0800149 # Options relating to upload hook. Note that verify and no-verify are NOT
150 # opposites of each other, which is why they store to different locations.
151 # We are using them to match 'git commit' syntax.
152 #
153 # Combinations:
154 # - no-verify=False, verify=False (DEFAULT):
155 # If stdout is a tty, can prompt about running upload hooks if needed.
156 # If user denies running hooks, the upload is cancelled. If stdout is
157 # not a tty and we would need to prompt about upload hooks, upload is
158 # cancelled.
159 # - no-verify=False, verify=True:
160 # Always run upload hooks with no prompt.
161 # - no-verify=True, verify=False:
162 # Never run upload hooks, but upload anyway (AKA bypass hooks).
163 # - no-verify=True, verify=True:
164 # Invalid
165 p.add_option('--no-verify',
166 dest='bypass_hooks', action='store_true',
167 help='Do not run the upload hook.')
168 p.add_option('--verify',
169 dest='allow_all_hooks', action='store_true',
170 help='Run the upload hook without prompting.')
171
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700172 def _SingleBranch(self, opt, branch, people):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700173 project = branch.project
174 name = branch.name
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700175 remote = project.GetBranch(name).remote
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700176
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700177 key = 'review.%s.autoupload' % remote.review
178 answer = project.config.GetBoolean(key)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700179
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700180 if answer is False:
181 _die("upload blocked by %s = false" % key)
182
183 if answer is None:
Shawn O. Pearce66bdd462009-04-17 18:47:22 -0700184 date = branch.date
David Pursehouse8a68ff92012-09-24 12:15:13 +0900185 commit_list = branch.commits
Shawn O. Pearce66bdd462009-04-17 18:47:22 -0700186
Sarah Owenscecd1d82012-11-01 22:59:27 -0700187 print('Upload project %s/ to remote branch %s:' % (project.relpath, project.revisionExpr))
188 print(' branch %s (%2d commit%s, %s):' % (
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700189 name,
David Pursehouse8a68ff92012-09-24 12:15:13 +0900190 len(commit_list),
191 len(commit_list) != 1 and 's' or '',
Sarah Owenscecd1d82012-11-01 22:59:27 -0700192 date))
David Pursehouse8a68ff92012-09-24 12:15:13 +0900193 for commit in commit_list:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700194 print(' %s' % commit)
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700195
Mike Frysingere9311272011-08-11 15:46:43 -0400196 sys.stdout.write('to %s (y/N)? ' % remote.review)
David Pursehousefc241242012-11-14 09:19:39 +0900197 answer = sys.stdin.readline().strip().lower()
198 answer = answer in ('y', 'yes', '1', 'true', 't')
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700199
200 if answer:
Dan Morrill879a9a52010-05-04 16:56:07 -0700201 if len(branch.commits) > UNUSUAL_COMMIT_THRESHOLD:
202 answer = _ConfirmManyUploads()
203
204 if answer:
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700205 self._UploadAndReport(opt, [branch], people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700206 else:
207 _die("upload aborted by user")
208
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700209 def _MultipleBranches(self, opt, pending, people):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700210 projects = {}
211 branches = {}
212
213 script = []
214 script.append('# Uncomment the branches to upload:')
215 for project, avail in pending:
216 script.append('#')
217 script.append('# project %s/:' % project.relpath)
218
219 b = {}
220 for branch in avail:
221 name = branch.name
222 date = branch.date
David Pursehouse8a68ff92012-09-24 12:15:13 +0900223 commit_list = branch.commits
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700224
225 if b:
226 script.append('#')
Christer Fletcher6a1f7372011-04-28 14:13:14 +0200227 script.append('# branch %s (%2d commit%s, %s) to remote branch %s:' % (
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700228 name,
David Pursehouse8a68ff92012-09-24 12:15:13 +0900229 len(commit_list),
230 len(commit_list) != 1 and 's' or '',
Christer Fletcher6a1f7372011-04-28 14:13:14 +0200231 date,
232 project.revisionExpr))
David Pursehouse8a68ff92012-09-24 12:15:13 +0900233 for commit in commit_list:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700234 script.append('# %s' % commit)
235 b[name] = branch
236
237 projects[project.relpath] = project
238 branches[project.name] = b
239 script.append('')
240
chenguodong605a9a42011-08-22 18:42:47 +0800241 script = [ x.encode('utf-8')
242 if issubclass(type(x), unicode)
243 else x
244 for x in script ]
245
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700246 script = Editor.EditString("\n".join(script)).split("\n")
247
248 project_re = re.compile(r'^#?\s*project\s*([^\s]+)/:$')
249 branch_re = re.compile(r'^\s*branch\s*([^\s(]+)\s*\(.*')
250
251 project = None
252 todo = []
253
254 for line in script:
255 m = project_re.match(line)
256 if m:
257 name = m.group(1)
258 project = projects.get(name)
259 if not project:
260 _die('project %s not available for upload', name)
261 continue
262
263 m = branch_re.match(line)
264 if m:
265 name = m.group(1)
266 if not project:
267 _die('project for branch %s not in script', name)
268 branch = branches[project.name].get(name)
269 if not branch:
270 _die('branch %s not in %s', name, project.relpath)
271 todo.append(branch)
272 if not todo:
273 _die("nothing uncommented for upload")
Dan Morrill879a9a52010-05-04 16:56:07 -0700274
275 many_commits = False
276 for branch in todo:
277 if len(branch.commits) > UNUSUAL_COMMIT_THRESHOLD:
278 many_commits = True
279 break
280 if many_commits:
281 if not _ConfirmManyUploads(multiple_branches=True):
282 _die("upload aborted by user")
283
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700284 self._UploadAndReport(opt, todo, people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700285
Ben Komalo08a3f682010-07-15 16:03:02 -0700286 def _AppendAutoCcList(self, branch, people):
287 """
288 Appends the list of users in the CC list in the git project's config if a
289 non-empty reviewer list was found.
290 """
291
292 name = branch.name
293 project = branch.project
294 key = 'review.%s.autocopy' % project.GetBranch(name).remote.review
295 raw_list = project.config.GetString(key)
296 if not raw_list is None and len(people[0]) > 0:
297 people[1].extend([entry.strip() for entry in raw_list.split(',')])
298
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700299 def _FindGerritChange(self, branch):
300 last_pub = branch.project.WasPublished(branch.name)
301 if last_pub is None:
302 return ""
303
304 refs = branch.GetPublishedRefs()
305 try:
306 # refs/changes/XYZ/N --> XYZ
307 return refs.get(last_pub).split('/')[-2]
David Pursehouse1d947b32012-10-25 12:23:11 +0900308 except (AttributeError, IndexError):
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700309 return ""
310
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700311 def _UploadAndReport(self, opt, todo, original_people):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700312 have_errors = False
313 for branch in todo:
314 try:
Ben Komalo08a3f682010-07-15 16:03:02 -0700315 people = copy.deepcopy(original_people)
316 self._AppendAutoCcList(branch, people)
317
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500318 # Check if there are local changes that may have been forgotten
319 if branch.project.HasChanges():
David Pursehousec1b86a22012-11-14 11:36:51 +0900320 key = 'review.%s.autoupload' % branch.project.remote.review
321 answer = branch.project.config.GetBoolean(key)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500322
David Pursehousec1b86a22012-11-14 11:36:51 +0900323 # if they want to auto upload, let's not ask because it could be automated
324 if answer is None:
325 sys.stdout.write('Uncommitted changes in ' + branch.project.name + ' (did you forget to amend?). Continue uploading? (y/N) ')
326 a = sys.stdin.readline().strip().lower()
327 if a not in ('y', 'yes', 't', 'true', 'on'):
328 print("skipping upload", file=sys.stderr)
329 branch.uploaded = False
330 branch.error = 'User aborted'
331 continue
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500332
Anthony Russellod666e932012-06-01 00:48:22 -0400333 # Check if topic branches should be sent to the server during upload
334 if opt.auto_topic is not True:
David Pursehousec1b86a22012-11-14 11:36:51 +0900335 key = 'review.%s.uploadtopic' % branch.project.remote.review
336 opt.auto_topic = branch.project.config.GetBoolean(key)
Anthony Russellod666e932012-06-01 00:48:22 -0400337
Brian Harring435370c2012-07-28 15:37:04 -0700338 branch.UploadForReview(people, auto_topic=opt.auto_topic, draft=opt.draft)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700339 branch.uploaded = True
Sarah Owensa5be53f2012-09-09 15:37:57 -0700340 except UploadError as e:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700341 branch.error = e
342 branch.uploaded = False
343 have_errors = True
344
Sarah Owenscecd1d82012-11-01 22:59:27 -0700345 print(file=sys.stderr)
346 print('----------------------------------------------------------------------', file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700347
348 if have_errors:
349 for branch in todo:
350 if not branch.uploaded:
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -0700351 if len(str(branch.error)) <= 30:
352 fmt = ' (%s)'
353 else:
354 fmt = '\n (%s)'
Sarah Owenscecd1d82012-11-01 22:59:27 -0700355 print(('[FAILED] %-15s %-15s' + fmt) % (
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700356 branch.project.relpath + '/', \
357 branch.name, \
Sarah Owenscecd1d82012-11-01 22:59:27 -0700358 str(branch.error)),
359 file=sys.stderr)
360 print()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700361
362 for branch in todo:
David Pursehousec1b86a22012-11-14 11:36:51 +0900363 if branch.uploaded:
364 print('[OK ] %-15s %s' % (
365 branch.project.relpath + '/',
366 branch.name),
367 file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700368
369 if have_errors:
370 sys.exit(1)
371
372 def Execute(self, opt, args):
373 project_list = self.GetProjects(args)
374 pending = []
Joe Onorato2896a792008-11-17 16:56:36 -0500375 reviewers = []
376 cc = []
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700377 branch = None
378
379 if opt.branch:
380 branch = opt.branch
Joe Onorato2896a792008-11-17 16:56:36 -0500381
Doug Anderson37282b42011-03-04 11:54:18 -0800382 for project in project_list:
Daniel Sandlere9d6b612012-04-06 10:39:32 -0400383 if opt.current_branch:
384 cbr = project.CurrentBranch
385 avail = [project.GetUploadableBranch(cbr)] if cbr else None
386 else:
387 avail = project.GetUploadableBranches(branch)
Doug Anderson37282b42011-03-04 11:54:18 -0800388 if avail:
389 pending.append((project, avail))
390
391 if pending and (not opt.bypass_hooks):
392 hook = RepoHook('pre-upload', self.manifest.repo_hooks_project,
393 self.manifest.topdir, abort_if_user_denies=True)
394 pending_proj_names = [project.name for (project, avail) in pending]
395 try:
396 hook.Run(opt.allow_all_hooks, project_list=pending_proj_names)
Sarah Owensa5be53f2012-09-09 15:37:57 -0700397 except HookError as e:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700398 print("ERROR: %s" % str(e), file=sys.stderr)
Doug Anderson37282b42011-03-04 11:54:18 -0800399 return
400
Joe Onorato2896a792008-11-17 16:56:36 -0500401 if opt.reviewers:
402 reviewers = _SplitEmails(opt.reviewers)
403 if opt.cc:
404 cc = _SplitEmails(opt.cc)
David Pursehouse8f62fb72012-11-14 12:09:38 +0900405 people = (reviewers, cc)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700406
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700407 if not pending:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700408 print("no branches ready for upload", file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700409 elif len(pending) == 1 and len(pending[0][1]) == 1:
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700410 self._SingleBranch(opt, pending[0][1][0], people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700411 else:
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700412 self._MultipleBranches(opt, pending, people)