blob: de3fe8573ada5b5e797898a58d1b7ec642ec0495 [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
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700102 remote = project.GetBranch(name).remote
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700103
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700104 key = 'review.%s.autoupload' % remote.review
105 answer = project.config.GetBoolean(key)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700106
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700107 if answer is False:
108 _die("upload blocked by %s = false" % key)
109
110 if answer is None:
Shawn O. Pearce66bdd462009-04-17 18:47:22 -0700111 date = branch.date
112 list = branch.commits
113
Shawn O. Pearcea608fb02009-04-17 12:11:24 -0700114 print 'Upload project %s/:' % project.relpath
115 print ' branch %s (%2d commit%s, %s):' % (
116 name,
117 len(list),
118 len(list) != 1 and 's' or '',
119 date)
120 for commit in list:
121 print ' %s' % commit
122
123 sys.stdout.write('(y/n)? ')
124 answer = sys.stdin.readline().strip()
125 answer = answer in ('y', 'Y', 'yes', '1', 'true', 't')
126
127 if answer:
Joe Onorato2896a792008-11-17 16:56:36 -0500128 self._UploadAndReport([branch], people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700129 else:
130 _die("upload aborted by user")
131
Joe Onorato2896a792008-11-17 16:56:36 -0500132 def _MultipleBranches(self, pending, people):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700133 projects = {}
134 branches = {}
135
136 script = []
137 script.append('# Uncomment the branches to upload:')
138 for project, avail in pending:
139 script.append('#')
140 script.append('# project %s/:' % project.relpath)
141
142 b = {}
143 for branch in avail:
144 name = branch.name
145 date = branch.date
146 list = branch.commits
147
148 if b:
149 script.append('#')
150 script.append('# branch %s (%2d commit%s, %s):' % (
151 name,
152 len(list),
153 len(list) != 1 and 's' or '',
154 date))
155 for commit in list:
156 script.append('# %s' % commit)
157 b[name] = branch
158
159 projects[project.relpath] = project
160 branches[project.name] = b
161 script.append('')
162
163 script = Editor.EditString("\n".join(script)).split("\n")
164
165 project_re = re.compile(r'^#?\s*project\s*([^\s]+)/:$')
166 branch_re = re.compile(r'^\s*branch\s*([^\s(]+)\s*\(.*')
167
168 project = None
169 todo = []
170
171 for line in script:
172 m = project_re.match(line)
173 if m:
174 name = m.group(1)
175 project = projects.get(name)
176 if not project:
177 _die('project %s not available for upload', name)
178 continue
179
180 m = branch_re.match(line)
181 if m:
182 name = m.group(1)
183 if not project:
184 _die('project for branch %s not in script', name)
185 branch = branches[project.name].get(name)
186 if not branch:
187 _die('branch %s not in %s', name, project.relpath)
188 todo.append(branch)
189 if not todo:
190 _die("nothing uncommented for upload")
Joe Onorato2896a792008-11-17 16:56:36 -0500191 self._UploadAndReport(todo, people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700192
Shawn O. Pearcee92ceeb2008-11-24 15:51:25 -0800193 def _ReplaceBranch(self, project, people):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800194 branch = project.CurrentBranch
195 if not branch:
196 print >>sys.stdout, "no branches ready for upload"
197 return
198 branch = project.GetUploadableBranch(branch)
199 if not branch:
200 print >>sys.stdout, "no branches ready for upload"
201 return
202
203 script = []
204 script.append('# Replacing from branch %s' % branch.name)
205 for commit in branch.commits:
206 script.append('[ ] %s' % commit)
207 script.append('')
208 script.append('# Insert change numbers in the brackets to add a new patch set.')
209 script.append('# To create a new change record, leave the brackets empty.')
210
211 script = Editor.EditString("\n".join(script)).split("\n")
212
213 change_re = re.compile(r'^\[\s*(\d{1,})\s*\]\s*([0-9a-f]{1,}) .*$')
214 to_replace = dict()
215 full_hashes = branch.unabbrev_commits
216
217 for line in script:
218 m = change_re.match(line)
219 if m:
Shawn O. Pearce67092442008-12-12 08:01:12 -0800220 c = m.group(1)
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800221 f = m.group(2)
222 try:
223 f = full_hashes[f]
224 except KeyError:
225 print 'fh = %s' % full_hashes
226 print >>sys.stderr, "error: commit %s not found" % f
227 sys.exit(1)
Shawn O. Pearce67092442008-12-12 08:01:12 -0800228 if c in to_replace:
229 print >>sys.stderr,\
230 "error: change %s cannot accept multiple commits" % c
231 sys.exit(1)
232 to_replace[c] = f
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800233
234 if not to_replace:
235 print >>sys.stderr, "error: no replacements specified"
236 print >>sys.stderr, " use 'repo upload' without --replace"
237 sys.exit(1)
238
239 branch.replace_changes = to_replace
Joe Onorato2896a792008-11-17 16:56:36 -0500240 self._UploadAndReport([branch], people)
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800241
Joe Onorato2896a792008-11-17 16:56:36 -0500242 def _UploadAndReport(self, todo, people):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700243 have_errors = False
244 for branch in todo:
245 try:
Joe Onorato2896a792008-11-17 16:56:36 -0500246 branch.UploadForReview(people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700247 branch.uploaded = True
248 except UploadError, e:
249 branch.error = e
250 branch.uploaded = False
251 have_errors = True
252
253 print >>sys.stderr, ''
254 print >>sys.stderr, '--------------------------------------------'
255
256 if have_errors:
257 for branch in todo:
258 if not branch.uploaded:
259 print >>sys.stderr, '[FAILED] %-15s %-15s (%s)' % (
260 branch.project.relpath + '/', \
261 branch.name, \
262 branch.error)
263 print >>sys.stderr, ''
264
265 for branch in todo:
266 if branch.uploaded:
267 print >>sys.stderr, '[OK ] %-15s %s' % (
268 branch.project.relpath + '/',
269 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700270
271 if have_errors:
272 sys.exit(1)
273
274 def Execute(self, opt, args):
275 project_list = self.GetProjects(args)
276 pending = []
Joe Onorato2896a792008-11-17 16:56:36 -0500277 reviewers = []
278 cc = []
279
280 if opt.reviewers:
281 reviewers = _SplitEmails(opt.reviewers)
282 if opt.cc:
283 cc = _SplitEmails(opt.cc)
284 people = (reviewers,cc)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700285
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800286 if opt.replace:
287 if len(project_list) != 1:
288 print >>sys.stderr, \
289 'error: --replace requires exactly one project'
290 sys.exit(1)
Shawn O. Pearcee92ceeb2008-11-24 15:51:25 -0800291 self._ReplaceBranch(project_list[0], people)
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800292 return
293
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700294 for project in project_list:
295 avail = project.GetUploadableBranches()
296 if avail:
297 pending.append((project, avail))
298
299 if not pending:
300 print >>sys.stdout, "no branches ready for upload"
301 elif len(pending) == 1 and len(pending[0][1]) == 1:
Joe Onorato2896a792008-11-17 16:56:36 -0500302 self._SingleBranch(pending[0][1][0], people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700303 else:
Joe Onorato2896a792008-11-17 16:56:36 -0500304 self._MultipleBranches(pending, people)