blob: 0c74738ca8576544cf90958755d6a338887a588e [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
181
182 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')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700205
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700206 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('#')
Bryan Jacobs691a7592013-05-31 15:45:28 -0400233 destination = opt.dest_branch or project.dest_branch or project.revisionExpr
Christer Fletcher6a1f7372011-04-28 14:13:14 +0200234 script.append('# branch %s (%2d commit%s, %s) to remote branch %s:' % (
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700235 name,
David Pursehouse8a68ff92012-09-24 12:15:13 +0900236 len(commit_list),
237 len(commit_list) != 1 and 's' or '',
Christer Fletcher6a1f7372011-04-28 14:13:14 +0200238 date,
Bryan Jacobs691a7592013-05-31 15:45:28 -0400239 destination))
David Pursehouse8a68ff92012-09-24 12:15:13 +0900240 for commit in commit_list:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700241 script.append('# %s' % commit)
242 b[name] = branch
243
244 projects[project.relpath] = project
245 branches[project.name] = b
246 script.append('')
247
chenguodong605a9a42011-08-22 18:42:47 +0800248 script = [ x.encode('utf-8')
249 if issubclass(type(x), unicode)
250 else x
251 for x in script ]
252
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700253 script = Editor.EditString("\n".join(script)).split("\n")
254
255 project_re = re.compile(r'^#?\s*project\s*([^\s]+)/:$')
256 branch_re = re.compile(r'^\s*branch\s*([^\s(]+)\s*\(.*')
257
258 project = None
259 todo = []
260
261 for line in script:
262 m = project_re.match(line)
263 if m:
264 name = m.group(1)
265 project = projects.get(name)
266 if not project:
267 _die('project %s not available for upload', name)
268 continue
269
270 m = branch_re.match(line)
271 if m:
272 name = m.group(1)
273 if not project:
274 _die('project for branch %s not in script', name)
275 branch = branches[project.name].get(name)
276 if not branch:
277 _die('branch %s not in %s', name, project.relpath)
278 todo.append(branch)
279 if not todo:
280 _die("nothing uncommented for upload")
Dan Morrill879a9a52010-05-04 16:56:07 -0700281
282 many_commits = False
283 for branch in todo:
284 if len(branch.commits) > UNUSUAL_COMMIT_THRESHOLD:
285 many_commits = True
286 break
287 if many_commits:
288 if not _ConfirmManyUploads(multiple_branches=True):
289 _die("upload aborted by user")
290
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700291 self._UploadAndReport(opt, todo, people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700292
Ben Komalo08a3f682010-07-15 16:03:02 -0700293 def _AppendAutoCcList(self, branch, people):
294 """
295 Appends the list of users in the CC list in the git project's config if a
296 non-empty reviewer list was found.
297 """
298
299 name = branch.name
300 project = branch.project
301 key = 'review.%s.autocopy' % project.GetBranch(name).remote.review
302 raw_list = project.config.GetString(key)
303 if not raw_list is None and len(people[0]) > 0:
304 people[1].extend([entry.strip() for entry in raw_list.split(',')])
305
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700306 def _FindGerritChange(self, branch):
307 last_pub = branch.project.WasPublished(branch.name)
308 if last_pub is None:
309 return ""
310
311 refs = branch.GetPublishedRefs()
312 try:
313 # refs/changes/XYZ/N --> XYZ
314 return refs.get(last_pub).split('/')[-2]
David Pursehouse1d947b32012-10-25 12:23:11 +0900315 except (AttributeError, IndexError):
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700316 return ""
317
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700318 def _UploadAndReport(self, opt, todo, original_people):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700319 have_errors = False
320 for branch in todo:
321 try:
Ben Komalo08a3f682010-07-15 16:03:02 -0700322 people = copy.deepcopy(original_people)
323 self._AppendAutoCcList(branch, people)
324
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500325 # Check if there are local changes that may have been forgotten
326 if branch.project.HasChanges():
David Pursehousec1b86a22012-11-14 11:36:51 +0900327 key = 'review.%s.autoupload' % branch.project.remote.review
328 answer = branch.project.config.GetBoolean(key)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500329
David Pursehousec1b86a22012-11-14 11:36:51 +0900330 # if they want to auto upload, let's not ask because it could be automated
331 if answer is None:
332 sys.stdout.write('Uncommitted changes in ' + branch.project.name + ' (did you forget to amend?). Continue uploading? (y/N) ')
333 a = sys.stdin.readline().strip().lower()
334 if a not in ('y', 'yes', 't', 'true', 'on'):
335 print("skipping upload", file=sys.stderr)
336 branch.uploaded = False
337 branch.error = 'User aborted'
338 continue
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500339
Anthony Russellod666e932012-06-01 00:48:22 -0400340 # Check if topic branches should be sent to the server during upload
341 if opt.auto_topic is not True:
David Pursehousec1b86a22012-11-14 11:36:51 +0900342 key = 'review.%s.uploadtopic' % branch.project.remote.review
343 opt.auto_topic = branch.project.config.GetBoolean(key)
Anthony Russellod666e932012-06-01 00:48:22 -0400344
Bryan Jacobs691a7592013-05-31 15:45:28 -0400345 destination = opt.dest_branch or branch.project.dest_branch or branch.project.revisionExpr
346 branch.UploadForReview(people, auto_topic=opt.auto_topic, draft=opt.draft, dest_branch=destination)
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)