blob: 6a98a8bd19734480969519507a0ce7ee0817a72a [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 = """
41The '%prog' command is used to send changes to the Gerrit code
42review system. It searches for changes in local projects that do
43not yet exist in the corresponding remote repository. If multiple
44changes are found, '%prog' opens an editor to allow the
45user to choose which change to upload. After a successful upload,
46repo prints the URL for the change in the Gerrit code review system.
47
48'%prog' searches for uploadable changes in all projects listed
49at the command line. Projects can be specified either by name, or
50by a relative or absolute path to the project's local directory. If
51no projects are specified, '%prog' will search for uploadable
52changes in all projects listed in the manifest.
Joe Onorato2896a792008-11-17 16:56:36 -050053
54If the --reviewers or --cc options are passed, those emails are
55added to the respective list of users, and emails are sent to any
56new users. Users passed to --reviewers must be already registered
57with the code review system, or the upload will fail.
Shawn O. Pearcea6df7d22008-12-12 08:04:07 -080058
59If the --replace option is passed the user can designate which
60existing change(s) in Gerrit match up to the commits in the branch
61being uploaded. For each matched pair of change,commit the commit
62will be added as a new patch set, completely replacing the set of
63files and description associated with the change in Gerrit.
Shawn O. Pearcea608fb02009-04-17 12:11:24 -070064
65Configuration
66-------------
67
68review.URL.autoupload:
69
70To disable the "Upload ... (y/n)?" prompt, you can set a per-project
71or global Git configuration option. If review.URL.autoupload is set
72to "true" then repo will assume you always answer "y" at the prompt,
73and will not prompt you further. If it is set to "false" then repo
74will assume you always answer "n", and will abort.
75
76The URL must match the review URL listed in the manifest XML file,
77or in the .git/config within the project. For example:
78
79 [remote "origin"]
80 url = git://git.example.com/project.git
81 review = http://review.example.com/
82
83 [review "http://review.example.com/"]
84 autoupload = true
85
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070086"""
87
Shawn O. Pearcec99883f2008-11-11 17:12:43 -080088 def _Options(self, p):
89 p.add_option('--replace',
90 dest='replace', action='store_true',
91 help='Upload replacement patchesets from this branch')
Joe Onorato2896a792008-11-17 16:56:36 -050092 p.add_option('--re', '--reviewers',
93 type='string', action='append', dest='reviewers',
94 help='Request reviews from these people.')
95 p.add_option('--cc',
96 type='string', action='append', dest='cc',
97 help='Also send email to these email addresses.')
Shawn O. Pearcec99883f2008-11-11 17:12:43 -080098
Joe Onorato2896a792008-11-17 16:56:36 -050099 def _SingleBranch(self, branch, people):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700100 project = branch.project
101 name = branch.name
102 date = branch.date
103 list = branch.commits
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700104 remote = project.GetBranch(name).remote
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700105
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700106 key = 'review.%s.autoupload' % remote.review
107 answer = project.config.GetBoolean(key)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700108
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700109 if answer is False:
110 _die("upload blocked by %s = false" % key)
111
112 if answer is None:
113 print 'Upload project %s/:' % project.relpath
114 print ' branch %s (%2d commit%s, %s):' % (
115 name,
116 len(list),
117 len(list) != 1 and 's' or '',
118 date)
119 for commit in list:
120 print ' %s' % commit
121
122 sys.stdout.write('(y/n)? ')
123 answer = sys.stdin.readline().strip()
124 answer = answer in ('y', 'Y', 'yes', '1', 'true', 't')
125
126 if answer:
Joe Onorato2896a792008-11-17 16:56:36 -0500127 self._UploadAndReport([branch], people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700128 else:
129 _die("upload aborted by user")
130
Joe Onorato2896a792008-11-17 16:56:36 -0500131 def _MultipleBranches(self, pending, people):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700132 projects = {}
133 branches = {}
134
135 script = []
136 script.append('# Uncomment the branches to upload:')
137 for project, avail in pending:
138 script.append('#')
139 script.append('# project %s/:' % project.relpath)
140
141 b = {}
142 for branch in avail:
143 name = branch.name
144 date = branch.date
145 list = branch.commits
146
147 if b:
148 script.append('#')
149 script.append('# branch %s (%2d commit%s, %s):' % (
150 name,
151 len(list),
152 len(list) != 1 and 's' or '',
153 date))
154 for commit in list:
155 script.append('# %s' % commit)
156 b[name] = branch
157
158 projects[project.relpath] = project
159 branches[project.name] = b
160 script.append('')
161
162 script = Editor.EditString("\n".join(script)).split("\n")
163
164 project_re = re.compile(r'^#?\s*project\s*([^\s]+)/:$')
165 branch_re = re.compile(r'^\s*branch\s*([^\s(]+)\s*\(.*')
166
167 project = None
168 todo = []
169
170 for line in script:
171 m = project_re.match(line)
172 if m:
173 name = m.group(1)
174 project = projects.get(name)
175 if not project:
176 _die('project %s not available for upload', name)
177 continue
178
179 m = branch_re.match(line)
180 if m:
181 name = m.group(1)
182 if not project:
183 _die('project for branch %s not in script', name)
184 branch = branches[project.name].get(name)
185 if not branch:
186 _die('branch %s not in %s', name, project.relpath)
187 todo.append(branch)
188 if not todo:
189 _die("nothing uncommented for upload")
Joe Onorato2896a792008-11-17 16:56:36 -0500190 self._UploadAndReport(todo, people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700191
Shawn O. Pearcee92ceeb2008-11-24 15:51:25 -0800192 def _ReplaceBranch(self, project, people):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800193 branch = project.CurrentBranch
194 if not branch:
195 print >>sys.stdout, "no branches ready for upload"
196 return
197 branch = project.GetUploadableBranch(branch)
198 if not branch:
199 print >>sys.stdout, "no branches ready for upload"
200 return
201
202 script = []
203 script.append('# Replacing from branch %s' % branch.name)
204 for commit in branch.commits:
205 script.append('[ ] %s' % commit)
206 script.append('')
207 script.append('# Insert change numbers in the brackets to add a new patch set.')
208 script.append('# To create a new change record, leave the brackets empty.')
209
210 script = Editor.EditString("\n".join(script)).split("\n")
211
212 change_re = re.compile(r'^\[\s*(\d{1,})\s*\]\s*([0-9a-f]{1,}) .*$')
213 to_replace = dict()
214 full_hashes = branch.unabbrev_commits
215
216 for line in script:
217 m = change_re.match(line)
218 if m:
Shawn O. Pearce67092442008-12-12 08:01:12 -0800219 c = m.group(1)
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800220 f = m.group(2)
221 try:
222 f = full_hashes[f]
223 except KeyError:
224 print 'fh = %s' % full_hashes
225 print >>sys.stderr, "error: commit %s not found" % f
226 sys.exit(1)
Shawn O. Pearce67092442008-12-12 08:01:12 -0800227 if c in to_replace:
228 print >>sys.stderr,\
229 "error: change %s cannot accept multiple commits" % c
230 sys.exit(1)
231 to_replace[c] = f
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800232
233 if not to_replace:
234 print >>sys.stderr, "error: no replacements specified"
235 print >>sys.stderr, " use 'repo upload' without --replace"
236 sys.exit(1)
237
238 branch.replace_changes = to_replace
Joe Onorato2896a792008-11-17 16:56:36 -0500239 self._UploadAndReport([branch], people)
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800240
Joe Onorato2896a792008-11-17 16:56:36 -0500241 def _UploadAndReport(self, todo, people):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700242 have_errors = False
243 for branch in todo:
244 try:
Joe Onorato2896a792008-11-17 16:56:36 -0500245 branch.UploadForReview(people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700246 branch.uploaded = True
247 except UploadError, e:
248 branch.error = e
249 branch.uploaded = False
250 have_errors = True
251
252 print >>sys.stderr, ''
253 print >>sys.stderr, '--------------------------------------------'
254
255 if have_errors:
256 for branch in todo:
257 if not branch.uploaded:
258 print >>sys.stderr, '[FAILED] %-15s %-15s (%s)' % (
259 branch.project.relpath + '/', \
260 branch.name, \
261 branch.error)
262 print >>sys.stderr, ''
263
264 for branch in todo:
265 if branch.uploaded:
266 print >>sys.stderr, '[OK ] %-15s %s' % (
267 branch.project.relpath + '/',
268 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700269
270 if have_errors:
271 sys.exit(1)
272
273 def Execute(self, opt, args):
274 project_list = self.GetProjects(args)
275 pending = []
Joe Onorato2896a792008-11-17 16:56:36 -0500276 reviewers = []
277 cc = []
278
279 if opt.reviewers:
280 reviewers = _SplitEmails(opt.reviewers)
281 if opt.cc:
282 cc = _SplitEmails(opt.cc)
283 people = (reviewers,cc)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700284
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800285 if opt.replace:
286 if len(project_list) != 1:
287 print >>sys.stderr, \
288 'error: --replace requires exactly one project'
289 sys.exit(1)
Shawn O. Pearcee92ceeb2008-11-24 15:51:25 -0800290 self._ReplaceBranch(project_list[0], people)
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800291 return
292
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700293 for project in project_list:
294 avail = project.GetUploadableBranches()
295 if avail:
296 pending.append((project, avail))
297
298 if not pending:
299 print >>sys.stdout, "no branches ready for upload"
300 elif len(pending) == 1 and len(pending[0][1]) == 1:
Joe Onorato2896a792008-11-17 16:56:36 -0500301 self._SingleBranch(pending[0][1][0], people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700302 else:
Joe Onorato2896a792008-11-17 16:56:36 -0500303 self._MultipleBranches(pending, people)