blob: 01ba4ad03b7bc97ff43b82d98a7b6f2c0e4e3b1f [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
16import re
17import sys
18
19from command import InteractiveCommand
20from editor import Editor
21from error import UploadError
22
23def _die(fmt, *args):
24 msg = fmt % args
25 print >>sys.stderr, 'error: %s' % msg
26 sys.exit(1)
27
Joe Onorato2896a792008-11-17 16:56:36 -050028def _SplitEmails(values):
29 result = []
30 for str in values:
31 result.extend([s.strip() for s in str.split(',')])
32 return result
33
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070034class Upload(InteractiveCommand):
35 common = True
36 helpSummary = "Upload changes for code review"
37 helpUsage="""
Joe Onorato2896a792008-11-17 16:56:36 -050038%prog [--re --cc] {[<project>]... | --replace <project>}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070039"""
40 helpDescription = """
Shawn O. Pearce337fb9c2009-04-18 10:59:33 -070041The '%prog' command is used to send changes to the Gerrit Code
42Review system. It searches for topic branches in local projects
43that have not yet been published for review. If multiple topic
44branches are found, '%prog' opens an editor to allow the user to
45select which branches to upload.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070046
Shawn O. Pearce337fb9c2009-04-18 10:59:33 -070047'%prog' searches for uploadable changes in all projects listed at
48the command line. Projects can be specified either by name, or by
49a relative or absolute path to the project's local directory. If no
50projects are specified, '%prog' will search for uploadable changes
51in all projects listed in the manifest.
Joe Onorato2896a792008-11-17 16:56:36 -050052
53If the --reviewers or --cc options are passed, those emails are
54added to the respective list of users, and emails are sent to any
Shawn O. Pearce337fb9c2009-04-18 10:59:33 -070055new users. Users passed as --reviewers must already be registered
Joe Onorato2896a792008-11-17 16:56:36 -050056with the code review system, or the upload will fail.
Shawn O. Pearcea6df7d22008-12-12 08:04:07 -080057
58If the --replace option is passed the user can designate which
59existing change(s) in Gerrit match up to the commits in the branch
60being uploaded. For each matched pair of change,commit the commit
61will be added as a new patch set, completely replacing the set of
62files and description associated with the change in Gerrit.
Shawn O. Pearcea608fb02009-04-17 12:11:24 -070063
64Configuration
65-------------
66
67review.URL.autoupload:
68
69To disable the "Upload ... (y/n)?" prompt, you can set a per-project
70or global Git configuration option. If review.URL.autoupload is set
71to "true" then repo will assume you always answer "y" at the prompt,
72and will not prompt you further. If it is set to "false" then repo
73will assume you always answer "n", and will abort.
74
75The URL must match the review URL listed in the manifest XML file,
76or in the .git/config within the project. For example:
77
78 [remote "origin"]
79 url = git://git.example.com/project.git
80 review = http://review.example.com/
81
82 [review "http://review.example.com/"]
83 autoupload = true
84
Shawn O. Pearce337fb9c2009-04-18 10:59:33 -070085References
86----------
87
88Gerrit Code Review: http://code.google.com/p/gerrit/
89
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070090"""
91
Shawn O. Pearcec99883f2008-11-11 17:12:43 -080092 def _Options(self, p):
93 p.add_option('--replace',
94 dest='replace', action='store_true',
95 help='Upload replacement patchesets from this branch')
Joe Onorato2896a792008-11-17 16:56:36 -050096 p.add_option('--re', '--reviewers',
97 type='string', action='append', dest='reviewers',
98 help='Request reviews from these people.')
99 p.add_option('--cc',
100 type='string', action='append', dest='cc',
101 help='Also send email to these email addresses.')
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800102
Joe Onorato2896a792008-11-17 16:56:36 -0500103 def _SingleBranch(self, branch, people):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700104 project = branch.project
105 name = branch.name
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700106 remote = project.GetBranch(name).remote
107
108 key = 'review.%s.autoupload' % remote.review
109 answer = project.config.GetBoolean(key)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700110
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700111 if answer is False:
112 _die("upload blocked by %s = false" % key)
113
114 if answer is None:
Shawn O. Pearce66bdd462009-04-17 18:47:22 -0700115 date = branch.date
116 list = branch.commits
117
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700118 print 'Upload project %s/:' % project.relpath
119 print ' branch %s (%2d commit%s, %s):' % (
120 name,
121 len(list),
122 len(list) != 1 and 's' or '',
123 date)
124 for commit in list:
125 print ' %s' % commit
126
Shawn O. Pearce8225cdc2009-04-18 11:00:35 -0700127 sys.stdout.write('to %s (y/n)? ' % remote.review)
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700128 answer = sys.stdin.readline().strip()
129 answer = answer in ('y', 'Y', 'yes', '1', 'true', 't')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700130
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700131 if answer:
Joe Onorato2896a792008-11-17 16:56:36 -0500132 self._UploadAndReport([branch], people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700133 else:
134 _die("upload aborted by user")
135
Joe Onorato2896a792008-11-17 16:56:36 -0500136 def _MultipleBranches(self, pending, people):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700137 projects = {}
138 branches = {}
139
140 script = []
141 script.append('# Uncomment the branches to upload:')
142 for project, avail in pending:
143 script.append('#')
144 script.append('# project %s/:' % project.relpath)
145
146 b = {}
147 for branch in avail:
148 name = branch.name
149 date = branch.date
150 list = branch.commits
151
152 if b:
153 script.append('#')
154 script.append('# branch %s (%2d commit%s, %s):' % (
155 name,
156 len(list),
157 len(list) != 1 and 's' or '',
158 date))
159 for commit in list:
160 script.append('# %s' % commit)
161 b[name] = branch
162
163 projects[project.relpath] = project
164 branches[project.name] = b
165 script.append('')
166
167 script = Editor.EditString("\n".join(script)).split("\n")
168
169 project_re = re.compile(r'^#?\s*project\s*([^\s]+)/:$')
170 branch_re = re.compile(r'^\s*branch\s*([^\s(]+)\s*\(.*')
171
172 project = None
173 todo = []
174
175 for line in script:
176 m = project_re.match(line)
177 if m:
178 name = m.group(1)
179 project = projects.get(name)
180 if not project:
181 _die('project %s not available for upload', name)
182 continue
183
184 m = branch_re.match(line)
185 if m:
186 name = m.group(1)
187 if not project:
188 _die('project for branch %s not in script', name)
189 branch = branches[project.name].get(name)
190 if not branch:
191 _die('branch %s not in %s', name, project.relpath)
192 todo.append(branch)
193 if not todo:
194 _die("nothing uncommented for upload")
Joe Onorato2896a792008-11-17 16:56:36 -0500195 self._UploadAndReport(todo, people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700196
Shawn O. Pearcee92ceeb2008-11-24 15:51:25 -0800197 def _ReplaceBranch(self, project, people):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800198 branch = project.CurrentBranch
199 if not branch:
200 print >>sys.stdout, "no branches ready for upload"
201 return
202 branch = project.GetUploadableBranch(branch)
203 if not branch:
204 print >>sys.stdout, "no branches ready for upload"
205 return
206
207 script = []
208 script.append('# Replacing from branch %s' % branch.name)
209 for commit in branch.commits:
210 script.append('[ ] %s' % commit)
211 script.append('')
212 script.append('# Insert change numbers in the brackets to add a new patch set.')
213 script.append('# To create a new change record, leave the brackets empty.')
214
215 script = Editor.EditString("\n".join(script)).split("\n")
216
217 change_re = re.compile(r'^\[\s*(\d{1,})\s*\]\s*([0-9a-f]{1,}) .*$')
218 to_replace = dict()
219 full_hashes = branch.unabbrev_commits
220
221 for line in script:
222 m = change_re.match(line)
223 if m:
Shawn O. Pearce67092442008-12-12 08:01:12 -0800224 c = m.group(1)
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800225 f = m.group(2)
226 try:
227 f = full_hashes[f]
228 except KeyError:
229 print 'fh = %s' % full_hashes
230 print >>sys.stderr, "error: commit %s not found" % f
231 sys.exit(1)
Shawn O. Pearce67092442008-12-12 08:01:12 -0800232 if c in to_replace:
233 print >>sys.stderr,\
234 "error: change %s cannot accept multiple commits" % c
235 sys.exit(1)
236 to_replace[c] = f
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800237
238 if not to_replace:
239 print >>sys.stderr, "error: no replacements specified"
240 print >>sys.stderr, " use 'repo upload' without --replace"
241 sys.exit(1)
242
243 branch.replace_changes = to_replace
Joe Onorato2896a792008-11-17 16:56:36 -0500244 self._UploadAndReport([branch], people)
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800245
Joe Onorato2896a792008-11-17 16:56:36 -0500246 def _UploadAndReport(self, todo, people):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700247 have_errors = False
248 for branch in todo:
249 try:
Joe Onorato2896a792008-11-17 16:56:36 -0500250 branch.UploadForReview(people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700251 branch.uploaded = True
252 except UploadError, e:
253 branch.error = e
254 branch.uploaded = False
255 have_errors = True
256
257 print >>sys.stderr, ''
258 print >>sys.stderr, '--------------------------------------------'
259
260 if have_errors:
261 for branch in todo:
262 if not branch.uploaded:
263 print >>sys.stderr, '[FAILED] %-15s %-15s (%s)' % (
264 branch.project.relpath + '/', \
265 branch.name, \
266 branch.error)
267 print >>sys.stderr, ''
268
269 for branch in todo:
270 if branch.uploaded:
271 print >>sys.stderr, '[OK ] %-15s %s' % (
272 branch.project.relpath + '/',
273 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700274
275 if have_errors:
276 sys.exit(1)
277
278 def Execute(self, opt, args):
279 project_list = self.GetProjects(args)
280 pending = []
Joe Onorato2896a792008-11-17 16:56:36 -0500281 reviewers = []
282 cc = []
283
284 if opt.reviewers:
285 reviewers = _SplitEmails(opt.reviewers)
286 if opt.cc:
287 cc = _SplitEmails(opt.cc)
288 people = (reviewers,cc)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700289
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800290 if opt.replace:
291 if len(project_list) != 1:
292 print >>sys.stderr, \
293 'error: --replace requires exactly one project'
294 sys.exit(1)
Shawn O. Pearcee92ceeb2008-11-24 15:51:25 -0800295 self._ReplaceBranch(project_list[0], people)
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800296 return
297
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700298 for project in project_list:
299 avail = project.GetUploadableBranches()
300 if avail:
301 pending.append((project, avail))
302
303 if not pending:
304 print >>sys.stdout, "no branches ready for upload"
305 elif len(pending) == 1 and len(pending[0][1]) == 1:
Joe Onorato2896a792008-11-17 16:56:36 -0500306 self._SingleBranch(pending[0][1][0], people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700307 else:
Joe Onorato2896a792008-11-17 16:56:36 -0500308 self._MultipleBranches(pending, people)