blob: f5833dd336353361234d8fe4e905c2b81ab570b1 [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
Chirayu Desai610d3c42013-06-24 14:02:12 +0530192 destination = opt.dest_branch or project.dest_branch or project.revisionExpr
Bryan Jacobsf609f912013-05-06 13:36:24 -0400193 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:
Bryan Jacobs710d4b02013-05-31 15:28:05 -0400227 if branch is None:
228 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700229 name = branch.name
230 date = branch.date
David Pursehouse8a68ff92012-09-24 12:15:13 +0900231 commit_list = branch.commits
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700232
233 if b:
234 script.append('#')
Bryan Jacobs691a7592013-05-31 15:45:28 -0400235 destination = opt.dest_branch or project.dest_branch or project.revisionExpr
Christer Fletcher6a1f7372011-04-28 14:13:14 +0200236 script.append('# branch %s (%2d commit%s, %s) to remote branch %s:' % (
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700237 name,
David Pursehouse8a68ff92012-09-24 12:15:13 +0900238 len(commit_list),
239 len(commit_list) != 1 and 's' or '',
Christer Fletcher6a1f7372011-04-28 14:13:14 +0200240 date,
Bryan Jacobs691a7592013-05-31 15:45:28 -0400241 destination))
David Pursehouse8a68ff92012-09-24 12:15:13 +0900242 for commit in commit_list:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700243 script.append('# %s' % commit)
244 b[name] = branch
245
246 projects[project.relpath] = project
247 branches[project.name] = b
248 script.append('')
249
chenguodong605a9a42011-08-22 18:42:47 +0800250 script = [ x.encode('utf-8')
251 if issubclass(type(x), unicode)
252 else x
253 for x in script ]
254
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700255 script = Editor.EditString("\n".join(script)).split("\n")
256
257 project_re = re.compile(r'^#?\s*project\s*([^\s]+)/:$')
258 branch_re = re.compile(r'^\s*branch\s*([^\s(]+)\s*\(.*')
259
260 project = None
261 todo = []
262
263 for line in script:
264 m = project_re.match(line)
265 if m:
266 name = m.group(1)
267 project = projects.get(name)
268 if not project:
269 _die('project %s not available for upload', name)
270 continue
271
272 m = branch_re.match(line)
273 if m:
274 name = m.group(1)
275 if not project:
276 _die('project for branch %s not in script', name)
277 branch = branches[project.name].get(name)
278 if not branch:
279 _die('branch %s not in %s', name, project.relpath)
280 todo.append(branch)
281 if not todo:
282 _die("nothing uncommented for upload")
Dan Morrill879a9a52010-05-04 16:56:07 -0700283
284 many_commits = False
285 for branch in todo:
286 if len(branch.commits) > UNUSUAL_COMMIT_THRESHOLD:
287 many_commits = True
288 break
289 if many_commits:
290 if not _ConfirmManyUploads(multiple_branches=True):
291 _die("upload aborted by user")
292
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700293 self._UploadAndReport(opt, todo, people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700294
Ben Komalo08a3f682010-07-15 16:03:02 -0700295 def _AppendAutoCcList(self, branch, people):
296 """
297 Appends the list of users in the CC list in the git project's config if a
298 non-empty reviewer list was found.
299 """
300
301 name = branch.name
302 project = branch.project
303 key = 'review.%s.autocopy' % project.GetBranch(name).remote.review
304 raw_list = project.config.GetString(key)
305 if not raw_list is None and len(people[0]) > 0:
306 people[1].extend([entry.strip() for entry in raw_list.split(',')])
307
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700308 def _FindGerritChange(self, branch):
309 last_pub = branch.project.WasPublished(branch.name)
310 if last_pub is None:
311 return ""
312
313 refs = branch.GetPublishedRefs()
314 try:
315 # refs/changes/XYZ/N --> XYZ
316 return refs.get(last_pub).split('/')[-2]
David Pursehouse1d947b32012-10-25 12:23:11 +0900317 except (AttributeError, IndexError):
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700318 return ""
319
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700320 def _UploadAndReport(self, opt, todo, original_people):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700321 have_errors = False
322 for branch in todo:
323 try:
Ben Komalo08a3f682010-07-15 16:03:02 -0700324 people = copy.deepcopy(original_people)
325 self._AppendAutoCcList(branch, people)
326
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500327 # Check if there are local changes that may have been forgotten
328 if branch.project.HasChanges():
David Pursehousec1b86a22012-11-14 11:36:51 +0900329 key = 'review.%s.autoupload' % branch.project.remote.review
330 answer = branch.project.config.GetBoolean(key)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500331
David Pursehousec1b86a22012-11-14 11:36:51 +0900332 # if they want to auto upload, let's not ask because it could be automated
333 if answer is None:
334 sys.stdout.write('Uncommitted changes in ' + branch.project.name + ' (did you forget to amend?). Continue uploading? (y/N) ')
335 a = sys.stdin.readline().strip().lower()
336 if a not in ('y', 'yes', 't', 'true', 'on'):
337 print("skipping upload", file=sys.stderr)
338 branch.uploaded = False
339 branch.error = 'User aborted'
340 continue
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500341
Anthony Russellod666e932012-06-01 00:48:22 -0400342 # Check if topic branches should be sent to the server during upload
343 if opt.auto_topic is not True:
David Pursehousec1b86a22012-11-14 11:36:51 +0900344 key = 'review.%s.uploadtopic' % branch.project.remote.review
345 opt.auto_topic = branch.project.config.GetBoolean(key)
Anthony Russellod666e932012-06-01 00:48:22 -0400346
Bryan Jacobs691a7592013-05-31 15:45:28 -0400347 destination = opt.dest_branch or branch.project.dest_branch or branch.project.revisionExpr
348 branch.UploadForReview(people, auto_topic=opt.auto_topic, draft=opt.draft, dest_branch=destination)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700349 branch.uploaded = True
Sarah Owensa5be53f2012-09-09 15:37:57 -0700350 except UploadError as e:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700351 branch.error = e
352 branch.uploaded = False
353 have_errors = True
354
Sarah Owenscecd1d82012-11-01 22:59:27 -0700355 print(file=sys.stderr)
356 print('----------------------------------------------------------------------', file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700357
358 if have_errors:
359 for branch in todo:
360 if not branch.uploaded:
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -0700361 if len(str(branch.error)) <= 30:
362 fmt = ' (%s)'
363 else:
364 fmt = '\n (%s)'
Sarah Owenscecd1d82012-11-01 22:59:27 -0700365 print(('[FAILED] %-15s %-15s' + fmt) % (
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700366 branch.project.relpath + '/', \
367 branch.name, \
Sarah Owenscecd1d82012-11-01 22:59:27 -0700368 str(branch.error)),
369 file=sys.stderr)
370 print()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700371
372 for branch in todo:
David Pursehousec1b86a22012-11-14 11:36:51 +0900373 if branch.uploaded:
374 print('[OK ] %-15s %s' % (
375 branch.project.relpath + '/',
376 branch.name),
377 file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700378
379 if have_errors:
380 sys.exit(1)
381
382 def Execute(self, opt, args):
383 project_list = self.GetProjects(args)
384 pending = []
Joe Onorato2896a792008-11-17 16:56:36 -0500385 reviewers = []
386 cc = []
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700387 branch = None
388
389 if opt.branch:
390 branch = opt.branch
Joe Onorato2896a792008-11-17 16:56:36 -0500391
Doug Anderson37282b42011-03-04 11:54:18 -0800392 for project in project_list:
Daniel Sandlere9d6b612012-04-06 10:39:32 -0400393 if opt.current_branch:
394 cbr = project.CurrentBranch
395 avail = [project.GetUploadableBranch(cbr)] if cbr else None
396 else:
397 avail = project.GetUploadableBranches(branch)
Doug Anderson37282b42011-03-04 11:54:18 -0800398 if avail:
399 pending.append((project, avail))
400
401 if pending and (not opt.bypass_hooks):
402 hook = RepoHook('pre-upload', self.manifest.repo_hooks_project,
403 self.manifest.topdir, abort_if_user_denies=True)
404 pending_proj_names = [project.name for (project, avail) in pending]
405 try:
406 hook.Run(opt.allow_all_hooks, project_list=pending_proj_names)
Sarah Owensa5be53f2012-09-09 15:37:57 -0700407 except HookError as e:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700408 print("ERROR: %s" % str(e), file=sys.stderr)
Doug Anderson37282b42011-03-04 11:54:18 -0800409 return
410
Joe Onorato2896a792008-11-17 16:56:36 -0500411 if opt.reviewers:
412 reviewers = _SplitEmails(opt.reviewers)
413 if opt.cc:
414 cc = _SplitEmails(opt.cc)
David Pursehouse8f62fb72012-11-14 12:09:38 +0900415 people = (reviewers, cc)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700416
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700417 if not pending:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700418 print("no branches ready for upload", file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700419 elif len(pending) == 1 and len(pending[0][1]) == 1:
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700420 self._SingleBranch(opt, pending[0][1][0], people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700421 else:
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700422 self._MultipleBranches(opt, pending, people)