blob: 84a5e440359c3bbee8bf34cb5a25f85ae58bce44 [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
Ben Komalo08a3f682010-07-15 16:03:02 -070016import copy
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070017import re
18import sys
19
20from command import InteractiveCommand
21from editor import Editor
Doug Anderson37282b42011-03-04 11:54:18 -080022from error import HookError, UploadError
23from project import RepoHook
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070024
Dan Morrillf0a9a1a2010-05-05 08:18:35 -070025UNUSUAL_COMMIT_THRESHOLD = 5
Dan Morrill879a9a52010-05-04 16:56:07 -070026
27def _ConfirmManyUploads(multiple_branches=False):
28 if multiple_branches:
29 print "ATTENTION: One or more branches has an unusually high number of commits."
30 else:
31 print "ATTENTION: You are uploading an unusually high number of commits."
32 print "YOU PROBABLY DO NOT MEAN TO DO THIS. (Did you rebase across branches?)"
33 answer = raw_input("If you are sure you intend to do this, type 'yes': ").strip()
34 return answer == "yes"
35
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070036def _die(fmt, *args):
37 msg = fmt % args
38 print >>sys.stderr, 'error: %s' % msg
39 sys.exit(1)
40
Joe Onorato2896a792008-11-17 16:56:36 -050041def _SplitEmails(values):
42 result = []
David Pursehouse8a68ff92012-09-24 12:15:13 +090043 for value in values:
44 result.extend([s.strip() for s in value.split(',')])
Joe Onorato2896a792008-11-17 16:56:36 -050045 return result
46
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070047class Upload(InteractiveCommand):
48 common = True
49 helpSummary = "Upload changes for code review"
50 helpUsage="""
Ficus Kirkpatricka0de6e82010-10-22 13:06:47 -070051%prog [--re --cc] [<project>]...
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070052"""
53 helpDescription = """
Shawn O. Pearce337fb9c2009-04-18 10:59:33 -070054The '%prog' command is used to send changes to the Gerrit Code
55Review system. It searches for topic branches in local projects
56that have not yet been published for review. If multiple topic
57branches are found, '%prog' opens an editor to allow the user to
58select which branches to upload.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070059
Shawn O. Pearce337fb9c2009-04-18 10:59:33 -070060'%prog' searches for uploadable changes in all projects listed at
61the command line. Projects can be specified either by name, or by
62a relative or absolute path to the project's local directory. If no
63projects are specified, '%prog' will search for uploadable changes
64in all projects listed in the manifest.
Joe Onorato2896a792008-11-17 16:56:36 -050065
66If the --reviewers or --cc options are passed, those emails are
67added to the respective list of users, and emails are sent to any
Shawn O. Pearce337fb9c2009-04-18 10:59:33 -070068new users. Users passed as --reviewers must already be registered
Joe Onorato2896a792008-11-17 16:56:36 -050069with the code review system, or the upload will fail.
Shawn O. Pearcea6df7d22008-12-12 08:04:07 -080070
Shawn O. Pearcea608fb02009-04-17 12:11:24 -070071Configuration
72-------------
73
74review.URL.autoupload:
75
Mike Frysingere9311272011-08-11 15:46:43 -040076To disable the "Upload ... (y/N)?" prompt, you can set a per-project
Shawn O. Pearcea608fb02009-04-17 12:11:24 -070077or global Git configuration option. If review.URL.autoupload is set
78to "true" then repo will assume you always answer "y" at the prompt,
79and will not prompt you further. If it is set to "false" then repo
80will assume you always answer "n", and will abort.
81
Ben Komalo08a3f682010-07-15 16:03:02 -070082review.URL.autocopy:
83
84To automatically copy a user or mailing list to all uploaded reviews,
85you can set a per-project or global Git option to do so. Specifically,
86review.URL.autocopy can be set to a comma separated list of reviewers
87who you always want copied on all uploads with a non-empty --re
88argument.
89
Shawn O. Pearce3575b8f2010-07-15 17:00:14 -070090review.URL.username:
91
92Override the username used to connect to Gerrit Code Review.
93By default the local part of the email address is used.
94
Shawn O. Pearcea608fb02009-04-17 12:11:24 -070095The URL must match the review URL listed in the manifest XML file,
96or in the .git/config within the project. For example:
97
98 [remote "origin"]
99 url = git://git.example.com/project.git
100 review = http://review.example.com/
101
102 [review "http://review.example.com/"]
103 autoupload = true
Ben Komalo08a3f682010-07-15 16:03:02 -0700104 autocopy = johndoe@company.com,my-team-alias@company.com
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700105
Anthony Russellod666e932012-06-01 00:48:22 -0400106review.URL.uploadtopic:
107
108To add a topic branch whenever uploading a commit, you can set a
109per-project or global Git option to do so. If review.URL.uploadtopic
110is set to "true" then repo will assume you always want the equivalent
111of the -t option to the repo command. If unset or set to "false" then
112repo will make use of only the command line option.
113
Shawn O. Pearce337fb9c2009-04-18 10:59:33 -0700114References
115----------
116
117Gerrit Code Review: http://code.google.com/p/gerrit/
118
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700119"""
120
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800121 def _Options(self, p):
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700122 p.add_option('-t',
123 dest='auto_topic', action='store_true',
124 help='Send local branch name to Gerrit Code Review')
Joe Onorato2896a792008-11-17 16:56:36 -0500125 p.add_option('--re', '--reviewers',
126 type='string', action='append', dest='reviewers',
127 help='Request reviews from these people.')
128 p.add_option('--cc',
129 type='string', action='append', dest='cc',
130 help='Also send email to these email addresses.')
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700131 p.add_option('--br',
132 type='string', action='store', dest='branch',
133 help='Branch to upload.')
Daniel Sandlere9d6b612012-04-06 10:39:32 -0400134 p.add_option('--cbr', '--current-branch',
135 dest='current_branch', action='store_true',
136 help='Upload current git branch.')
Brian Harring435370c2012-07-28 15:37:04 -0700137 p.add_option('-d', '--draft',
138 action='store_true', dest='draft', default=False,
139 help='If specified, upload as a draft.')
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800140
Doug Anderson37282b42011-03-04 11:54:18 -0800141 # Options relating to upload hook. Note that verify and no-verify are NOT
142 # opposites of each other, which is why they store to different locations.
143 # We are using them to match 'git commit' syntax.
144 #
145 # Combinations:
146 # - no-verify=False, verify=False (DEFAULT):
147 # If stdout is a tty, can prompt about running upload hooks if needed.
148 # If user denies running hooks, the upload is cancelled. If stdout is
149 # not a tty and we would need to prompt about upload hooks, upload is
150 # cancelled.
151 # - no-verify=False, verify=True:
152 # Always run upload hooks with no prompt.
153 # - no-verify=True, verify=False:
154 # Never run upload hooks, but upload anyway (AKA bypass hooks).
155 # - no-verify=True, verify=True:
156 # Invalid
157 p.add_option('--no-verify',
158 dest='bypass_hooks', action='store_true',
159 help='Do not run the upload hook.')
160 p.add_option('--verify',
161 dest='allow_all_hooks', action='store_true',
162 help='Run the upload hook without prompting.')
163
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700164 def _SingleBranch(self, opt, branch, people):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700165 project = branch.project
166 name = branch.name
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700167 remote = project.GetBranch(name).remote
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700168
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700169 key = 'review.%s.autoupload' % remote.review
170 answer = project.config.GetBoolean(key)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700171
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700172 if answer is False:
173 _die("upload blocked by %s = false" % key)
174
175 if answer is None:
Shawn O. Pearce66bdd462009-04-17 18:47:22 -0700176 date = branch.date
David Pursehouse8a68ff92012-09-24 12:15:13 +0900177 commit_list = branch.commits
Shawn O. Pearce66bdd462009-04-17 18:47:22 -0700178
Christer Fletcher6a1f7372011-04-28 14:13:14 +0200179 print 'Upload project %s/ to remote branch %s:' % (project.relpath, project.revisionExpr)
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700180 print ' branch %s (%2d commit%s, %s):' % (
181 name,
David Pursehouse8a68ff92012-09-24 12:15:13 +0900182 len(commit_list),
183 len(commit_list) != 1 and 's' or '',
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700184 date)
David Pursehouse8a68ff92012-09-24 12:15:13 +0900185 for commit in commit_list:
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700186 print ' %s' % commit
187
Mike Frysingere9311272011-08-11 15:46:43 -0400188 sys.stdout.write('to %s (y/N)? ' % remote.review)
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700189 answer = sys.stdin.readline().strip()
190 answer = answer in ('y', 'Y', 'yes', '1', 'true', 't')
191
192 if answer:
Dan Morrill879a9a52010-05-04 16:56:07 -0700193 if len(branch.commits) > UNUSUAL_COMMIT_THRESHOLD:
194 answer = _ConfirmManyUploads()
195
196 if answer:
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700197 self._UploadAndReport(opt, [branch], people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700198 else:
199 _die("upload aborted by user")
200
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700201 def _MultipleBranches(self, opt, pending, people):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700202 projects = {}
203 branches = {}
204
205 script = []
206 script.append('# Uncomment the branches to upload:')
207 for project, avail in pending:
208 script.append('#')
209 script.append('# project %s/:' % project.relpath)
210
211 b = {}
212 for branch in avail:
213 name = branch.name
214 date = branch.date
David Pursehouse8a68ff92012-09-24 12:15:13 +0900215 commit_list = branch.commits
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700216
217 if b:
218 script.append('#')
Christer Fletcher6a1f7372011-04-28 14:13:14 +0200219 script.append('# branch %s (%2d commit%s, %s) to remote branch %s:' % (
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700220 name,
David Pursehouse8a68ff92012-09-24 12:15:13 +0900221 len(commit_list),
222 len(commit_list) != 1 and 's' or '',
Christer Fletcher6a1f7372011-04-28 14:13:14 +0200223 date,
224 project.revisionExpr))
David Pursehouse8a68ff92012-09-24 12:15:13 +0900225 for commit in commit_list:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700226 script.append('# %s' % commit)
227 b[name] = branch
228
229 projects[project.relpath] = project
230 branches[project.name] = b
231 script.append('')
232
chenguodong605a9a42011-08-22 18:42:47 +0800233 script = [ x.encode('utf-8')
234 if issubclass(type(x), unicode)
235 else x
236 for x in script ]
237
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700238 script = Editor.EditString("\n".join(script)).split("\n")
239
240 project_re = re.compile(r'^#?\s*project\s*([^\s]+)/:$')
241 branch_re = re.compile(r'^\s*branch\s*([^\s(]+)\s*\(.*')
242
243 project = None
244 todo = []
245
246 for line in script:
247 m = project_re.match(line)
248 if m:
249 name = m.group(1)
250 project = projects.get(name)
251 if not project:
252 _die('project %s not available for upload', name)
253 continue
254
255 m = branch_re.match(line)
256 if m:
257 name = m.group(1)
258 if not project:
259 _die('project for branch %s not in script', name)
260 branch = branches[project.name].get(name)
261 if not branch:
262 _die('branch %s not in %s', name, project.relpath)
263 todo.append(branch)
264 if not todo:
265 _die("nothing uncommented for upload")
Dan Morrill879a9a52010-05-04 16:56:07 -0700266
267 many_commits = False
268 for branch in todo:
269 if len(branch.commits) > UNUSUAL_COMMIT_THRESHOLD:
270 many_commits = True
271 break
272 if many_commits:
273 if not _ConfirmManyUploads(multiple_branches=True):
274 _die("upload aborted by user")
275
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700276 self._UploadAndReport(opt, todo, people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700277
Ben Komalo08a3f682010-07-15 16:03:02 -0700278 def _AppendAutoCcList(self, branch, people):
279 """
280 Appends the list of users in the CC list in the git project's config if a
281 non-empty reviewer list was found.
282 """
283
284 name = branch.name
285 project = branch.project
286 key = 'review.%s.autocopy' % project.GetBranch(name).remote.review
287 raw_list = project.config.GetString(key)
288 if not raw_list is None and len(people[0]) > 0:
289 people[1].extend([entry.strip() for entry in raw_list.split(',')])
290
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700291 def _FindGerritChange(self, branch):
292 last_pub = branch.project.WasPublished(branch.name)
293 if last_pub is None:
294 return ""
295
296 refs = branch.GetPublishedRefs()
297 try:
298 # refs/changes/XYZ/N --> XYZ
299 return refs.get(last_pub).split('/')[-2]
300 except:
301 return ""
302
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700303 def _UploadAndReport(self, opt, todo, original_people):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700304 have_errors = False
305 for branch in todo:
306 try:
Ben Komalo08a3f682010-07-15 16:03:02 -0700307 people = copy.deepcopy(original_people)
308 self._AppendAutoCcList(branch, people)
309
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500310 # Check if there are local changes that may have been forgotten
311 if branch.project.HasChanges():
312 key = 'review.%s.autoupload' % branch.project.remote.review
313 answer = branch.project.config.GetBoolean(key)
314
315 # if they want to auto upload, let's not ask because it could be automated
316 if answer is None:
Mike Frysingere9311272011-08-11 15:46:43 -0400317 sys.stdout.write('Uncommitted changes in ' + branch.project.name + ' (did you forget to amend?). Continue uploading? (y/N) ')
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500318 a = sys.stdin.readline().strip().lower()
319 if a not in ('y', 'yes', 't', 'true', 'on'):
320 print >>sys.stderr, "skipping upload"
321 branch.uploaded = False
322 branch.error = 'User aborted'
323 continue
324
Anthony Russellod666e932012-06-01 00:48:22 -0400325 # Check if topic branches should be sent to the server during upload
326 if opt.auto_topic is not True:
327 key = 'review.%s.uploadtopic' % branch.project.remote.review
328 opt.auto_topic = branch.project.config.GetBoolean(key)
329
Brian Harring435370c2012-07-28 15:37:04 -0700330 branch.UploadForReview(people, auto_topic=opt.auto_topic, draft=opt.draft)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700331 branch.uploaded = True
Sarah Owensa5be53f2012-09-09 15:37:57 -0700332 except UploadError as e:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700333 branch.error = e
334 branch.uploaded = False
335 have_errors = True
336
337 print >>sys.stderr, ''
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -0700338 print >>sys.stderr, '----------------------------------------------------------------------'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700339
340 if have_errors:
341 for branch in todo:
342 if not branch.uploaded:
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -0700343 if len(str(branch.error)) <= 30:
344 fmt = ' (%s)'
345 else:
346 fmt = '\n (%s)'
347 print >>sys.stderr, ('[FAILED] %-15s %-15s' + fmt) % (
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700348 branch.project.relpath + '/', \
349 branch.name, \
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -0700350 str(branch.error))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700351 print >>sys.stderr, ''
352
353 for branch in todo:
354 if branch.uploaded:
355 print >>sys.stderr, '[OK ] %-15s %s' % (
356 branch.project.relpath + '/',
357 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700358
359 if have_errors:
360 sys.exit(1)
361
362 def Execute(self, opt, args):
363 project_list = self.GetProjects(args)
364 pending = []
Joe Onorato2896a792008-11-17 16:56:36 -0500365 reviewers = []
366 cc = []
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700367 branch = None
368
369 if opt.branch:
370 branch = opt.branch
Joe Onorato2896a792008-11-17 16:56:36 -0500371
Doug Anderson37282b42011-03-04 11:54:18 -0800372 for project in project_list:
Daniel Sandlere9d6b612012-04-06 10:39:32 -0400373 if opt.current_branch:
374 cbr = project.CurrentBranch
375 avail = [project.GetUploadableBranch(cbr)] if cbr else None
376 else:
377 avail = project.GetUploadableBranches(branch)
Doug Anderson37282b42011-03-04 11:54:18 -0800378 if avail:
379 pending.append((project, avail))
380
381 if pending and (not opt.bypass_hooks):
382 hook = RepoHook('pre-upload', self.manifest.repo_hooks_project,
383 self.manifest.topdir, abort_if_user_denies=True)
384 pending_proj_names = [project.name for (project, avail) in pending]
385 try:
386 hook.Run(opt.allow_all_hooks, project_list=pending_proj_names)
Sarah Owensa5be53f2012-09-09 15:37:57 -0700387 except HookError as e:
Doug Anderson37282b42011-03-04 11:54:18 -0800388 print >>sys.stderr, "ERROR: %s" % str(e)
389 return
390
Joe Onorato2896a792008-11-17 16:56:36 -0500391 if opt.reviewers:
392 reviewers = _SplitEmails(opt.reviewers)
393 if opt.cc:
394 cc = _SplitEmails(opt.cc)
395 people = (reviewers,cc)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700396
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700397 if not pending:
398 print >>sys.stdout, "no branches ready for upload"
399 elif len(pending) == 1 and len(pending[0][1]) == 1:
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700400 self._SingleBranch(opt, pending[0][1][0], people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700401 else:
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700402 self._MultipleBranches(opt, pending, people)