blob: 53118f6be05a7a6a79aadf19e01199c08dae2298 [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
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
Christer Fletcher6a1f7372011-04-28 14:13:14 +0200165 print 'Upload project %s/ to remote branch %s:' % (project.relpath, project.revisionExpr)
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700166 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
Mike Frysingere9311272011-08-11 15:46:43 -0400174 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('#')
Christer Fletcher6a1f7372011-04-28 14:13:14 +0200205 script.append('# branch %s (%2d commit%s, %s) to remote branch %s:' % (
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700206 name,
207 len(list),
208 len(list) != 1 and 's' or '',
Christer Fletcher6a1f7372011-04-28 14:13:14 +0200209 date,
210 project.revisionExpr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700211 for commit in list:
212 script.append('# %s' % commit)
213 b[name] = branch
214
215 projects[project.relpath] = project
216 branches[project.name] = b
217 script.append('')
218
chenguodong605a9a42011-08-22 18:42:47 +0800219 script = [ x.encode('utf-8')
220 if issubclass(type(x), unicode)
221 else x
222 for x in script ]
223
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700224 script = Editor.EditString("\n".join(script)).split("\n")
225
226 project_re = re.compile(r'^#?\s*project\s*([^\s]+)/:$')
227 branch_re = re.compile(r'^\s*branch\s*([^\s(]+)\s*\(.*')
228
229 project = None
230 todo = []
231
232 for line in script:
233 m = project_re.match(line)
234 if m:
235 name = m.group(1)
236 project = projects.get(name)
237 if not project:
238 _die('project %s not available for upload', name)
239 continue
240
241 m = branch_re.match(line)
242 if m:
243 name = m.group(1)
244 if not project:
245 _die('project for branch %s not in script', name)
246 branch = branches[project.name].get(name)
247 if not branch:
248 _die('branch %s not in %s', name, project.relpath)
249 todo.append(branch)
250 if not todo:
251 _die("nothing uncommented for upload")
Dan Morrill879a9a52010-05-04 16:56:07 -0700252
253 many_commits = False
254 for branch in todo:
255 if len(branch.commits) > UNUSUAL_COMMIT_THRESHOLD:
256 many_commits = True
257 break
258 if many_commits:
259 if not _ConfirmManyUploads(multiple_branches=True):
260 _die("upload aborted by user")
261
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700262 self._UploadAndReport(opt, todo, people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700263
Ben Komalo08a3f682010-07-15 16:03:02 -0700264 def _AppendAutoCcList(self, branch, people):
265 """
266 Appends the list of users in the CC list in the git project's config if a
267 non-empty reviewer list was found.
268 """
269
270 name = branch.name
271 project = branch.project
272 key = 'review.%s.autocopy' % project.GetBranch(name).remote.review
273 raw_list = project.config.GetString(key)
274 if not raw_list is None and len(people[0]) > 0:
275 people[1].extend([entry.strip() for entry in raw_list.split(',')])
276
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700277 def _FindGerritChange(self, branch):
278 last_pub = branch.project.WasPublished(branch.name)
279 if last_pub is None:
280 return ""
281
282 refs = branch.GetPublishedRefs()
283 try:
284 # refs/changes/XYZ/N --> XYZ
285 return refs.get(last_pub).split('/')[-2]
286 except:
287 return ""
288
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700289 def _UploadAndReport(self, opt, todo, original_people):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700290 have_errors = False
291 for branch in todo:
292 try:
Ben Komalo08a3f682010-07-15 16:03:02 -0700293 people = copy.deepcopy(original_people)
294 self._AppendAutoCcList(branch, people)
295
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500296 # Check if there are local changes that may have been forgotten
297 if branch.project.HasChanges():
298 key = 'review.%s.autoupload' % branch.project.remote.review
299 answer = branch.project.config.GetBoolean(key)
300
301 # if they want to auto upload, let's not ask because it could be automated
302 if answer is None:
Mike Frysingere9311272011-08-11 15:46:43 -0400303 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 -0500304 a = sys.stdin.readline().strip().lower()
305 if a not in ('y', 'yes', 't', 'true', 'on'):
306 print >>sys.stderr, "skipping upload"
307 branch.uploaded = False
308 branch.error = 'User aborted'
309 continue
310
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700311 branch.UploadForReview(people, auto_topic=opt.auto_topic)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700312 branch.uploaded = True
313 except UploadError, e:
314 branch.error = e
315 branch.uploaded = False
316 have_errors = True
317
318 print >>sys.stderr, ''
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -0700319 print >>sys.stderr, '----------------------------------------------------------------------'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700320
321 if have_errors:
322 for branch in todo:
323 if not branch.uploaded:
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -0700324 if len(str(branch.error)) <= 30:
325 fmt = ' (%s)'
326 else:
327 fmt = '\n (%s)'
328 print >>sys.stderr, ('[FAILED] %-15s %-15s' + fmt) % (
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700329 branch.project.relpath + '/', \
330 branch.name, \
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -0700331 str(branch.error))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700332 print >>sys.stderr, ''
333
334 for branch in todo:
335 if branch.uploaded:
336 print >>sys.stderr, '[OK ] %-15s %s' % (
337 branch.project.relpath + '/',
338 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700339
340 if have_errors:
341 sys.exit(1)
342
343 def Execute(self, opt, args):
344 project_list = self.GetProjects(args)
345 pending = []
Joe Onorato2896a792008-11-17 16:56:36 -0500346 reviewers = []
347 cc = []
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700348 branch = None
349
350 if opt.branch:
351 branch = opt.branch
Joe Onorato2896a792008-11-17 16:56:36 -0500352
Doug Anderson37282b42011-03-04 11:54:18 -0800353 for project in project_list:
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700354 avail = project.GetUploadableBranches(branch)
Doug Anderson37282b42011-03-04 11:54:18 -0800355 if avail:
356 pending.append((project, avail))
357
358 if pending and (not opt.bypass_hooks):
359 hook = RepoHook('pre-upload', self.manifest.repo_hooks_project,
360 self.manifest.topdir, abort_if_user_denies=True)
361 pending_proj_names = [project.name for (project, avail) in pending]
362 try:
363 hook.Run(opt.allow_all_hooks, project_list=pending_proj_names)
364 except HookError, e:
365 print >>sys.stderr, "ERROR: %s" % str(e)
366 return
367
Joe Onorato2896a792008-11-17 16:56:36 -0500368 if opt.reviewers:
369 reviewers = _SplitEmails(opt.reviewers)
370 if opt.cc:
371 cc = _SplitEmails(opt.cc)
372 people = (reviewers,cc)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700373
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700374 if not pending:
375 print >>sys.stdout, "no branches ready for upload"
376 elif len(pending) == 1 and len(pending[0][1]) == 1:
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700377 self._SingleBranch(opt, pending[0][1][0], people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700378 else:
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700379 self._MultipleBranches(opt, pending, people)