blob: 20822096b1f0dc866d7b1efefda89033c9d20d27 [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
22from error import UploadError
23
Dan Morrillf0a9a1a2010-05-05 08:18:35 -070024UNUSUAL_COMMIT_THRESHOLD = 5
Dan Morrill879a9a52010-05-04 16:56:07 -070025
26def _ConfirmManyUploads(multiple_branches=False):
27 if multiple_branches:
28 print "ATTENTION: One or more branches has an unusually high number of commits."
29 else:
30 print "ATTENTION: You are uploading an unusually high number of commits."
31 print "YOU PROBABLY DO NOT MEAN TO DO THIS. (Did you rebase across branches?)"
32 answer = raw_input("If you are sure you intend to do this, type 'yes': ").strip()
33 return answer == "yes"
34
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070035def _die(fmt, *args):
36 msg = fmt % args
37 print >>sys.stderr, 'error: %s' % msg
38 sys.exit(1)
39
Joe Onorato2896a792008-11-17 16:56:36 -050040def _SplitEmails(values):
41 result = []
42 for str in values:
43 result.extend([s.strip() for s in str.split(',')])
44 return result
45
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070046class Upload(InteractiveCommand):
47 common = True
48 helpSummary = "Upload changes for code review"
49 helpUsage="""
Ficus Kirkpatricka0de6e82010-10-22 13:06:47 -070050%prog [--re --cc] [<project>]...
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070051"""
52 helpDescription = """
Shawn O. Pearce337fb9c2009-04-18 10:59:33 -070053The '%prog' command is used to send changes to the Gerrit Code
54Review system. It searches for topic branches in local projects
55that have not yet been published for review. If multiple topic
56branches are found, '%prog' opens an editor to allow the user to
57select which branches to upload.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070058
Shawn O. Pearce337fb9c2009-04-18 10:59:33 -070059'%prog' searches for uploadable changes in all projects listed at
60the command line. Projects can be specified either by name, or by
61a relative or absolute path to the project's local directory. If no
62projects are specified, '%prog' will search for uploadable changes
63in all projects listed in the manifest.
Joe Onorato2896a792008-11-17 16:56:36 -050064
65If the --reviewers or --cc options are passed, those emails are
66added to the respective list of users, and emails are sent to any
Shawn O. Pearce337fb9c2009-04-18 10:59:33 -070067new users. Users passed as --reviewers must already be registered
Joe Onorato2896a792008-11-17 16:56:36 -050068with the code review system, or the upload will fail.
Shawn O. Pearcea6df7d22008-12-12 08:04:07 -080069
Shawn O. Pearcea608fb02009-04-17 12:11:24 -070070Configuration
71-------------
72
73review.URL.autoupload:
74
75To disable the "Upload ... (y/n)?" prompt, you can set a per-project
76or global Git configuration option. If review.URL.autoupload is set
77to "true" then repo will assume you always answer "y" at the prompt,
78and will not prompt you further. If it is set to "false" then repo
79will assume you always answer "n", and will abort.
80
Ben Komalo08a3f682010-07-15 16:03:02 -070081review.URL.autocopy:
82
83To automatically copy a user or mailing list to all uploaded reviews,
84you can set a per-project or global Git option to do so. Specifically,
85review.URL.autocopy can be set to a comma separated list of reviewers
86who you always want copied on all uploads with a non-empty --re
87argument.
88
Shawn O. Pearce3575b8f2010-07-15 17:00:14 -070089review.URL.username:
90
91Override the username used to connect to Gerrit Code Review.
92By default the local part of the email address is used.
93
Shawn O. Pearcea608fb02009-04-17 12:11:24 -070094The URL must match the review URL listed in the manifest XML file,
95or in the .git/config within the project. For example:
96
97 [remote "origin"]
98 url = git://git.example.com/project.git
99 review = http://review.example.com/
100
101 [review "http://review.example.com/"]
102 autoupload = true
Ben Komalo08a3f682010-07-15 16:03:02 -0700103 autocopy = johndoe@company.com,my-team-alias@company.com
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700104
Shawn O. Pearce337fb9c2009-04-18 10:59:33 -0700105References
106----------
107
108Gerrit Code Review: http://code.google.com/p/gerrit/
109
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700110"""
111
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800112 def _Options(self, p):
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700113 p.add_option('-t',
114 dest='auto_topic', action='store_true',
115 help='Send local branch name to Gerrit Code Review')
Joe Onorato2896a792008-11-17 16:56:36 -0500116 p.add_option('--re', '--reviewers',
117 type='string', action='append', dest='reviewers',
118 help='Request reviews from these people.')
119 p.add_option('--cc',
120 type='string', action='append', dest='cc',
121 help='Also send email to these email addresses.')
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800122
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700123 def _SingleBranch(self, opt, branch, people):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700124 project = branch.project
125 name = branch.name
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700126 remote = project.GetBranch(name).remote
127
128 key = 'review.%s.autoupload' % remote.review
129 answer = project.config.GetBoolean(key)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700130
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700131 if answer is False:
132 _die("upload blocked by %s = false" % key)
133
134 if answer is None:
Shawn O. Pearce66bdd462009-04-17 18:47:22 -0700135 date = branch.date
136 list = branch.commits
137
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700138 print 'Upload project %s/:' % project.relpath
139 print ' branch %s (%2d commit%s, %s):' % (
140 name,
141 len(list),
142 len(list) != 1 and 's' or '',
143 date)
144 for commit in list:
145 print ' %s' % commit
146
Shawn O. Pearce8225cdc2009-04-18 11:00:35 -0700147 sys.stdout.write('to %s (y/n)? ' % remote.review)
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700148 answer = sys.stdin.readline().strip()
149 answer = answer in ('y', 'Y', 'yes', '1', 'true', 't')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700150
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700151 if answer:
Dan Morrill879a9a52010-05-04 16:56:07 -0700152 if len(branch.commits) > UNUSUAL_COMMIT_THRESHOLD:
153 answer = _ConfirmManyUploads()
154
155 if answer:
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700156 self._UploadAndReport(opt, [branch], people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700157 else:
158 _die("upload aborted by user")
159
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700160 def _MultipleBranches(self, opt, pending, people):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700161 projects = {}
162 branches = {}
163
164 script = []
165 script.append('# Uncomment the branches to upload:')
166 for project, avail in pending:
167 script.append('#')
168 script.append('# project %s/:' % project.relpath)
169
170 b = {}
171 for branch in avail:
172 name = branch.name
173 date = branch.date
174 list = branch.commits
175
176 if b:
177 script.append('#')
178 script.append('# branch %s (%2d commit%s, %s):' % (
179 name,
180 len(list),
181 len(list) != 1 and 's' or '',
182 date))
183 for commit in list:
184 script.append('# %s' % commit)
185 b[name] = branch
186
187 projects[project.relpath] = project
188 branches[project.name] = b
189 script.append('')
190
191 script = Editor.EditString("\n".join(script)).split("\n")
192
193 project_re = re.compile(r'^#?\s*project\s*([^\s]+)/:$')
194 branch_re = re.compile(r'^\s*branch\s*([^\s(]+)\s*\(.*')
195
196 project = None
197 todo = []
198
199 for line in script:
200 m = project_re.match(line)
201 if m:
202 name = m.group(1)
203 project = projects.get(name)
204 if not project:
205 _die('project %s not available for upload', name)
206 continue
207
208 m = branch_re.match(line)
209 if m:
210 name = m.group(1)
211 if not project:
212 _die('project for branch %s not in script', name)
213 branch = branches[project.name].get(name)
214 if not branch:
215 _die('branch %s not in %s', name, project.relpath)
216 todo.append(branch)
217 if not todo:
218 _die("nothing uncommented for upload")
Dan Morrill879a9a52010-05-04 16:56:07 -0700219
220 many_commits = False
221 for branch in todo:
222 if len(branch.commits) > UNUSUAL_COMMIT_THRESHOLD:
223 many_commits = True
224 break
225 if many_commits:
226 if not _ConfirmManyUploads(multiple_branches=True):
227 _die("upload aborted by user")
228
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700229 self._UploadAndReport(opt, todo, people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700230
Ben Komalo08a3f682010-07-15 16:03:02 -0700231 def _AppendAutoCcList(self, branch, people):
232 """
233 Appends the list of users in the CC list in the git project's config if a
234 non-empty reviewer list was found.
235 """
236
237 name = branch.name
238 project = branch.project
239 key = 'review.%s.autocopy' % project.GetBranch(name).remote.review
240 raw_list = project.config.GetString(key)
241 if not raw_list is None and len(people[0]) > 0:
242 people[1].extend([entry.strip() for entry in raw_list.split(',')])
243
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700244 def _FindGerritChange(self, branch):
245 last_pub = branch.project.WasPublished(branch.name)
246 if last_pub is None:
247 return ""
248
249 refs = branch.GetPublishedRefs()
250 try:
251 # refs/changes/XYZ/N --> XYZ
252 return refs.get(last_pub).split('/')[-2]
253 except:
254 return ""
255
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700256 def _UploadAndReport(self, opt, todo, original_people):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700257 have_errors = False
258 for branch in todo:
259 try:
Ben Komalo08a3f682010-07-15 16:03:02 -0700260 people = copy.deepcopy(original_people)
261 self._AppendAutoCcList(branch, people)
262
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500263 # Check if there are local changes that may have been forgotten
264 if branch.project.HasChanges():
265 key = 'review.%s.autoupload' % branch.project.remote.review
266 answer = branch.project.config.GetBoolean(key)
267
268 # if they want to auto upload, let's not ask because it could be automated
269 if answer is None:
270 sys.stdout.write('Uncommitted changes in ' + branch.project.name + ' (did you forget to amend?). Continue uploading? (y/n) ')
271 a = sys.stdin.readline().strip().lower()
272 if a not in ('y', 'yes', 't', 'true', 'on'):
273 print >>sys.stderr, "skipping upload"
274 branch.uploaded = False
275 branch.error = 'User aborted'
276 continue
277
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700278 branch.UploadForReview(people, auto_topic=opt.auto_topic)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700279 branch.uploaded = True
280 except UploadError, e:
281 branch.error = e
282 branch.uploaded = False
283 have_errors = True
284
285 print >>sys.stderr, ''
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -0700286 print >>sys.stderr, '----------------------------------------------------------------------'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700287
288 if have_errors:
289 for branch in todo:
290 if not branch.uploaded:
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -0700291 if len(str(branch.error)) <= 30:
292 fmt = ' (%s)'
293 else:
294 fmt = '\n (%s)'
295 print >>sys.stderr, ('[FAILED] %-15s %-15s' + fmt) % (
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700296 branch.project.relpath + '/', \
297 branch.name, \
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -0700298 str(branch.error))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700299 print >>sys.stderr, ''
300
301 for branch in todo:
302 if branch.uploaded:
303 print >>sys.stderr, '[OK ] %-15s %s' % (
304 branch.project.relpath + '/',
305 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700306
307 if have_errors:
308 sys.exit(1)
309
310 def Execute(self, opt, args):
311 project_list = self.GetProjects(args)
312 pending = []
Joe Onorato2896a792008-11-17 16:56:36 -0500313 reviewers = []
314 cc = []
315
316 if opt.reviewers:
317 reviewers = _SplitEmails(opt.reviewers)
318 if opt.cc:
319 cc = _SplitEmails(opt.cc)
320 people = (reviewers,cc)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700321
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700322 for project in project_list:
323 avail = project.GetUploadableBranches()
324 if avail:
325 pending.append((project, avail))
326
327 if not pending:
328 print >>sys.stdout, "no branches ready for upload"
329 elif len(pending) == 1 and len(pending[0][1]) == 1:
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700330 self._SingleBranch(opt, pending[0][1][0], people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700331 else:
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700332 self._MultipleBranches(opt, pending, people)