blob: a08926c60a523292efdad6576f0cb32478d1d9b2 [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
76To disable the "Upload ... (y/n)?" prompt, you can set a per-project
77or 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
Shawn O. Pearce337fb9c2009-04-18 10:59:33 -0700106References
107----------
108
109Gerrit Code Review: http://code.google.com/p/gerrit/
110
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700111"""
112
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800113 def _Options(self, p):
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700114 p.add_option('-t',
115 dest='auto_topic', action='store_true',
116 help='Send local branch name to Gerrit Code Review')
Joe Onorato2896a792008-11-17 16:56:36 -0500117 p.add_option('--re', '--reviewers',
118 type='string', action='append', dest='reviewers',
119 help='Request reviews from these people.')
120 p.add_option('--cc',
121 type='string', action='append', dest='cc',
122 help='Also send email to these email addresses.')
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700123 p.add_option('--br',
124 type='string', action='store', dest='branch',
125 help='Branch to upload.')
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800126
Doug Anderson37282b42011-03-04 11:54:18 -0800127 # Options relating to upload hook. Note that verify and no-verify are NOT
128 # opposites of each other, which is why they store to different locations.
129 # We are using them to match 'git commit' syntax.
130 #
131 # Combinations:
132 # - no-verify=False, verify=False (DEFAULT):
133 # If stdout is a tty, can prompt about running upload hooks if needed.
134 # If user denies running hooks, the upload is cancelled. If stdout is
135 # not a tty and we would need to prompt about upload hooks, upload is
136 # cancelled.
137 # - no-verify=False, verify=True:
138 # Always run upload hooks with no prompt.
139 # - no-verify=True, verify=False:
140 # Never run upload hooks, but upload anyway (AKA bypass hooks).
141 # - no-verify=True, verify=True:
142 # Invalid
143 p.add_option('--no-verify',
144 dest='bypass_hooks', action='store_true',
145 help='Do not run the upload hook.')
146 p.add_option('--verify',
147 dest='allow_all_hooks', action='store_true',
148 help='Run the upload hook without prompting.')
149
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700150 def _SingleBranch(self, opt, branch, people):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700151 project = branch.project
152 name = branch.name
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700153 remote = project.GetBranch(name).remote
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700154
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700155 key = 'review.%s.autoupload' % remote.review
156 answer = project.config.GetBoolean(key)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700157
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700158 if answer is False:
159 _die("upload blocked by %s = false" % key)
160
161 if answer is None:
Shawn O. Pearce66bdd462009-04-17 18:47:22 -0700162 date = branch.date
163 list = branch.commits
164
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700165 print 'Upload project %s/:' % project.relpath
166 print ' branch %s (%2d commit%s, %s):' % (
167 name,
168 len(list),
169 len(list) != 1 and 's' or '',
170 date)
171 for commit in list:
172 print ' %s' % commit
173
Shawn O. Pearce8225cdc2009-04-18 11:00:35 -0700174 sys.stdout.write('to %s (y/n)? ' % remote.review)
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700175 answer = sys.stdin.readline().strip()
176 answer = answer in ('y', 'Y', 'yes', '1', 'true', 't')
177
178 if answer:
Dan Morrill879a9a52010-05-04 16:56:07 -0700179 if len(branch.commits) > UNUSUAL_COMMIT_THRESHOLD:
180 answer = _ConfirmManyUploads()
181
182 if answer:
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700183 self._UploadAndReport(opt, [branch], people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700184 else:
185 _die("upload aborted by user")
186
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700187 def _MultipleBranches(self, opt, pending, people):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700188 projects = {}
189 branches = {}
190
191 script = []
192 script.append('# Uncomment the branches to upload:')
193 for project, avail in pending:
194 script.append('#')
195 script.append('# project %s/:' % project.relpath)
196
197 b = {}
198 for branch in avail:
199 name = branch.name
200 date = branch.date
201 list = branch.commits
202
203 if b:
204 script.append('#')
205 script.append('# branch %s (%2d commit%s, %s):' % (
206 name,
207 len(list),
208 len(list) != 1 and 's' or '',
209 date))
210 for commit in list:
211 script.append('# %s' % commit)
212 b[name] = branch
213
214 projects[project.relpath] = project
215 branches[project.name] = b
216 script.append('')
217
chenguodong605a9a42011-08-22 18:42:47 +0800218 script = [ x.encode('utf-8')
219 if issubclass(type(x), unicode)
220 else x
221 for x in script ]
222
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700223 script = Editor.EditString("\n".join(script)).split("\n")
224
225 project_re = re.compile(r'^#?\s*project\s*([^\s]+)/:$')
226 branch_re = re.compile(r'^\s*branch\s*([^\s(]+)\s*\(.*')
227
228 project = None
229 todo = []
230
231 for line in script:
232 m = project_re.match(line)
233 if m:
234 name = m.group(1)
235 project = projects.get(name)
236 if not project:
237 _die('project %s not available for upload', name)
238 continue
239
240 m = branch_re.match(line)
241 if m:
242 name = m.group(1)
243 if not project:
244 _die('project for branch %s not in script', name)
245 branch = branches[project.name].get(name)
246 if not branch:
247 _die('branch %s not in %s', name, project.relpath)
248 todo.append(branch)
249 if not todo:
250 _die("nothing uncommented for upload")
Dan Morrill879a9a52010-05-04 16:56:07 -0700251
252 many_commits = False
253 for branch in todo:
254 if len(branch.commits) > UNUSUAL_COMMIT_THRESHOLD:
255 many_commits = True
256 break
257 if many_commits:
258 if not _ConfirmManyUploads(multiple_branches=True):
259 _die("upload aborted by user")
260
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700261 self._UploadAndReport(opt, todo, people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700262
Ben Komalo08a3f682010-07-15 16:03:02 -0700263 def _AppendAutoCcList(self, branch, people):
264 """
265 Appends the list of users in the CC list in the git project's config if a
266 non-empty reviewer list was found.
267 """
268
269 name = branch.name
270 project = branch.project
271 key = 'review.%s.autocopy' % project.GetBranch(name).remote.review
272 raw_list = project.config.GetString(key)
273 if not raw_list is None and len(people[0]) > 0:
274 people[1].extend([entry.strip() for entry in raw_list.split(',')])
275
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700276 def _FindGerritChange(self, branch):
277 last_pub = branch.project.WasPublished(branch.name)
278 if last_pub is None:
279 return ""
280
281 refs = branch.GetPublishedRefs()
282 try:
283 # refs/changes/XYZ/N --> XYZ
284 return refs.get(last_pub).split('/')[-2]
285 except:
286 return ""
287
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700288 def _UploadAndReport(self, opt, todo, original_people):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700289 have_errors = False
290 for branch in todo:
291 try:
Ben Komalo08a3f682010-07-15 16:03:02 -0700292 people = copy.deepcopy(original_people)
293 self._AppendAutoCcList(branch, people)
294
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500295 # Check if there are local changes that may have been forgotten
296 if branch.project.HasChanges():
297 key = 'review.%s.autoupload' % branch.project.remote.review
298 answer = branch.project.config.GetBoolean(key)
299
300 # if they want to auto upload, let's not ask because it could be automated
301 if answer is None:
302 sys.stdout.write('Uncommitted changes in ' + branch.project.name + ' (did you forget to amend?). Continue uploading? (y/n) ')
303 a = sys.stdin.readline().strip().lower()
304 if a not in ('y', 'yes', 't', 'true', 'on'):
305 print >>sys.stderr, "skipping upload"
306 branch.uploaded = False
307 branch.error = 'User aborted'
308 continue
309
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700310 branch.UploadForReview(people, auto_topic=opt.auto_topic)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700311 branch.uploaded = True
312 except UploadError, e:
313 branch.error = e
314 branch.uploaded = False
315 have_errors = True
316
317 print >>sys.stderr, ''
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -0700318 print >>sys.stderr, '----------------------------------------------------------------------'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700319
320 if have_errors:
321 for branch in todo:
322 if not branch.uploaded:
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -0700323 if len(str(branch.error)) <= 30:
324 fmt = ' (%s)'
325 else:
326 fmt = '\n (%s)'
327 print >>sys.stderr, ('[FAILED] %-15s %-15s' + fmt) % (
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700328 branch.project.relpath + '/', \
329 branch.name, \
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -0700330 str(branch.error))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700331 print >>sys.stderr, ''
332
333 for branch in todo:
334 if branch.uploaded:
335 print >>sys.stderr, '[OK ] %-15s %s' % (
336 branch.project.relpath + '/',
337 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700338
339 if have_errors:
340 sys.exit(1)
341
342 def Execute(self, opt, args):
343 project_list = self.GetProjects(args)
344 pending = []
Joe Onorato2896a792008-11-17 16:56:36 -0500345 reviewers = []
346 cc = []
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700347 branch = None
348
349 if opt.branch:
350 branch = opt.branch
Joe Onorato2896a792008-11-17 16:56:36 -0500351
Doug Anderson37282b42011-03-04 11:54:18 -0800352 for project in project_list:
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700353 avail = project.GetUploadableBranches(branch)
Doug Anderson37282b42011-03-04 11:54:18 -0800354 if avail:
355 pending.append((project, avail))
356
357 if pending and (not opt.bypass_hooks):
358 hook = RepoHook('pre-upload', self.manifest.repo_hooks_project,
359 self.manifest.topdir, abort_if_user_denies=True)
360 pending_proj_names = [project.name for (project, avail) in pending]
361 try:
362 hook.Run(opt.allow_all_hooks, project_list=pending_proj_names)
363 except HookError, e:
364 print >>sys.stderr, "ERROR: %s" % str(e)
365 return
366
Joe Onorato2896a792008-11-17 16:56:36 -0500367 if opt.reviewers:
368 reviewers = _SplitEmails(opt.reviewers)
369 if opt.cc:
370 cc = _SplitEmails(opt.cc)
371 people = (reviewers,cc)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700372
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700373 if not pending:
374 print >>sys.stdout, "no branches ready for upload"
375 elif len(pending) == 1 and len(pending[0][1]) == 1:
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700376 self._SingleBranch(opt, pending[0][1][0], people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700377 else:
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700378 self._MultipleBranches(opt, pending, people)