blob: 48ee685cee127dacc00ba79bd27505bdb527cedb [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
Dan Morrillf0a9a1a2010-05-05 08:18:35 -070026UNUSUAL_COMMIT_THRESHOLD = 5
Dan Morrill879a9a52010-05-04 16:56:07 -070027
28def _ConfirmManyUploads(multiple_branches=False):
29 if multiple_branches:
David Pursehouse2f9e7e42013-03-05 17:26:46 +090030 print('ATTENTION: One or more branches has an unusually high number '
Sarah Owenscecd1d82012-11-01 22:59:27 -070031 'of commits.')
Dan Morrill879a9a52010-05-04 16:56:07 -070032 else:
Sarah Owenscecd1d82012-11-01 22:59:27 -070033 print('ATTENTION: You are uploading an unusually high number of commits.')
David Pursehouse2f9e7e42013-03-05 17:26:46 +090034 print('YOU PROBABLY DO NOT MEAN TO DO THIS. (Did you rebase across '
Sarah Owenscecd1d82012-11-01 22:59:27 -070035 'branches?)')
Dan Morrill879a9a52010-05-04 16:56:07 -070036 answer = raw_input("If you are sure you intend to do this, type 'yes': ").strip()
37 return answer == "yes"
38
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070039def _die(fmt, *args):
40 msg = fmt % args
Sarah Owenscecd1d82012-11-01 22:59:27 -070041 print('error: %s' % msg, file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070042 sys.exit(1)
43
Joe Onorato2896a792008-11-17 16:56:36 -050044def _SplitEmails(values):
45 result = []
David Pursehouse8a68ff92012-09-24 12:15:13 +090046 for value in values:
47 result.extend([s.strip() for s in value.split(',')])
Joe Onorato2896a792008-11-17 16:56:36 -050048 return result
49
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070050class Upload(InteractiveCommand):
51 common = True
52 helpSummary = "Upload changes for code review"
David Pursehouse8f62fb72012-11-14 12:09:38 +090053 helpUsage = """
Ficus Kirkpatricka0de6e82010-10-22 13:06:47 -070054%prog [--re --cc] [<project>]...
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070055"""
56 helpDescription = """
Shawn O. Pearce337fb9c2009-04-18 10:59:33 -070057The '%prog' command is used to send changes to the Gerrit Code
58Review system. It searches for topic branches in local projects
59that have not yet been published for review. If multiple topic
60branches are found, '%prog' opens an editor to allow the user to
61select which branches to upload.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070062
Shawn O. Pearce337fb9c2009-04-18 10:59:33 -070063'%prog' searches for uploadable changes in all projects listed at
64the command line. Projects can be specified either by name, or by
65a relative or absolute path to the project's local directory. If no
66projects are specified, '%prog' will search for uploadable changes
67in all projects listed in the manifest.
Joe Onorato2896a792008-11-17 16:56:36 -050068
69If the --reviewers or --cc options are passed, those emails are
70added to the respective list of users, and emails are sent to any
Shawn O. Pearce337fb9c2009-04-18 10:59:33 -070071new users. Users passed as --reviewers must already be registered
Joe Onorato2896a792008-11-17 16:56:36 -050072with the code review system, or the upload will fail.
Shawn O. Pearcea6df7d22008-12-12 08:04:07 -080073
Shawn O. Pearcea608fb02009-04-17 12:11:24 -070074Configuration
75-------------
76
77review.URL.autoupload:
78
Mike Frysingere9311272011-08-11 15:46:43 -040079To disable the "Upload ... (y/N)?" prompt, you can set a per-project
Shawn O. Pearcea608fb02009-04-17 12:11:24 -070080or global Git configuration option. If review.URL.autoupload is set
81to "true" then repo will assume you always answer "y" at the prompt,
82and will not prompt you further. If it is set to "false" then repo
83will assume you always answer "n", and will abort.
84
Ben Komalo08a3f682010-07-15 16:03:02 -070085review.URL.autocopy:
86
87To automatically copy a user or mailing list to all uploaded reviews,
88you can set a per-project or global Git option to do so. Specifically,
89review.URL.autocopy can be set to a comma separated list of reviewers
90who you always want copied on all uploads with a non-empty --re
91argument.
92
Shawn O. Pearce3575b8f2010-07-15 17:00:14 -070093review.URL.username:
94
95Override the username used to connect to Gerrit Code Review.
96By default the local part of the email address is used.
97
Shawn O. Pearcea608fb02009-04-17 12:11:24 -070098The URL must match the review URL listed in the manifest XML file,
99or in the .git/config within the project. For example:
100
101 [remote "origin"]
102 url = git://git.example.com/project.git
103 review = http://review.example.com/
104
105 [review "http://review.example.com/"]
106 autoupload = true
Ben Komalo08a3f682010-07-15 16:03:02 -0700107 autocopy = johndoe@company.com,my-team-alias@company.com
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700108
Anthony Russellod666e932012-06-01 00:48:22 -0400109review.URL.uploadtopic:
110
111To add a topic branch whenever uploading a commit, you can set a
112per-project or global Git option to do so. If review.URL.uploadtopic
113is set to "true" then repo will assume you always want the equivalent
114of the -t option to the repo command. If unset or set to "false" then
115repo will make use of only the command line option.
116
Shawn O. Pearce337fb9c2009-04-18 10:59:33 -0700117References
118----------
119
120Gerrit Code Review: http://code.google.com/p/gerrit/
121
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700122"""
123
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800124 def _Options(self, p):
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700125 p.add_option('-t',
126 dest='auto_topic', action='store_true',
127 help='Send local branch name to Gerrit Code Review')
Joe Onorato2896a792008-11-17 16:56:36 -0500128 p.add_option('--re', '--reviewers',
129 type='string', action='append', dest='reviewers',
130 help='Request reviews from these people.')
131 p.add_option('--cc',
132 type='string', action='append', dest='cc',
133 help='Also send email to these email addresses.')
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700134 p.add_option('--br',
135 type='string', action='store', dest='branch',
136 help='Branch to upload.')
Daniel Sandlere9d6b612012-04-06 10:39:32 -0400137 p.add_option('--cbr', '--current-branch',
138 dest='current_branch', action='store_true',
139 help='Upload current git branch.')
Brian Harring435370c2012-07-28 15:37:04 -0700140 p.add_option('-d', '--draft',
141 action='store_true', dest='draft', default=False,
142 help='If specified, upload as a draft.')
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800143
Doug Anderson37282b42011-03-04 11:54:18 -0800144 # Options relating to upload hook. Note that verify and no-verify are NOT
145 # opposites of each other, which is why they store to different locations.
146 # We are using them to match 'git commit' syntax.
147 #
148 # Combinations:
149 # - no-verify=False, verify=False (DEFAULT):
150 # If stdout is a tty, can prompt about running upload hooks if needed.
151 # If user denies running hooks, the upload is cancelled. If stdout is
152 # not a tty and we would need to prompt about upload hooks, upload is
153 # cancelled.
154 # - no-verify=False, verify=True:
155 # Always run upload hooks with no prompt.
156 # - no-verify=True, verify=False:
157 # Never run upload hooks, but upload anyway (AKA bypass hooks).
158 # - no-verify=True, verify=True:
159 # Invalid
160 p.add_option('--no-verify',
161 dest='bypass_hooks', action='store_true',
162 help='Do not run the upload hook.')
163 p.add_option('--verify',
164 dest='allow_all_hooks', action='store_true',
165 help='Run the upload hook without prompting.')
166
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700167 def _SingleBranch(self, opt, branch, people):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700168 project = branch.project
169 name = branch.name
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700170 remote = project.GetBranch(name).remote
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700171
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700172 key = 'review.%s.autoupload' % remote.review
173 answer = project.config.GetBoolean(key)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700174
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700175 if answer is False:
176 _die("upload blocked by %s = false" % key)
177
178 if answer is None:
Shawn O. Pearce66bdd462009-04-17 18:47:22 -0700179 date = branch.date
David Pursehouse8a68ff92012-09-24 12:15:13 +0900180 commit_list = branch.commits
Shawn O. Pearce66bdd462009-04-17 18:47:22 -0700181
Sarah Owenscecd1d82012-11-01 22:59:27 -0700182 print('Upload project %s/ to remote branch %s:' % (project.relpath, project.revisionExpr))
183 print(' branch %s (%2d commit%s, %s):' % (
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700184 name,
David Pursehouse8a68ff92012-09-24 12:15:13 +0900185 len(commit_list),
186 len(commit_list) != 1 and 's' or '',
Sarah Owenscecd1d82012-11-01 22:59:27 -0700187 date))
David Pursehouse8a68ff92012-09-24 12:15:13 +0900188 for commit in commit_list:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700189 print(' %s' % commit)
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700190
Mike Frysingere9311272011-08-11 15:46:43 -0400191 sys.stdout.write('to %s (y/N)? ' % remote.review)
David Pursehousefc241242012-11-14 09:19:39 +0900192 answer = sys.stdin.readline().strip().lower()
193 answer = answer in ('y', 'yes', '1', 'true', 't')
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700194
195 if answer:
Dan Morrill879a9a52010-05-04 16:56:07 -0700196 if len(branch.commits) > UNUSUAL_COMMIT_THRESHOLD:
197 answer = _ConfirmManyUploads()
198
199 if answer:
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700200 self._UploadAndReport(opt, [branch], people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700201 else:
202 _die("upload aborted by user")
203
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700204 def _MultipleBranches(self, opt, pending, people):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700205 projects = {}
206 branches = {}
207
208 script = []
209 script.append('# Uncomment the branches to upload:')
210 for project, avail in pending:
211 script.append('#')
212 script.append('# project %s/:' % project.relpath)
213
214 b = {}
215 for branch in avail:
216 name = branch.name
217 date = branch.date
David Pursehouse8a68ff92012-09-24 12:15:13 +0900218 commit_list = branch.commits
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700219
220 if b:
221 script.append('#')
Christer Fletcher6a1f7372011-04-28 14:13:14 +0200222 script.append('# branch %s (%2d commit%s, %s) to remote branch %s:' % (
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700223 name,
David Pursehouse8a68ff92012-09-24 12:15:13 +0900224 len(commit_list),
225 len(commit_list) != 1 and 's' or '',
Christer Fletcher6a1f7372011-04-28 14:13:14 +0200226 date,
227 project.revisionExpr))
David Pursehouse8a68ff92012-09-24 12:15:13 +0900228 for commit in commit_list:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700229 script.append('# %s' % commit)
230 b[name] = branch
231
232 projects[project.relpath] = project
233 branches[project.name] = b
234 script.append('')
235
chenguodong605a9a42011-08-22 18:42:47 +0800236 script = [ x.encode('utf-8')
237 if issubclass(type(x), unicode)
238 else x
239 for x in script ]
240
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700241 script = Editor.EditString("\n".join(script)).split("\n")
242
243 project_re = re.compile(r'^#?\s*project\s*([^\s]+)/:$')
244 branch_re = re.compile(r'^\s*branch\s*([^\s(]+)\s*\(.*')
245
246 project = None
247 todo = []
248
249 for line in script:
250 m = project_re.match(line)
251 if m:
252 name = m.group(1)
253 project = projects.get(name)
254 if not project:
255 _die('project %s not available for upload', name)
256 continue
257
258 m = branch_re.match(line)
259 if m:
260 name = m.group(1)
261 if not project:
262 _die('project for branch %s not in script', name)
263 branch = branches[project.name].get(name)
264 if not branch:
265 _die('branch %s not in %s', name, project.relpath)
266 todo.append(branch)
267 if not todo:
268 _die("nothing uncommented for upload")
Dan Morrill879a9a52010-05-04 16:56:07 -0700269
270 many_commits = False
271 for branch in todo:
272 if len(branch.commits) > UNUSUAL_COMMIT_THRESHOLD:
273 many_commits = True
274 break
275 if many_commits:
276 if not _ConfirmManyUploads(multiple_branches=True):
277 _die("upload aborted by user")
278
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700279 self._UploadAndReport(opt, todo, people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700280
Ben Komalo08a3f682010-07-15 16:03:02 -0700281 def _AppendAutoCcList(self, branch, people):
282 """
283 Appends the list of users in the CC list in the git project's config if a
284 non-empty reviewer list was found.
285 """
286
287 name = branch.name
288 project = branch.project
289 key = 'review.%s.autocopy' % project.GetBranch(name).remote.review
290 raw_list = project.config.GetString(key)
291 if not raw_list is None and len(people[0]) > 0:
292 people[1].extend([entry.strip() for entry in raw_list.split(',')])
293
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700294 def _FindGerritChange(self, branch):
295 last_pub = branch.project.WasPublished(branch.name)
296 if last_pub is None:
297 return ""
298
299 refs = branch.GetPublishedRefs()
300 try:
301 # refs/changes/XYZ/N --> XYZ
302 return refs.get(last_pub).split('/')[-2]
David Pursehouse1d947b32012-10-25 12:23:11 +0900303 except (AttributeError, IndexError):
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700304 return ""
305
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700306 def _UploadAndReport(self, opt, todo, original_people):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700307 have_errors = False
308 for branch in todo:
309 try:
Ben Komalo08a3f682010-07-15 16:03:02 -0700310 people = copy.deepcopy(original_people)
311 self._AppendAutoCcList(branch, people)
312
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500313 # Check if there are local changes that may have been forgotten
314 if branch.project.HasChanges():
David Pursehousec1b86a22012-11-14 11:36:51 +0900315 key = 'review.%s.autoupload' % branch.project.remote.review
316 answer = branch.project.config.GetBoolean(key)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500317
David Pursehousec1b86a22012-11-14 11:36:51 +0900318 # if they want to auto upload, let's not ask because it could be automated
319 if answer is None:
320 sys.stdout.write('Uncommitted changes in ' + branch.project.name + ' (did you forget to amend?). Continue uploading? (y/N) ')
321 a = sys.stdin.readline().strip().lower()
322 if a not in ('y', 'yes', 't', 'true', 'on'):
323 print("skipping upload", file=sys.stderr)
324 branch.uploaded = False
325 branch.error = 'User aborted'
326 continue
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500327
Anthony Russellod666e932012-06-01 00:48:22 -0400328 # Check if topic branches should be sent to the server during upload
329 if opt.auto_topic is not True:
David Pursehousec1b86a22012-11-14 11:36:51 +0900330 key = 'review.%s.uploadtopic' % branch.project.remote.review
331 opt.auto_topic = branch.project.config.GetBoolean(key)
Anthony Russellod666e932012-06-01 00:48:22 -0400332
Brian Harring435370c2012-07-28 15:37:04 -0700333 branch.UploadForReview(people, auto_topic=opt.auto_topic, draft=opt.draft)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700334 branch.uploaded = True
Sarah Owensa5be53f2012-09-09 15:37:57 -0700335 except UploadError as e:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700336 branch.error = e
337 branch.uploaded = False
338 have_errors = True
339
Sarah Owenscecd1d82012-11-01 22:59:27 -0700340 print(file=sys.stderr)
341 print('----------------------------------------------------------------------', file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700342
343 if have_errors:
344 for branch in todo:
345 if not branch.uploaded:
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -0700346 if len(str(branch.error)) <= 30:
347 fmt = ' (%s)'
348 else:
349 fmt = '\n (%s)'
Sarah Owenscecd1d82012-11-01 22:59:27 -0700350 print(('[FAILED] %-15s %-15s' + fmt) % (
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700351 branch.project.relpath + '/', \
352 branch.name, \
Sarah Owenscecd1d82012-11-01 22:59:27 -0700353 str(branch.error)),
354 file=sys.stderr)
355 print()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700356
357 for branch in todo:
David Pursehousec1b86a22012-11-14 11:36:51 +0900358 if branch.uploaded:
359 print('[OK ] %-15s %s' % (
360 branch.project.relpath + '/',
361 branch.name),
362 file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700363
364 if have_errors:
365 sys.exit(1)
366
367 def Execute(self, opt, args):
368 project_list = self.GetProjects(args)
369 pending = []
Joe Onorato2896a792008-11-17 16:56:36 -0500370 reviewers = []
371 cc = []
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700372 branch = None
373
374 if opt.branch:
375 branch = opt.branch
Joe Onorato2896a792008-11-17 16:56:36 -0500376
Doug Anderson37282b42011-03-04 11:54:18 -0800377 for project in project_list:
Daniel Sandlere9d6b612012-04-06 10:39:32 -0400378 if opt.current_branch:
379 cbr = project.CurrentBranch
380 avail = [project.GetUploadableBranch(cbr)] if cbr else None
381 else:
382 avail = project.GetUploadableBranches(branch)
Doug Anderson37282b42011-03-04 11:54:18 -0800383 if avail:
384 pending.append((project, avail))
385
386 if pending and (not opt.bypass_hooks):
387 hook = RepoHook('pre-upload', self.manifest.repo_hooks_project,
388 self.manifest.topdir, abort_if_user_denies=True)
389 pending_proj_names = [project.name for (project, avail) in pending]
390 try:
391 hook.Run(opt.allow_all_hooks, project_list=pending_proj_names)
Sarah Owensa5be53f2012-09-09 15:37:57 -0700392 except HookError as e:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700393 print("ERROR: %s" % str(e), file=sys.stderr)
Doug Anderson37282b42011-03-04 11:54:18 -0800394 return
395
Joe Onorato2896a792008-11-17 16:56:36 -0500396 if opt.reviewers:
397 reviewers = _SplitEmails(opt.reviewers)
398 if opt.cc:
399 cc = _SplitEmails(opt.cc)
David Pursehouse8f62fb72012-11-14 12:09:38 +0900400 people = (reviewers, cc)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700402 if not pending:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700403 print("no branches ready for upload", file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700404 elif len(pending) == 1 and len(pending[0][1]) == 1:
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700405 self._SingleBranch(opt, pending[0][1][0], people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700406 else:
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700407 self._MultipleBranches(opt, pending, people)