blob: 69efa107c6b24b848761b48ba75ddf02d02974a6 [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 = []
43 for str in values:
44 result.extend([s.strip() for s in str.split(',')])
45 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.')
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800137
Doug Anderson37282b42011-03-04 11:54:18 -0800138 # Options relating to upload hook. Note that verify and no-verify are NOT
139 # opposites of each other, which is why they store to different locations.
140 # We are using them to match 'git commit' syntax.
141 #
142 # Combinations:
143 # - no-verify=False, verify=False (DEFAULT):
144 # If stdout is a tty, can prompt about running upload hooks if needed.
145 # If user denies running hooks, the upload is cancelled. If stdout is
146 # not a tty and we would need to prompt about upload hooks, upload is
147 # cancelled.
148 # - no-verify=False, verify=True:
149 # Always run upload hooks with no prompt.
150 # - no-verify=True, verify=False:
151 # Never run upload hooks, but upload anyway (AKA bypass hooks).
152 # - no-verify=True, verify=True:
153 # Invalid
154 p.add_option('--no-verify',
155 dest='bypass_hooks', action='store_true',
156 help='Do not run the upload hook.')
157 p.add_option('--verify',
158 dest='allow_all_hooks', action='store_true',
159 help='Run the upload hook without prompting.')
160
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700161 def _SingleBranch(self, opt, branch, people):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700162 project = branch.project
163 name = branch.name
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700164 remote = project.GetBranch(name).remote
165
166 key = 'review.%s.autoupload' % remote.review
167 answer = project.config.GetBoolean(key)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700168
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700169 if answer is False:
170 _die("upload blocked by %s = false" % key)
171
172 if answer is None:
Shawn O. Pearce66bdd462009-04-17 18:47:22 -0700173 date = branch.date
174 list = branch.commits
175
Christer Fletcher6a1f7372011-04-28 14:13:14 +0200176 print 'Upload project %s/ to remote branch %s:' % (project.relpath, project.revisionExpr)
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700177 print ' branch %s (%2d commit%s, %s):' % (
178 name,
179 len(list),
180 len(list) != 1 and 's' or '',
181 date)
182 for commit in list:
183 print ' %s' % commit
184
Mike Frysingere9311272011-08-11 15:46:43 -0400185 sys.stdout.write('to %s (y/N)? ' % remote.review)
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700186 answer = sys.stdin.readline().strip()
187 answer = answer in ('y', 'Y', 'yes', '1', 'true', 't')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700188
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700189 if answer:
Dan Morrill879a9a52010-05-04 16:56:07 -0700190 if len(branch.commits) > UNUSUAL_COMMIT_THRESHOLD:
191 answer = _ConfirmManyUploads()
192
193 if answer:
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700194 self._UploadAndReport(opt, [branch], people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700195 else:
196 _die("upload aborted by user")
197
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700198 def _MultipleBranches(self, opt, pending, people):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700199 projects = {}
200 branches = {}
201
202 script = []
203 script.append('# Uncomment the branches to upload:')
204 for project, avail in pending:
205 script.append('#')
206 script.append('# project %s/:' % project.relpath)
207
208 b = {}
209 for branch in avail:
210 name = branch.name
211 date = branch.date
212 list = branch.commits
213
214 if b:
215 script.append('#')
Christer Fletcher6a1f7372011-04-28 14:13:14 +0200216 script.append('# branch %s (%2d commit%s, %s) to remote branch %s:' % (
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700217 name,
218 len(list),
219 len(list) != 1 and 's' or '',
Christer Fletcher6a1f7372011-04-28 14:13:14 +0200220 date,
221 project.revisionExpr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700222 for commit in list:
223 script.append('# %s' % commit)
224 b[name] = branch
225
226 projects[project.relpath] = project
227 branches[project.name] = b
228 script.append('')
229
chenguodong605a9a42011-08-22 18:42:47 +0800230 script = [ x.encode('utf-8')
231 if issubclass(type(x), unicode)
232 else x
233 for x in script ]
234
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700235 script = Editor.EditString("\n".join(script)).split("\n")
236
237 project_re = re.compile(r'^#?\s*project\s*([^\s]+)/:$')
238 branch_re = re.compile(r'^\s*branch\s*([^\s(]+)\s*\(.*')
239
240 project = None
241 todo = []
242
243 for line in script:
244 m = project_re.match(line)
245 if m:
246 name = m.group(1)
247 project = projects.get(name)
248 if not project:
249 _die('project %s not available for upload', name)
250 continue
251
252 m = branch_re.match(line)
253 if m:
254 name = m.group(1)
255 if not project:
256 _die('project for branch %s not in script', name)
257 branch = branches[project.name].get(name)
258 if not branch:
259 _die('branch %s not in %s', name, project.relpath)
260 todo.append(branch)
261 if not todo:
262 _die("nothing uncommented for upload")
Dan Morrill879a9a52010-05-04 16:56:07 -0700263
264 many_commits = False
265 for branch in todo:
266 if len(branch.commits) > UNUSUAL_COMMIT_THRESHOLD:
267 many_commits = True
268 break
269 if many_commits:
270 if not _ConfirmManyUploads(multiple_branches=True):
271 _die("upload aborted by user")
272
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700273 self._UploadAndReport(opt, todo, people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700274
Ben Komalo08a3f682010-07-15 16:03:02 -0700275 def _AppendAutoCcList(self, branch, people):
276 """
277 Appends the list of users in the CC list in the git project's config if a
278 non-empty reviewer list was found.
279 """
280
281 name = branch.name
282 project = branch.project
283 key = 'review.%s.autocopy' % project.GetBranch(name).remote.review
284 raw_list = project.config.GetString(key)
285 if not raw_list is None and len(people[0]) > 0:
286 people[1].extend([entry.strip() for entry in raw_list.split(',')])
287
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700288 def _FindGerritChange(self, branch):
289 last_pub = branch.project.WasPublished(branch.name)
290 if last_pub is None:
291 return ""
292
293 refs = branch.GetPublishedRefs()
294 try:
295 # refs/changes/XYZ/N --> XYZ
296 return refs.get(last_pub).split('/')[-2]
297 except:
298 return ""
299
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700300 def _UploadAndReport(self, opt, todo, original_people):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700301 have_errors = False
302 for branch in todo:
303 try:
Ben Komalo08a3f682010-07-15 16:03:02 -0700304 people = copy.deepcopy(original_people)
305 self._AppendAutoCcList(branch, people)
306
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500307 # Check if there are local changes that may have been forgotten
308 if branch.project.HasChanges():
309 key = 'review.%s.autoupload' % branch.project.remote.review
310 answer = branch.project.config.GetBoolean(key)
311
312 # if they want to auto upload, let's not ask because it could be automated
313 if answer is None:
Mike Frysingere9311272011-08-11 15:46:43 -0400314 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 -0500315 a = sys.stdin.readline().strip().lower()
316 if a not in ('y', 'yes', 't', 'true', 'on'):
317 print >>sys.stderr, "skipping upload"
318 branch.uploaded = False
319 branch.error = 'User aborted'
320 continue
321
Anthony Russellod666e932012-06-01 00:48:22 -0400322 # Check if topic branches should be sent to the server during upload
323 if opt.auto_topic is not True:
324 key = 'review.%s.uploadtopic' % branch.project.remote.review
325 opt.auto_topic = branch.project.config.GetBoolean(key)
326
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700327 branch.UploadForReview(people, auto_topic=opt.auto_topic)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700328 branch.uploaded = True
329 except UploadError, e:
330 branch.error = e
331 branch.uploaded = False
332 have_errors = True
333
334 print >>sys.stderr, ''
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -0700335 print >>sys.stderr, '----------------------------------------------------------------------'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700336
337 if have_errors:
338 for branch in todo:
339 if not branch.uploaded:
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -0700340 if len(str(branch.error)) <= 30:
341 fmt = ' (%s)'
342 else:
343 fmt = '\n (%s)'
344 print >>sys.stderr, ('[FAILED] %-15s %-15s' + fmt) % (
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700345 branch.project.relpath + '/', \
346 branch.name, \
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -0700347 str(branch.error))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700348 print >>sys.stderr, ''
349
350 for branch in todo:
351 if branch.uploaded:
352 print >>sys.stderr, '[OK ] %-15s %s' % (
353 branch.project.relpath + '/',
354 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700355
356 if have_errors:
357 sys.exit(1)
358
359 def Execute(self, opt, args):
360 project_list = self.GetProjects(args)
361 pending = []
Joe Onorato2896a792008-11-17 16:56:36 -0500362 reviewers = []
363 cc = []
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700364 branch = None
365
366 if opt.branch:
367 branch = opt.branch
Joe Onorato2896a792008-11-17 16:56:36 -0500368
Doug Anderson37282b42011-03-04 11:54:18 -0800369 for project in project_list:
Daniel Sandlere9d6b612012-04-06 10:39:32 -0400370 if opt.current_branch:
371 cbr = project.CurrentBranch
372 avail = [project.GetUploadableBranch(cbr)] if cbr else None
373 else:
374 avail = project.GetUploadableBranches(branch)
Doug Anderson37282b42011-03-04 11:54:18 -0800375 if avail:
376 pending.append((project, avail))
377
378 if pending and (not opt.bypass_hooks):
379 hook = RepoHook('pre-upload', self.manifest.repo_hooks_project,
380 self.manifest.topdir, abort_if_user_denies=True)
381 pending_proj_names = [project.name for (project, avail) in pending]
382 try:
383 hook.Run(opt.allow_all_hooks, project_list=pending_proj_names)
384 except HookError, e:
385 print >>sys.stderr, "ERROR: %s" % str(e)
386 return
387
Joe Onorato2896a792008-11-17 16:56:36 -0500388 if opt.reviewers:
389 reviewers = _SplitEmails(opt.reviewers)
390 if opt.cc:
391 cc = _SplitEmails(opt.cc)
392 people = (reviewers,cc)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700393
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700394 if not pending:
395 print >>sys.stdout, "no branches ready for upload"
396 elif len(pending) == 1 and len(pending[0][1]) == 1:
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700397 self._SingleBranch(opt, pending[0][1][0], people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700398 else:
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700399 self._MultipleBranches(opt, pending, people)