blob: 07bd4d8133d69c1fc26af521028ea7314f36f8be [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.')
Daniel Sandlere9d6b612012-04-06 10:39:32 -0400126 p.add_option('--cbr', '--current-branch',
127 dest='current_branch', action='store_true',
128 help='Upload current git branch.')
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800129
Doug Anderson37282b42011-03-04 11:54:18 -0800130 # Options relating to upload hook. Note that verify and no-verify are NOT
131 # opposites of each other, which is why they store to different locations.
132 # We are using them to match 'git commit' syntax.
133 #
134 # Combinations:
135 # - no-verify=False, verify=False (DEFAULT):
136 # If stdout is a tty, can prompt about running upload hooks if needed.
137 # If user denies running hooks, the upload is cancelled. If stdout is
138 # not a tty and we would need to prompt about upload hooks, upload is
139 # cancelled.
140 # - no-verify=False, verify=True:
141 # Always run upload hooks with no prompt.
142 # - no-verify=True, verify=False:
143 # Never run upload hooks, but upload anyway (AKA bypass hooks).
144 # - no-verify=True, verify=True:
145 # Invalid
146 p.add_option('--no-verify',
147 dest='bypass_hooks', action='store_true',
148 help='Do not run the upload hook.')
149 p.add_option('--verify',
150 dest='allow_all_hooks', action='store_true',
151 help='Run the upload hook without prompting.')
152
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700153 def _SingleBranch(self, opt, branch, people):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700154 project = branch.project
155 name = branch.name
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700156 remote = project.GetBranch(name).remote
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700157
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700158 key = 'review.%s.autoupload' % remote.review
159 answer = project.config.GetBoolean(key)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700160
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700161 if answer is False:
162 _die("upload blocked by %s = false" % key)
163
164 if answer is None:
Shawn O. Pearce66bdd462009-04-17 18:47:22 -0700165 date = branch.date
166 list = branch.commits
167
Christer Fletcher6a1f7372011-04-28 14:13:14 +0200168 print 'Upload project %s/ to remote branch %s:' % (project.relpath, project.revisionExpr)
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700169 print ' branch %s (%2d commit%s, %s):' % (
170 name,
171 len(list),
172 len(list) != 1 and 's' or '',
173 date)
174 for commit in list:
175 print ' %s' % commit
176
Mike Frysingere9311272011-08-11 15:46:43 -0400177 sys.stdout.write('to %s (y/N)? ' % remote.review)
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700178 answer = sys.stdin.readline().strip()
179 answer = answer in ('y', 'Y', 'yes', '1', 'true', 't')
180
181 if answer:
Dan Morrill879a9a52010-05-04 16:56:07 -0700182 if len(branch.commits) > UNUSUAL_COMMIT_THRESHOLD:
183 answer = _ConfirmManyUploads()
184
185 if answer:
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700186 self._UploadAndReport(opt, [branch], people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700187 else:
188 _die("upload aborted by user")
189
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700190 def _MultipleBranches(self, opt, pending, people):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700191 projects = {}
192 branches = {}
193
194 script = []
195 script.append('# Uncomment the branches to upload:')
196 for project, avail in pending:
197 script.append('#')
198 script.append('# project %s/:' % project.relpath)
199
200 b = {}
201 for branch in avail:
202 name = branch.name
203 date = branch.date
204 list = branch.commits
205
206 if b:
207 script.append('#')
Christer Fletcher6a1f7372011-04-28 14:13:14 +0200208 script.append('# branch %s (%2d commit%s, %s) to remote branch %s:' % (
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700209 name,
210 len(list),
211 len(list) != 1 and 's' or '',
Christer Fletcher6a1f7372011-04-28 14:13:14 +0200212 date,
213 project.revisionExpr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700214 for commit in list:
215 script.append('# %s' % commit)
216 b[name] = branch
217
218 projects[project.relpath] = project
219 branches[project.name] = b
220 script.append('')
221
chenguodong605a9a42011-08-22 18:42:47 +0800222 script = [ x.encode('utf-8')
223 if issubclass(type(x), unicode)
224 else x
225 for x in script ]
226
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700227 script = Editor.EditString("\n".join(script)).split("\n")
228
229 project_re = re.compile(r'^#?\s*project\s*([^\s]+)/:$')
230 branch_re = re.compile(r'^\s*branch\s*([^\s(]+)\s*\(.*')
231
232 project = None
233 todo = []
234
235 for line in script:
236 m = project_re.match(line)
237 if m:
238 name = m.group(1)
239 project = projects.get(name)
240 if not project:
241 _die('project %s not available for upload', name)
242 continue
243
244 m = branch_re.match(line)
245 if m:
246 name = m.group(1)
247 if not project:
248 _die('project for branch %s not in script', name)
249 branch = branches[project.name].get(name)
250 if not branch:
251 _die('branch %s not in %s', name, project.relpath)
252 todo.append(branch)
253 if not todo:
254 _die("nothing uncommented for upload")
Dan Morrill879a9a52010-05-04 16:56:07 -0700255
256 many_commits = False
257 for branch in todo:
258 if len(branch.commits) > UNUSUAL_COMMIT_THRESHOLD:
259 many_commits = True
260 break
261 if many_commits:
262 if not _ConfirmManyUploads(multiple_branches=True):
263 _die("upload aborted by user")
264
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700265 self._UploadAndReport(opt, todo, people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700266
Ben Komalo08a3f682010-07-15 16:03:02 -0700267 def _AppendAutoCcList(self, branch, people):
268 """
269 Appends the list of users in the CC list in the git project's config if a
270 non-empty reviewer list was found.
271 """
272
273 name = branch.name
274 project = branch.project
275 key = 'review.%s.autocopy' % project.GetBranch(name).remote.review
276 raw_list = project.config.GetString(key)
277 if not raw_list is None and len(people[0]) > 0:
278 people[1].extend([entry.strip() for entry in raw_list.split(',')])
279
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700280 def _FindGerritChange(self, branch):
281 last_pub = branch.project.WasPublished(branch.name)
282 if last_pub is None:
283 return ""
284
285 refs = branch.GetPublishedRefs()
286 try:
287 # refs/changes/XYZ/N --> XYZ
288 return refs.get(last_pub).split('/')[-2]
289 except:
290 return ""
291
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700292 def _UploadAndReport(self, opt, todo, original_people):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700293 have_errors = False
294 for branch in todo:
295 try:
Ben Komalo08a3f682010-07-15 16:03:02 -0700296 people = copy.deepcopy(original_people)
297 self._AppendAutoCcList(branch, people)
298
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500299 # Check if there are local changes that may have been forgotten
300 if branch.project.HasChanges():
301 key = 'review.%s.autoupload' % branch.project.remote.review
302 answer = branch.project.config.GetBoolean(key)
303
304 # if they want to auto upload, let's not ask because it could be automated
305 if answer is None:
Mike Frysingere9311272011-08-11 15:46:43 -0400306 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 -0500307 a = sys.stdin.readline().strip().lower()
308 if a not in ('y', 'yes', 't', 'true', 'on'):
309 print >>sys.stderr, "skipping upload"
310 branch.uploaded = False
311 branch.error = 'User aborted'
312 continue
313
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700314 branch.UploadForReview(people, auto_topic=opt.auto_topic)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700315 branch.uploaded = True
316 except UploadError, e:
317 branch.error = e
318 branch.uploaded = False
319 have_errors = True
320
321 print >>sys.stderr, ''
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -0700322 print >>sys.stderr, '----------------------------------------------------------------------'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700323
324 if have_errors:
325 for branch in todo:
326 if not branch.uploaded:
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -0700327 if len(str(branch.error)) <= 30:
328 fmt = ' (%s)'
329 else:
330 fmt = '\n (%s)'
331 print >>sys.stderr, ('[FAILED] %-15s %-15s' + fmt) % (
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700332 branch.project.relpath + '/', \
333 branch.name, \
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -0700334 str(branch.error))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700335 print >>sys.stderr, ''
336
337 for branch in todo:
338 if branch.uploaded:
339 print >>sys.stderr, '[OK ] %-15s %s' % (
340 branch.project.relpath + '/',
341 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700342
343 if have_errors:
344 sys.exit(1)
345
346 def Execute(self, opt, args):
347 project_list = self.GetProjects(args)
348 pending = []
Joe Onorato2896a792008-11-17 16:56:36 -0500349 reviewers = []
350 cc = []
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700351 branch = None
352
353 if opt.branch:
354 branch = opt.branch
Joe Onorato2896a792008-11-17 16:56:36 -0500355
Doug Anderson37282b42011-03-04 11:54:18 -0800356 for project in project_list:
Daniel Sandlere9d6b612012-04-06 10:39:32 -0400357 if opt.current_branch:
358 cbr = project.CurrentBranch
359 avail = [project.GetUploadableBranch(cbr)] if cbr else None
360 else:
361 avail = project.GetUploadableBranches(branch)
Doug Anderson37282b42011-03-04 11:54:18 -0800362 if avail:
363 pending.append((project, avail))
364
365 if pending and (not opt.bypass_hooks):
366 hook = RepoHook('pre-upload', self.manifest.repo_hooks_project,
367 self.manifest.topdir, abort_if_user_denies=True)
368 pending_proj_names = [project.name for (project, avail) in pending]
369 try:
370 hook.Run(opt.allow_all_hooks, project_list=pending_proj_names)
371 except HookError, e:
372 print >>sys.stderr, "ERROR: %s" % str(e)
373 return
374
Joe Onorato2896a792008-11-17 16:56:36 -0500375 if opt.reviewers:
376 reviewers = _SplitEmails(opt.reviewers)
377 if opt.cc:
378 cc = _SplitEmails(opt.cc)
379 people = (reviewers,cc)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700380
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700381 if not pending:
382 print >>sys.stdout, "no branches ready for upload"
383 elif len(pending) == 1 and len(pending[0][1]) == 1:
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700384 self._SingleBranch(opt, pending[0][1][0], people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700385 else:
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700386 self._MultipleBranches(opt, pending, people)