blob: c561b8aa7fa39d8623ceff18d244985a5aa8329b [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.')
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800123
Doug Anderson37282b42011-03-04 11:54:18 -0800124 # Options relating to upload hook. Note that verify and no-verify are NOT
125 # opposites of each other, which is why they store to different locations.
126 # We are using them to match 'git commit' syntax.
127 #
128 # Combinations:
129 # - no-verify=False, verify=False (DEFAULT):
130 # If stdout is a tty, can prompt about running upload hooks if needed.
131 # If user denies running hooks, the upload is cancelled. If stdout is
132 # not a tty and we would need to prompt about upload hooks, upload is
133 # cancelled.
134 # - no-verify=False, verify=True:
135 # Always run upload hooks with no prompt.
136 # - no-verify=True, verify=False:
137 # Never run upload hooks, but upload anyway (AKA bypass hooks).
138 # - no-verify=True, verify=True:
139 # Invalid
140 p.add_option('--no-verify',
141 dest='bypass_hooks', action='store_true',
142 help='Do not run the upload hook.')
143 p.add_option('--verify',
144 dest='allow_all_hooks', action='store_true',
145 help='Run the upload hook without prompting.')
146
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700147 def _SingleBranch(self, opt, branch, people):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700148 project = branch.project
149 name = branch.name
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700150 remote = project.GetBranch(name).remote
151
152 key = 'review.%s.autoupload' % remote.review
153 answer = project.config.GetBoolean(key)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700154
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700155 if answer is False:
156 _die("upload blocked by %s = false" % key)
157
158 if answer is None:
Shawn O. Pearce66bdd462009-04-17 18:47:22 -0700159 date = branch.date
160 list = branch.commits
161
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700162 print 'Upload project %s/:' % project.relpath
163 print ' branch %s (%2d commit%s, %s):' % (
164 name,
165 len(list),
166 len(list) != 1 and 's' or '',
167 date)
168 for commit in list:
169 print ' %s' % commit
170
Shawn O. Pearce8225cdc2009-04-18 11:00:35 -0700171 sys.stdout.write('to %s (y/n)? ' % remote.review)
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700172 answer = sys.stdin.readline().strip()
173 answer = answer in ('y', 'Y', 'yes', '1', 'true', 't')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700174
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700175 if answer:
Dan Morrill879a9a52010-05-04 16:56:07 -0700176 if len(branch.commits) > UNUSUAL_COMMIT_THRESHOLD:
177 answer = _ConfirmManyUploads()
178
179 if answer:
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700180 self._UploadAndReport(opt, [branch], people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700181 else:
182 _die("upload aborted by user")
183
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700184 def _MultipleBranches(self, opt, pending, people):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700185 projects = {}
186 branches = {}
187
188 script = []
189 script.append('# Uncomment the branches to upload:')
190 for project, avail in pending:
191 script.append('#')
192 script.append('# project %s/:' % project.relpath)
193
194 b = {}
195 for branch in avail:
196 name = branch.name
197 date = branch.date
198 list = branch.commits
199
200 if b:
201 script.append('#')
202 script.append('# branch %s (%2d commit%s, %s):' % (
203 name,
204 len(list),
205 len(list) != 1 and 's' or '',
206 date))
207 for commit in list:
208 script.append('# %s' % commit)
209 b[name] = branch
210
211 projects[project.relpath] = project
212 branches[project.name] = b
213 script.append('')
214
215 script = Editor.EditString("\n".join(script)).split("\n")
216
217 project_re = re.compile(r'^#?\s*project\s*([^\s]+)/:$')
218 branch_re = re.compile(r'^\s*branch\s*([^\s(]+)\s*\(.*')
219
220 project = None
221 todo = []
222
223 for line in script:
224 m = project_re.match(line)
225 if m:
226 name = m.group(1)
227 project = projects.get(name)
228 if not project:
229 _die('project %s not available for upload', name)
230 continue
231
232 m = branch_re.match(line)
233 if m:
234 name = m.group(1)
235 if not project:
236 _die('project for branch %s not in script', name)
237 branch = branches[project.name].get(name)
238 if not branch:
239 _die('branch %s not in %s', name, project.relpath)
240 todo.append(branch)
241 if not todo:
242 _die("nothing uncommented for upload")
Dan Morrill879a9a52010-05-04 16:56:07 -0700243
244 many_commits = False
245 for branch in todo:
246 if len(branch.commits) > UNUSUAL_COMMIT_THRESHOLD:
247 many_commits = True
248 break
249 if many_commits:
250 if not _ConfirmManyUploads(multiple_branches=True):
251 _die("upload aborted by user")
252
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700253 self._UploadAndReport(opt, todo, people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700254
Ben Komalo08a3f682010-07-15 16:03:02 -0700255 def _AppendAutoCcList(self, branch, people):
256 """
257 Appends the list of users in the CC list in the git project's config if a
258 non-empty reviewer list was found.
259 """
260
261 name = branch.name
262 project = branch.project
263 key = 'review.%s.autocopy' % project.GetBranch(name).remote.review
264 raw_list = project.config.GetString(key)
265 if not raw_list is None and len(people[0]) > 0:
266 people[1].extend([entry.strip() for entry in raw_list.split(',')])
267
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700268 def _FindGerritChange(self, branch):
269 last_pub = branch.project.WasPublished(branch.name)
270 if last_pub is None:
271 return ""
272
273 refs = branch.GetPublishedRefs()
274 try:
275 # refs/changes/XYZ/N --> XYZ
276 return refs.get(last_pub).split('/')[-2]
277 except:
278 return ""
279
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700280 def _UploadAndReport(self, opt, todo, original_people):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700281 have_errors = False
282 for branch in todo:
283 try:
Ben Komalo08a3f682010-07-15 16:03:02 -0700284 people = copy.deepcopy(original_people)
285 self._AppendAutoCcList(branch, people)
286
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500287 # Check if there are local changes that may have been forgotten
288 if branch.project.HasChanges():
289 key = 'review.%s.autoupload' % branch.project.remote.review
290 answer = branch.project.config.GetBoolean(key)
291
292 # if they want to auto upload, let's not ask because it could be automated
293 if answer is None:
294 sys.stdout.write('Uncommitted changes in ' + branch.project.name + ' (did you forget to amend?). Continue uploading? (y/n) ')
295 a = sys.stdin.readline().strip().lower()
296 if a not in ('y', 'yes', 't', 'true', 'on'):
297 print >>sys.stderr, "skipping upload"
298 branch.uploaded = False
299 branch.error = 'User aborted'
300 continue
301
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700302 branch.UploadForReview(people, auto_topic=opt.auto_topic)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700303 branch.uploaded = True
304 except UploadError, e:
305 branch.error = e
306 branch.uploaded = False
307 have_errors = True
308
309 print >>sys.stderr, ''
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -0700310 print >>sys.stderr, '----------------------------------------------------------------------'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700311
312 if have_errors:
313 for branch in todo:
314 if not branch.uploaded:
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -0700315 if len(str(branch.error)) <= 30:
316 fmt = ' (%s)'
317 else:
318 fmt = '\n (%s)'
319 print >>sys.stderr, ('[FAILED] %-15s %-15s' + fmt) % (
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700320 branch.project.relpath + '/', \
321 branch.name, \
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -0700322 str(branch.error))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700323 print >>sys.stderr, ''
324
325 for branch in todo:
326 if branch.uploaded:
327 print >>sys.stderr, '[OK ] %-15s %s' % (
328 branch.project.relpath + '/',
329 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700330
331 if have_errors:
332 sys.exit(1)
333
334 def Execute(self, opt, args):
335 project_list = self.GetProjects(args)
336 pending = []
Joe Onorato2896a792008-11-17 16:56:36 -0500337 reviewers = []
338 cc = []
339
Doug Anderson37282b42011-03-04 11:54:18 -0800340 for project in project_list:
341 avail = project.GetUploadableBranches()
342 if avail:
343 pending.append((project, avail))
344
345 if pending and (not opt.bypass_hooks):
346 hook = RepoHook('pre-upload', self.manifest.repo_hooks_project,
347 self.manifest.topdir, abort_if_user_denies=True)
348 pending_proj_names = [project.name for (project, avail) in pending]
349 try:
350 hook.Run(opt.allow_all_hooks, project_list=pending_proj_names)
351 except HookError, e:
352 print >>sys.stderr, "ERROR: %s" % str(e)
353 return
354
Joe Onorato2896a792008-11-17 16:56:36 -0500355 if opt.reviewers:
356 reviewers = _SplitEmails(opt.reviewers)
357 if opt.cc:
358 cc = _SplitEmails(opt.cc)
359 people = (reviewers,cc)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700360
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700361 if not pending:
362 print >>sys.stdout, "no branches ready for upload"
363 elif len(pending) == 1 and len(pending[0][1]) == 1:
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700364 self._SingleBranch(opt, pending[0][1][0], people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700365 else:
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700366 self._MultipleBranches(opt, pending, people)