blob: aea399b6307f50ebeb4d6c6fddcd611c1f772715 [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
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700107
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700108 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')
130
131 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
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700197 def _FindGerritChange(self, branch):
198 last_pub = branch.project.WasPublished(branch.name)
199 if last_pub is None:
200 return ""
201
202 refs = branch.GetPublishedRefs()
203 try:
204 # refs/changes/XYZ/N --> XYZ
205 return refs.get(last_pub).split('/')[-2]
206 except:
207 return ""
208
Shawn O. Pearcee92ceeb2008-11-24 15:51:25 -0800209 def _ReplaceBranch(self, project, people):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800210 branch = project.CurrentBranch
211 if not branch:
212 print >>sys.stdout, "no branches ready for upload"
213 return
214 branch = project.GetUploadableBranch(branch)
215 if not branch:
216 print >>sys.stdout, "no branches ready for upload"
217 return
218
219 script = []
220 script.append('# Replacing from branch %s' % branch.name)
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700221
222 if len(branch.commits) == 1:
223 change = self._FindGerritChange(branch)
224 script.append('[%-6s] %s' % (change, branch.commits[0]))
225 else:
226 for commit in branch.commits:
227 script.append('[ ] %s' % commit)
228
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800229 script.append('')
230 script.append('# Insert change numbers in the brackets to add a new patch set.')
231 script.append('# To create a new change record, leave the brackets empty.')
232
233 script = Editor.EditString("\n".join(script)).split("\n")
234
235 change_re = re.compile(r'^\[\s*(\d{1,})\s*\]\s*([0-9a-f]{1,}) .*$')
236 to_replace = dict()
237 full_hashes = branch.unabbrev_commits
238
239 for line in script:
240 m = change_re.match(line)
241 if m:
Shawn O. Pearce67092442008-12-12 08:01:12 -0800242 c = m.group(1)
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800243 f = m.group(2)
244 try:
245 f = full_hashes[f]
246 except KeyError:
247 print 'fh = %s' % full_hashes
248 print >>sys.stderr, "error: commit %s not found" % f
249 sys.exit(1)
Shawn O. Pearce67092442008-12-12 08:01:12 -0800250 if c in to_replace:
251 print >>sys.stderr,\
252 "error: change %s cannot accept multiple commits" % c
253 sys.exit(1)
254 to_replace[c] = f
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800255
256 if not to_replace:
257 print >>sys.stderr, "error: no replacements specified"
258 print >>sys.stderr, " use 'repo upload' without --replace"
259 sys.exit(1)
260
261 branch.replace_changes = to_replace
Joe Onorato2896a792008-11-17 16:56:36 -0500262 self._UploadAndReport([branch], people)
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800263
Joe Onorato2896a792008-11-17 16:56:36 -0500264 def _UploadAndReport(self, todo, people):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700265 have_errors = False
266 for branch in todo:
267 try:
Joe Onorato2896a792008-11-17 16:56:36 -0500268 branch.UploadForReview(people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700269 branch.uploaded = True
270 except UploadError, e:
271 branch.error = e
272 branch.uploaded = False
273 have_errors = True
274
275 print >>sys.stderr, ''
276 print >>sys.stderr, '--------------------------------------------'
277
278 if have_errors:
279 for branch in todo:
280 if not branch.uploaded:
281 print >>sys.stderr, '[FAILED] %-15s %-15s (%s)' % (
282 branch.project.relpath + '/', \
283 branch.name, \
284 branch.error)
285 print >>sys.stderr, ''
286
287 for branch in todo:
288 if branch.uploaded:
289 print >>sys.stderr, '[OK ] %-15s %s' % (
290 branch.project.relpath + '/',
291 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700292
293 if have_errors:
294 sys.exit(1)
295
296 def Execute(self, opt, args):
297 project_list = self.GetProjects(args)
298 pending = []
Joe Onorato2896a792008-11-17 16:56:36 -0500299 reviewers = []
300 cc = []
301
302 if opt.reviewers:
303 reviewers = _SplitEmails(opt.reviewers)
304 if opt.cc:
305 cc = _SplitEmails(opt.cc)
306 people = (reviewers,cc)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700307
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800308 if opt.replace:
309 if len(project_list) != 1:
310 print >>sys.stderr, \
311 'error: --replace requires exactly one project'
312 sys.exit(1)
Shawn O. Pearcee92ceeb2008-11-24 15:51:25 -0800313 self._ReplaceBranch(project_list[0], people)
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800314 return
315
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700316 for project in project_list:
317 avail = project.GetUploadableBranches()
318 if avail:
319 pending.append((project, avail))
320
321 if not pending:
322 print >>sys.stdout, "no branches ready for upload"
323 elif len(pending) == 1 and len(pending[0][1]) == 1:
Joe Onorato2896a792008-11-17 16:56:36 -0500324 self._SingleBranch(pending[0][1][0], people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700325 else:
Joe Onorato2896a792008-11-17 16:56:36 -0500326 self._MultipleBranches(pending, people)