blob: c051f1831a68379f008860ef176909952bb9b732 [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:
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('#')
Christer Fletcher6a1f7372011-04-28 14:13:14 +0200235 script.append('# branch %s (%2d commit%s, %s) to remote branch %s:' % (
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700236 name,
David Pursehouse8a68ff92012-09-24 12:15:13 +0900237 len(commit_list),
238 len(commit_list) != 1 and 's' or '',
Christer Fletcher6a1f7372011-04-28 14:13:14 +0200239 date,
240 project.revisionExpr))
David Pursehouse8a68ff92012-09-24 12:15:13 +0900241 for commit in commit_list:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700242 script.append('# %s' % commit)
243 b[name] = branch
244
245 projects[project.relpath] = project
246 branches[project.name] = b
247 script.append('')
248
chenguodong605a9a42011-08-22 18:42:47 +0800249 script = [ x.encode('utf-8')
250 if issubclass(type(x), unicode)
251 else x
252 for x in script ]
253
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700254 script = Editor.EditString("\n".join(script)).split("\n")
255
256 project_re = re.compile(r'^#?\s*project\s*([^\s]+)/:$')
257 branch_re = re.compile(r'^\s*branch\s*([^\s(]+)\s*\(.*')
258
259 project = None
260 todo = []
261
262 for line in script:
263 m = project_re.match(line)
264 if m:
265 name = m.group(1)
266 project = projects.get(name)
267 if not project:
268 _die('project %s not available for upload', name)
269 continue
270
271 m = branch_re.match(line)
272 if m:
273 name = m.group(1)
274 if not project:
275 _die('project for branch %s not in script', name)
276 branch = branches[project.name].get(name)
277 if not branch:
278 _die('branch %s not in %s', name, project.relpath)
279 todo.append(branch)
280 if not todo:
281 _die("nothing uncommented for upload")
Dan Morrill879a9a52010-05-04 16:56:07 -0700282
283 many_commits = False
284 for branch in todo:
285 if len(branch.commits) > UNUSUAL_COMMIT_THRESHOLD:
286 many_commits = True
287 break
288 if many_commits:
289 if not _ConfirmManyUploads(multiple_branches=True):
290 _die("upload aborted by user")
291
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700292 self._UploadAndReport(opt, todo, people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700293
Ben Komalo08a3f682010-07-15 16:03:02 -0700294 def _AppendAutoCcList(self, branch, people):
295 """
296 Appends the list of users in the CC list in the git project's config if a
297 non-empty reviewer list was found.
298 """
299
300 name = branch.name
301 project = branch.project
302 key = 'review.%s.autocopy' % project.GetBranch(name).remote.review
303 raw_list = project.config.GetString(key)
304 if not raw_list is None and len(people[0]) > 0:
305 people[1].extend([entry.strip() for entry in raw_list.split(',')])
306
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700307 def _FindGerritChange(self, branch):
308 last_pub = branch.project.WasPublished(branch.name)
309 if last_pub is None:
310 return ""
311
312 refs = branch.GetPublishedRefs()
313 try:
314 # refs/changes/XYZ/N --> XYZ
315 return refs.get(last_pub).split('/')[-2]
David Pursehouse1d947b32012-10-25 12:23:11 +0900316 except (AttributeError, IndexError):
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700317 return ""
318
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():
David Pursehousec1b86a22012-11-14 11:36:51 +0900328 key = 'review.%s.autoupload' % branch.project.remote.review
329 answer = branch.project.config.GetBoolean(key)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500330
David Pursehousec1b86a22012-11-14 11:36:51 +0900331 # 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("skipping upload", file=sys.stderr)
337 branch.uploaded = False
338 branch.error = 'User aborted'
339 continue
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500340
Anthony Russellod666e932012-06-01 00:48:22 -0400341 # Check if topic branches should be sent to the server during upload
342 if opt.auto_topic is not True:
David Pursehousec1b86a22012-11-14 11:36:51 +0900343 key = 'review.%s.uploadtopic' % branch.project.remote.review
344 opt.auto_topic = branch.project.config.GetBoolean(key)
Anthony Russellod666e932012-06-01 00:48:22 -0400345
Bryan Jacobsf609f912013-05-06 13:36:24 -0400346 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 -0700347 branch.uploaded = True
Sarah Owensa5be53f2012-09-09 15:37:57 -0700348 except UploadError as e:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700349 branch.error = e
350 branch.uploaded = False
351 have_errors = True
352
Sarah Owenscecd1d82012-11-01 22:59:27 -0700353 print(file=sys.stderr)
354 print('----------------------------------------------------------------------', file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700355
356 if have_errors:
357 for branch in todo:
358 if not branch.uploaded:
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -0700359 if len(str(branch.error)) <= 30:
360 fmt = ' (%s)'
361 else:
362 fmt = '\n (%s)'
Sarah Owenscecd1d82012-11-01 22:59:27 -0700363 print(('[FAILED] %-15s %-15s' + fmt) % (
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700364 branch.project.relpath + '/', \
365 branch.name, \
Sarah Owenscecd1d82012-11-01 22:59:27 -0700366 str(branch.error)),
367 file=sys.stderr)
368 print()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700369
370 for branch in todo:
David Pursehousec1b86a22012-11-14 11:36:51 +0900371 if branch.uploaded:
372 print('[OK ] %-15s %s' % (
373 branch.project.relpath + '/',
374 branch.name),
375 file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700376
377 if have_errors:
378 sys.exit(1)
379
380 def Execute(self, opt, args):
381 project_list = self.GetProjects(args)
382 pending = []
Joe Onorato2896a792008-11-17 16:56:36 -0500383 reviewers = []
384 cc = []
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700385 branch = None
386
387 if opt.branch:
388 branch = opt.branch
Joe Onorato2896a792008-11-17 16:56:36 -0500389
Doug Anderson37282b42011-03-04 11:54:18 -0800390 for project in project_list:
Daniel Sandlere9d6b612012-04-06 10:39:32 -0400391 if opt.current_branch:
392 cbr = project.CurrentBranch
393 avail = [project.GetUploadableBranch(cbr)] if cbr else None
394 else:
395 avail = project.GetUploadableBranches(branch)
Doug Anderson37282b42011-03-04 11:54:18 -0800396 if avail:
397 pending.append((project, avail))
398
399 if pending and (not opt.bypass_hooks):
400 hook = RepoHook('pre-upload', self.manifest.repo_hooks_project,
401 self.manifest.topdir, abort_if_user_denies=True)
402 pending_proj_names = [project.name for (project, avail) in pending]
403 try:
404 hook.Run(opt.allow_all_hooks, project_list=pending_proj_names)
Sarah Owensa5be53f2012-09-09 15:37:57 -0700405 except HookError as e:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700406 print("ERROR: %s" % str(e), file=sys.stderr)
Doug Anderson37282b42011-03-04 11:54:18 -0800407 return
408
Joe Onorato2896a792008-11-17 16:56:36 -0500409 if opt.reviewers:
410 reviewers = _SplitEmails(opt.reviewers)
411 if opt.cc:
412 cc = _SplitEmails(opt.cc)
David Pursehouse8f62fb72012-11-14 12:09:38 +0900413 people = (reviewers, cc)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700414
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700415 if not pending:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700416 print("no branches ready for upload", file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700417 elif len(pending) == 1 and len(pending[0][1]) == 1:
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700418 self._SingleBranch(opt, pending[0][1][0], people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700419 else:
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700420 self._MultipleBranches(opt, pending, people)