blob: 13b87784bae0327e25f3be5a1b0a76a117228034 [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.')
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800149
Doug Anderson37282b42011-03-04 11:54:18 -0800150 # Options relating to upload hook. Note that verify and no-verify are NOT
151 # opposites of each other, which is why they store to different locations.
152 # We are using them to match 'git commit' syntax.
153 #
154 # Combinations:
155 # - no-verify=False, verify=False (DEFAULT):
156 # If stdout is a tty, can prompt about running upload hooks if needed.
157 # If user denies running hooks, the upload is cancelled. If stdout is
158 # not a tty and we would need to prompt about upload hooks, upload is
159 # cancelled.
160 # - no-verify=False, verify=True:
161 # Always run upload hooks with no prompt.
162 # - no-verify=True, verify=False:
163 # Never run upload hooks, but upload anyway (AKA bypass hooks).
164 # - no-verify=True, verify=True:
165 # Invalid
166 p.add_option('--no-verify',
167 dest='bypass_hooks', action='store_true',
168 help='Do not run the upload hook.')
169 p.add_option('--verify',
170 dest='allow_all_hooks', action='store_true',
171 help='Run the upload hook without prompting.')
172
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700173 def _SingleBranch(self, opt, branch, people):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700174 project = branch.project
175 name = branch.name
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700176 remote = project.GetBranch(name).remote
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700177
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700178 key = 'review.%s.autoupload' % remote.review
179 answer = project.config.GetBoolean(key)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700180
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700181 if answer is False:
182 _die("upload blocked by %s = false" % key)
183
184 if answer is None:
Shawn O. Pearce66bdd462009-04-17 18:47:22 -0700185 date = branch.date
David Pursehouse8a68ff92012-09-24 12:15:13 +0900186 commit_list = branch.commits
Shawn O. Pearce66bdd462009-04-17 18:47:22 -0700187
Sarah Owenscecd1d82012-11-01 22:59:27 -0700188 print('Upload project %s/ to remote branch %s:' % (project.relpath, project.revisionExpr))
189 print(' branch %s (%2d commit%s, %s):' % (
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700190 name,
David Pursehouse8a68ff92012-09-24 12:15:13 +0900191 len(commit_list),
192 len(commit_list) != 1 and 's' or '',
Sarah Owenscecd1d82012-11-01 22:59:27 -0700193 date))
David Pursehouse8a68ff92012-09-24 12:15:13 +0900194 for commit in commit_list:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700195 print(' %s' % commit)
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700196
Mike Frysingere9311272011-08-11 15:46:43 -0400197 sys.stdout.write('to %s (y/N)? ' % remote.review)
David Pursehousefc241242012-11-14 09:19:39 +0900198 answer = sys.stdin.readline().strip().lower()
199 answer = answer in ('y', 'yes', '1', 'true', 't')
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700200
201 if answer:
Dan Morrill879a9a52010-05-04 16:56:07 -0700202 if len(branch.commits) > UNUSUAL_COMMIT_THRESHOLD:
203 answer = _ConfirmManyUploads()
204
205 if answer:
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700206 self._UploadAndReport(opt, [branch], people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700207 else:
208 _die("upload aborted by user")
209
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700210 def _MultipleBranches(self, opt, pending, people):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700211 projects = {}
212 branches = {}
213
214 script = []
215 script.append('# Uncomment the branches to upload:')
216 for project, avail in pending:
217 script.append('#')
218 script.append('# project %s/:' % project.relpath)
219
220 b = {}
221 for branch in avail:
222 name = branch.name
223 date = branch.date
David Pursehouse8a68ff92012-09-24 12:15:13 +0900224 commit_list = branch.commits
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700225
226 if b:
227 script.append('#')
Christer Fletcher6a1f7372011-04-28 14:13:14 +0200228 script.append('# branch %s (%2d commit%s, %s) to remote branch %s:' % (
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700229 name,
David Pursehouse8a68ff92012-09-24 12:15:13 +0900230 len(commit_list),
231 len(commit_list) != 1 and 's' or '',
Christer Fletcher6a1f7372011-04-28 14:13:14 +0200232 date,
233 project.revisionExpr))
David Pursehouse8a68ff92012-09-24 12:15:13 +0900234 for commit in commit_list:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700235 script.append('# %s' % commit)
236 b[name] = branch
237
238 projects[project.relpath] = project
239 branches[project.name] = b
240 script.append('')
241
chenguodong605a9a42011-08-22 18:42:47 +0800242 script = [ x.encode('utf-8')
243 if issubclass(type(x), unicode)
244 else x
245 for x in script ]
246
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700247 script = Editor.EditString("\n".join(script)).split("\n")
248
249 project_re = re.compile(r'^#?\s*project\s*([^\s]+)/:$')
250 branch_re = re.compile(r'^\s*branch\s*([^\s(]+)\s*\(.*')
251
252 project = None
253 todo = []
254
255 for line in script:
256 m = project_re.match(line)
257 if m:
258 name = m.group(1)
259 project = projects.get(name)
260 if not project:
261 _die('project %s not available for upload', name)
262 continue
263
264 m = branch_re.match(line)
265 if m:
266 name = m.group(1)
267 if not project:
268 _die('project for branch %s not in script', name)
269 branch = branches[project.name].get(name)
270 if not branch:
271 _die('branch %s not in %s', name, project.relpath)
272 todo.append(branch)
273 if not todo:
274 _die("nothing uncommented for upload")
Dan Morrill879a9a52010-05-04 16:56:07 -0700275
276 many_commits = False
277 for branch in todo:
278 if len(branch.commits) > UNUSUAL_COMMIT_THRESHOLD:
279 many_commits = True
280 break
281 if many_commits:
282 if not _ConfirmManyUploads(multiple_branches=True):
283 _die("upload aborted by user")
284
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700285 self._UploadAndReport(opt, todo, people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700286
Ben Komalo08a3f682010-07-15 16:03:02 -0700287 def _AppendAutoCcList(self, branch, people):
288 """
289 Appends the list of users in the CC list in the git project's config if a
290 non-empty reviewer list was found.
291 """
292
293 name = branch.name
294 project = branch.project
295 key = 'review.%s.autocopy' % project.GetBranch(name).remote.review
296 raw_list = project.config.GetString(key)
297 if not raw_list is None and len(people[0]) > 0:
298 people[1].extend([entry.strip() for entry in raw_list.split(',')])
299
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700300 def _FindGerritChange(self, branch):
301 last_pub = branch.project.WasPublished(branch.name)
302 if last_pub is None:
303 return ""
304
305 refs = branch.GetPublishedRefs()
306 try:
307 # refs/changes/XYZ/N --> XYZ
308 return refs.get(last_pub).split('/')[-2]
David Pursehouse1d947b32012-10-25 12:23:11 +0900309 except (AttributeError, IndexError):
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700310 return ""
311
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700312 def _UploadAndReport(self, opt, todo, original_people):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700313 have_errors = False
314 for branch in todo:
315 try:
Ben Komalo08a3f682010-07-15 16:03:02 -0700316 people = copy.deepcopy(original_people)
317 self._AppendAutoCcList(branch, people)
318
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500319 # Check if there are local changes that may have been forgotten
320 if branch.project.HasChanges():
David Pursehousec1b86a22012-11-14 11:36:51 +0900321 key = 'review.%s.autoupload' % branch.project.remote.review
322 answer = branch.project.config.GetBoolean(key)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500323
David Pursehousec1b86a22012-11-14 11:36:51 +0900324 # if they want to auto upload, let's not ask because it could be automated
325 if answer is None:
326 sys.stdout.write('Uncommitted changes in ' + branch.project.name + ' (did you forget to amend?). Continue uploading? (y/N) ')
327 a = sys.stdin.readline().strip().lower()
328 if a not in ('y', 'yes', 't', 'true', 'on'):
329 print("skipping upload", file=sys.stderr)
330 branch.uploaded = False
331 branch.error = 'User aborted'
332 continue
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500333
Anthony Russellod666e932012-06-01 00:48:22 -0400334 # Check if topic branches should be sent to the server during upload
335 if opt.auto_topic is not True:
David Pursehousec1b86a22012-11-14 11:36:51 +0900336 key = 'review.%s.uploadtopic' % branch.project.remote.review
337 opt.auto_topic = branch.project.config.GetBoolean(key)
Anthony Russellod666e932012-06-01 00:48:22 -0400338
Brian Harring435370c2012-07-28 15:37:04 -0700339 branch.UploadForReview(people, auto_topic=opt.auto_topic, draft=opt.draft)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700340 branch.uploaded = True
Sarah Owensa5be53f2012-09-09 15:37:57 -0700341 except UploadError as e:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700342 branch.error = e
343 branch.uploaded = False
344 have_errors = True
345
Sarah Owenscecd1d82012-11-01 22:59:27 -0700346 print(file=sys.stderr)
347 print('----------------------------------------------------------------------', file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700348
349 if have_errors:
350 for branch in todo:
351 if not branch.uploaded:
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -0700352 if len(str(branch.error)) <= 30:
353 fmt = ' (%s)'
354 else:
355 fmt = '\n (%s)'
Sarah Owenscecd1d82012-11-01 22:59:27 -0700356 print(('[FAILED] %-15s %-15s' + fmt) % (
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700357 branch.project.relpath + '/', \
358 branch.name, \
Sarah Owenscecd1d82012-11-01 22:59:27 -0700359 str(branch.error)),
360 file=sys.stderr)
361 print()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700362
363 for branch in todo:
David Pursehousec1b86a22012-11-14 11:36:51 +0900364 if branch.uploaded:
365 print('[OK ] %-15s %s' % (
366 branch.project.relpath + '/',
367 branch.name),
368 file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700369
370 if have_errors:
371 sys.exit(1)
372
373 def Execute(self, opt, args):
374 project_list = self.GetProjects(args)
375 pending = []
Joe Onorato2896a792008-11-17 16:56:36 -0500376 reviewers = []
377 cc = []
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700378 branch = None
379
380 if opt.branch:
381 branch = opt.branch
Joe Onorato2896a792008-11-17 16:56:36 -0500382
Doug Anderson37282b42011-03-04 11:54:18 -0800383 for project in project_list:
Daniel Sandlere9d6b612012-04-06 10:39:32 -0400384 if opt.current_branch:
385 cbr = project.CurrentBranch
386 avail = [project.GetUploadableBranch(cbr)] if cbr else None
387 else:
388 avail = project.GetUploadableBranches(branch)
Doug Anderson37282b42011-03-04 11:54:18 -0800389 if avail:
390 pending.append((project, avail))
391
392 if pending and (not opt.bypass_hooks):
393 hook = RepoHook('pre-upload', self.manifest.repo_hooks_project,
394 self.manifest.topdir, abort_if_user_denies=True)
395 pending_proj_names = [project.name for (project, avail) in pending]
396 try:
397 hook.Run(opt.allow_all_hooks, project_list=pending_proj_names)
Sarah Owensa5be53f2012-09-09 15:37:57 -0700398 except HookError as e:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700399 print("ERROR: %s" % str(e), file=sys.stderr)
Doug Anderson37282b42011-03-04 11:54:18 -0800400 return
401
Joe Onorato2896a792008-11-17 16:56:36 -0500402 if opt.reviewers:
403 reviewers = _SplitEmails(opt.reviewers)
404 if opt.cc:
405 cc = _SplitEmails(opt.cc)
David Pursehouse8f62fb72012-11-14 12:09:38 +0900406 people = (reviewers, cc)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700407
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700408 if not pending:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700409 print("no branches ready for upload", file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700410 elif len(pending) == 1 and len(pending[0][1]) == 1:
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700411 self._SingleBranch(opt, pending[0][1][0], people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700412 else:
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700413 self._MultipleBranches(opt, pending, people)