blob: 4a22e26a1a0b79ab14b0d488132a78fb0fed97f3 [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
David Pursehouse59bbb582013-05-17 10:49:33 +090026from pyversion import is_python3
27if not is_python3():
28 # pylint:disable=W0622
Chirayu Desai217ea7d2013-03-01 19:14:38 +053029 input = raw_input
David Pursehouse59bbb582013-05-17 10:49:33 +090030 # pylint:enable=W0622
Chirayu Desai217ea7d2013-03-01 19:14:38 +053031
Dan Morrillf0a9a1a2010-05-05 08:18:35 -070032UNUSUAL_COMMIT_THRESHOLD = 5
Dan Morrill879a9a52010-05-04 16:56:07 -070033
34def _ConfirmManyUploads(multiple_branches=False):
35 if multiple_branches:
David Pursehouse2f9e7e42013-03-05 17:26:46 +090036 print('ATTENTION: One or more branches has an unusually high number '
Sarah Owenscecd1d82012-11-01 22:59:27 -070037 'of commits.')
Dan Morrill879a9a52010-05-04 16:56:07 -070038 else:
Sarah Owenscecd1d82012-11-01 22:59:27 -070039 print('ATTENTION: You are uploading an unusually high number of commits.')
David Pursehouse2f9e7e42013-03-05 17:26:46 +090040 print('YOU PROBABLY DO NOT MEAN TO DO THIS. (Did you rebase across '
Sarah Owenscecd1d82012-11-01 22:59:27 -070041 'branches?)')
Chirayu Desai217ea7d2013-03-01 19:14:38 +053042 answer = input("If you are sure you intend to do this, type 'yes': ").strip()
Dan Morrill879a9a52010-05-04 16:56:07 -070043 return answer == "yes"
44
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070045def _die(fmt, *args):
46 msg = fmt % args
Sarah Owenscecd1d82012-11-01 22:59:27 -070047 print('error: %s' % msg, file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070048 sys.exit(1)
49
Joe Onorato2896a792008-11-17 16:56:36 -050050def _SplitEmails(values):
51 result = []
David Pursehouse8a68ff92012-09-24 12:15:13 +090052 for value in values:
53 result.extend([s.strip() for s in value.split(',')])
Joe Onorato2896a792008-11-17 16:56:36 -050054 return result
55
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070056class Upload(InteractiveCommand):
57 common = True
58 helpSummary = "Upload changes for code review"
David Pursehouse8f62fb72012-11-14 12:09:38 +090059 helpUsage = """
Ficus Kirkpatricka0de6e82010-10-22 13:06:47 -070060%prog [--re --cc] [<project>]...
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070061"""
62 helpDescription = """
Shawn O. Pearce337fb9c2009-04-18 10:59:33 -070063The '%prog' command is used to send changes to the Gerrit Code
64Review system. It searches for topic branches in local projects
65that have not yet been published for review. If multiple topic
66branches are found, '%prog' opens an editor to allow the user to
67select which branches to upload.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070068
Shawn O. Pearce337fb9c2009-04-18 10:59:33 -070069'%prog' searches for uploadable changes in all projects listed at
70the command line. Projects can be specified either by name, or by
71a relative or absolute path to the project's local directory. If no
72projects are specified, '%prog' will search for uploadable changes
73in all projects listed in the manifest.
Joe Onorato2896a792008-11-17 16:56:36 -050074
75If the --reviewers or --cc options are passed, those emails are
76added to the respective list of users, and emails are sent to any
Shawn O. Pearce337fb9c2009-04-18 10:59:33 -070077new users. Users passed as --reviewers must already be registered
Joe Onorato2896a792008-11-17 16:56:36 -050078with the code review system, or the upload will fail.
Shawn O. Pearcea6df7d22008-12-12 08:04:07 -080079
Shawn O. Pearcea608fb02009-04-17 12:11:24 -070080Configuration
81-------------
82
83review.URL.autoupload:
84
Mike Frysingere9311272011-08-11 15:46:43 -040085To disable the "Upload ... (y/N)?" prompt, you can set a per-project
Shawn O. Pearcea608fb02009-04-17 12:11:24 -070086or global Git configuration option. If review.URL.autoupload is set
87to "true" then repo will assume you always answer "y" at the prompt,
88and will not prompt you further. If it is set to "false" then repo
89will assume you always answer "n", and will abort.
90
Ben Komalo08a3f682010-07-15 16:03:02 -070091review.URL.autocopy:
92
93To automatically copy a user or mailing list to all uploaded reviews,
94you can set a per-project or global Git option to do so. Specifically,
95review.URL.autocopy can be set to a comma separated list of reviewers
96who you always want copied on all uploads with a non-empty --re
97argument.
98
Shawn O. Pearce3575b8f2010-07-15 17:00:14 -070099review.URL.username:
100
101Override the username used to connect to Gerrit Code Review.
102By default the local part of the email address is used.
103
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700104The URL must match the review URL listed in the manifest XML file,
105or in the .git/config within the project. For example:
106
107 [remote "origin"]
108 url = git://git.example.com/project.git
109 review = http://review.example.com/
110
111 [review "http://review.example.com/"]
112 autoupload = true
Ben Komalo08a3f682010-07-15 16:03:02 -0700113 autocopy = johndoe@company.com,my-team-alias@company.com
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700114
Anthony Russellod666e932012-06-01 00:48:22 -0400115review.URL.uploadtopic:
116
117To add a topic branch whenever uploading a commit, you can set a
118per-project or global Git option to do so. If review.URL.uploadtopic
119is set to "true" then repo will assume you always want the equivalent
120of the -t option to the repo command. If unset or set to "false" then
121repo will make use of only the command line option.
122
Shawn O. Pearce337fb9c2009-04-18 10:59:33 -0700123References
124----------
125
126Gerrit Code Review: http://code.google.com/p/gerrit/
127
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700128"""
129
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800130 def _Options(self, p):
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700131 p.add_option('-t',
132 dest='auto_topic', action='store_true',
133 help='Send local branch name to Gerrit Code Review')
Joe Onorato2896a792008-11-17 16:56:36 -0500134 p.add_option('--re', '--reviewers',
135 type='string', action='append', dest='reviewers',
136 help='Request reviews from these people.')
137 p.add_option('--cc',
138 type='string', action='append', dest='cc',
139 help='Also send email to these email addresses.')
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700140 p.add_option('--br',
141 type='string', action='store', dest='branch',
142 help='Branch to upload.')
Daniel Sandlere9d6b612012-04-06 10:39:32 -0400143 p.add_option('--cbr', '--current-branch',
144 dest='current_branch', action='store_true',
145 help='Upload current git branch.')
Brian Harring435370c2012-07-28 15:37:04 -0700146 p.add_option('-d', '--draft',
147 action='store_true', dest='draft', default=False,
148 help='If specified, upload as a draft.')
Bryan Jacobsf609f912013-05-06 13:36:24 -0400149 p.add_option('-D', '--destination', '--dest',
150 type='string', action='store', dest='dest_branch',
151 metavar='BRANCH',
152 help='Submit for review on this target branch.')
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800153
Doug Anderson37282b42011-03-04 11:54:18 -0800154 # Options relating to upload hook. Note that verify and no-verify are NOT
155 # opposites of each other, which is why they store to different locations.
156 # We are using them to match 'git commit' syntax.
157 #
158 # Combinations:
159 # - no-verify=False, verify=False (DEFAULT):
160 # If stdout is a tty, can prompt about running upload hooks if needed.
161 # If user denies running hooks, the upload is cancelled. If stdout is
162 # not a tty and we would need to prompt about upload hooks, upload is
163 # cancelled.
164 # - no-verify=False, verify=True:
165 # Always run upload hooks with no prompt.
166 # - no-verify=True, verify=False:
167 # Never run upload hooks, but upload anyway (AKA bypass hooks).
168 # - no-verify=True, verify=True:
169 # Invalid
170 p.add_option('--no-verify',
171 dest='bypass_hooks', action='store_true',
172 help='Do not run the upload hook.')
173 p.add_option('--verify',
174 dest='allow_all_hooks', action='store_true',
175 help='Run the upload hook without prompting.')
176
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700177 def _SingleBranch(self, opt, branch, people):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700178 project = branch.project
179 name = branch.name
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700180 remote = project.GetBranch(name).remote
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700181
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700182 key = 'review.%s.autoupload' % remote.review
183 answer = project.config.GetBoolean(key)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700184
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700185 if answer is False:
186 _die("upload blocked by %s = false" % key)
187
188 if answer is None:
Shawn O. Pearce66bdd462009-04-17 18:47:22 -0700189 date = branch.date
David Pursehouse8a68ff92012-09-24 12:15:13 +0900190 commit_list = branch.commits
Shawn O. Pearce66bdd462009-04-17 18:47:22 -0700191
Bryan Jacobsf609f912013-05-06 13:36:24 -0400192 destination = project.dest_branch or project.revisionExpr
193 print('Upload project %s/ to remote branch %s:' % (project.relpath, destination))
Sarah Owenscecd1d82012-11-01 22:59:27 -0700194 print(' branch %s (%2d commit%s, %s):' % (
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700195 name,
David Pursehouse8a68ff92012-09-24 12:15:13 +0900196 len(commit_list),
197 len(commit_list) != 1 and 's' or '',
Sarah Owenscecd1d82012-11-01 22:59:27 -0700198 date))
David Pursehouse8a68ff92012-09-24 12:15:13 +0900199 for commit in commit_list:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700200 print(' %s' % commit)
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700201
Mike Frysingere9311272011-08-11 15:46:43 -0400202 sys.stdout.write('to %s (y/N)? ' % remote.review)
David Pursehousefc241242012-11-14 09:19:39 +0900203 answer = sys.stdin.readline().strip().lower()
204 answer = answer in ('y', 'yes', '1', 'true', 't')
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700205
206 if answer:
Dan Morrill879a9a52010-05-04 16:56:07 -0700207 if len(branch.commits) > UNUSUAL_COMMIT_THRESHOLD:
208 answer = _ConfirmManyUploads()
209
210 if answer:
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700211 self._UploadAndReport(opt, [branch], people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700212 else:
213 _die("upload aborted by user")
214
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700215 def _MultipleBranches(self, opt, pending, people):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700216 projects = {}
217 branches = {}
218
219 script = []
220 script.append('# Uncomment the branches to upload:')
221 for project, avail in pending:
222 script.append('#')
223 script.append('# project %s/:' % project.relpath)
224
225 b = {}
226 for branch in avail:
227 name = branch.name
228 date = branch.date
David Pursehouse8a68ff92012-09-24 12:15:13 +0900229 commit_list = branch.commits
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700230
231 if b:
232 script.append('#')
Christer Fletcher6a1f7372011-04-28 14:13:14 +0200233 script.append('# branch %s (%2d commit%s, %s) to remote branch %s:' % (
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700234 name,
David Pursehouse8a68ff92012-09-24 12:15:13 +0900235 len(commit_list),
236 len(commit_list) != 1 and 's' or '',
Christer Fletcher6a1f7372011-04-28 14:13:14 +0200237 date,
238 project.revisionExpr))
David Pursehouse8a68ff92012-09-24 12:15:13 +0900239 for commit in commit_list:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700240 script.append('# %s' % commit)
241 b[name] = branch
242
243 projects[project.relpath] = project
244 branches[project.name] = b
245 script.append('')
246
chenguodong605a9a42011-08-22 18:42:47 +0800247 script = [ x.encode('utf-8')
248 if issubclass(type(x), unicode)
249 else x
250 for x in script ]
251
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700252 script = Editor.EditString("\n".join(script)).split("\n")
253
254 project_re = re.compile(r'^#?\s*project\s*([^\s]+)/:$')
255 branch_re = re.compile(r'^\s*branch\s*([^\s(]+)\s*\(.*')
256
257 project = None
258 todo = []
259
260 for line in script:
261 m = project_re.match(line)
262 if m:
263 name = m.group(1)
264 project = projects.get(name)
265 if not project:
266 _die('project %s not available for upload', name)
267 continue
268
269 m = branch_re.match(line)
270 if m:
271 name = m.group(1)
272 if not project:
273 _die('project for branch %s not in script', name)
274 branch = branches[project.name].get(name)
275 if not branch:
276 _die('branch %s not in %s', name, project.relpath)
277 todo.append(branch)
278 if not todo:
279 _die("nothing uncommented for upload")
Dan Morrill879a9a52010-05-04 16:56:07 -0700280
281 many_commits = False
282 for branch in todo:
283 if len(branch.commits) > UNUSUAL_COMMIT_THRESHOLD:
284 many_commits = True
285 break
286 if many_commits:
287 if not _ConfirmManyUploads(multiple_branches=True):
288 _die("upload aborted by user")
289
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700290 self._UploadAndReport(opt, todo, people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700291
Ben Komalo08a3f682010-07-15 16:03:02 -0700292 def _AppendAutoCcList(self, branch, people):
293 """
294 Appends the list of users in the CC list in the git project's config if a
295 non-empty reviewer list was found.
296 """
297
298 name = branch.name
299 project = branch.project
300 key = 'review.%s.autocopy' % project.GetBranch(name).remote.review
301 raw_list = project.config.GetString(key)
302 if not raw_list is None and len(people[0]) > 0:
303 people[1].extend([entry.strip() for entry in raw_list.split(',')])
304
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700305 def _FindGerritChange(self, branch):
306 last_pub = branch.project.WasPublished(branch.name)
307 if last_pub is None:
308 return ""
309
310 refs = branch.GetPublishedRefs()
311 try:
312 # refs/changes/XYZ/N --> XYZ
313 return refs.get(last_pub).split('/')[-2]
David Pursehouse1d947b32012-10-25 12:23:11 +0900314 except (AttributeError, IndexError):
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700315 return ""
316
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700317 def _UploadAndReport(self, opt, todo, original_people):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700318 have_errors = False
319 for branch in todo:
320 try:
Ben Komalo08a3f682010-07-15 16:03:02 -0700321 people = copy.deepcopy(original_people)
322 self._AppendAutoCcList(branch, people)
323
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500324 # Check if there are local changes that may have been forgotten
325 if branch.project.HasChanges():
David Pursehousec1b86a22012-11-14 11:36:51 +0900326 key = 'review.%s.autoupload' % branch.project.remote.review
327 answer = branch.project.config.GetBoolean(key)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500328
David Pursehousec1b86a22012-11-14 11:36:51 +0900329 # if they want to auto upload, let's not ask because it could be automated
330 if answer is None:
331 sys.stdout.write('Uncommitted changes in ' + branch.project.name + ' (did you forget to amend?). Continue uploading? (y/N) ')
332 a = sys.stdin.readline().strip().lower()
333 if a not in ('y', 'yes', 't', 'true', 'on'):
334 print("skipping upload", file=sys.stderr)
335 branch.uploaded = False
336 branch.error = 'User aborted'
337 continue
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500338
Anthony Russellod666e932012-06-01 00:48:22 -0400339 # Check if topic branches should be sent to the server during upload
340 if opt.auto_topic is not True:
David Pursehousec1b86a22012-11-14 11:36:51 +0900341 key = 'review.%s.uploadtopic' % branch.project.remote.review
342 opt.auto_topic = branch.project.config.GetBoolean(key)
Anthony Russellod666e932012-06-01 00:48:22 -0400343
Bryan Jacobsf609f912013-05-06 13:36:24 -0400344 branch.UploadForReview(people, auto_topic=opt.auto_topic, draft=opt.draft, dest_branch=opt.dest_branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700345 branch.uploaded = True
Sarah Owensa5be53f2012-09-09 15:37:57 -0700346 except UploadError as e:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700347 branch.error = e
348 branch.uploaded = False
349 have_errors = True
350
Sarah Owenscecd1d82012-11-01 22:59:27 -0700351 print(file=sys.stderr)
352 print('----------------------------------------------------------------------', file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700353
354 if have_errors:
355 for branch in todo:
356 if not branch.uploaded:
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -0700357 if len(str(branch.error)) <= 30:
358 fmt = ' (%s)'
359 else:
360 fmt = '\n (%s)'
Sarah Owenscecd1d82012-11-01 22:59:27 -0700361 print(('[FAILED] %-15s %-15s' + fmt) % (
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700362 branch.project.relpath + '/', \
363 branch.name, \
Sarah Owenscecd1d82012-11-01 22:59:27 -0700364 str(branch.error)),
365 file=sys.stderr)
366 print()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700367
368 for branch in todo:
David Pursehousec1b86a22012-11-14 11:36:51 +0900369 if branch.uploaded:
370 print('[OK ] %-15s %s' % (
371 branch.project.relpath + '/',
372 branch.name),
373 file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700374
375 if have_errors:
376 sys.exit(1)
377
378 def Execute(self, opt, args):
379 project_list = self.GetProjects(args)
380 pending = []
Joe Onorato2896a792008-11-17 16:56:36 -0500381 reviewers = []
382 cc = []
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700383 branch = None
384
385 if opt.branch:
386 branch = opt.branch
Joe Onorato2896a792008-11-17 16:56:36 -0500387
Doug Anderson37282b42011-03-04 11:54:18 -0800388 for project in project_list:
Daniel Sandlere9d6b612012-04-06 10:39:32 -0400389 if opt.current_branch:
390 cbr = project.CurrentBranch
391 avail = [project.GetUploadableBranch(cbr)] if cbr else None
392 else:
393 avail = project.GetUploadableBranches(branch)
Doug Anderson37282b42011-03-04 11:54:18 -0800394 if avail:
395 pending.append((project, avail))
396
397 if pending and (not opt.bypass_hooks):
398 hook = RepoHook('pre-upload', self.manifest.repo_hooks_project,
399 self.manifest.topdir, abort_if_user_denies=True)
400 pending_proj_names = [project.name for (project, avail) in pending]
401 try:
402 hook.Run(opt.allow_all_hooks, project_list=pending_proj_names)
Sarah Owensa5be53f2012-09-09 15:37:57 -0700403 except HookError as e:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700404 print("ERROR: %s" % str(e), file=sys.stderr)
Doug Anderson37282b42011-03-04 11:54:18 -0800405 return
406
Joe Onorato2896a792008-11-17 16:56:36 -0500407 if opt.reviewers:
408 reviewers = _SplitEmails(opt.reviewers)
409 if opt.cc:
410 cc = _SplitEmails(opt.cc)
David Pursehouse8f62fb72012-11-14 12:09:38 +0900411 people = (reviewers, cc)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700412
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700413 if not pending:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700414 print("no branches ready for upload", file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700415 elif len(pending) == 1 and len(pending[0][1]) == 1:
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700416 self._SingleBranch(opt, pending[0][1][0], people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700417 else:
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700418 self._MultipleBranches(opt, pending, people)