blob: 58797507f49b2c24f80f7fd62ebcb18f929cc502 [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.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070064"""
65
Shawn O. Pearcec99883f2008-11-11 17:12:43 -080066 def _Options(self, p):
67 p.add_option('--replace',
68 dest='replace', action='store_true',
69 help='Upload replacement patchesets from this branch')
Joe Onorato2896a792008-11-17 16:56:36 -050070 p.add_option('--re', '--reviewers',
71 type='string', action='append', dest='reviewers',
72 help='Request reviews from these people.')
73 p.add_option('--cc',
74 type='string', action='append', dest='cc',
75 help='Also send email to these email addresses.')
Shawn O. Pearcec99883f2008-11-11 17:12:43 -080076
Joe Onorato2896a792008-11-17 16:56:36 -050077 def _SingleBranch(self, branch, people):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070078 project = branch.project
79 name = branch.name
80 date = branch.date
81 list = branch.commits
82
83 print 'Upload project %s/:' % project.relpath
84 print ' branch %s (%2d commit%s, %s):' % (
85 name,
86 len(list),
87 len(list) != 1 and 's' or '',
88 date)
89 for commit in list:
90 print ' %s' % commit
91
92 sys.stdout.write('(y/n)? ')
93 answer = sys.stdin.readline().strip()
94 if answer in ('y', 'Y', 'yes', '1', 'true', 't'):
Joe Onorato2896a792008-11-17 16:56:36 -050095 self._UploadAndReport([branch], people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070096 else:
97 _die("upload aborted by user")
98
Joe Onorato2896a792008-11-17 16:56:36 -050099 def _MultipleBranches(self, pending, people):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700100 projects = {}
101 branches = {}
102
103 script = []
104 script.append('# Uncomment the branches to upload:')
105 for project, avail in pending:
106 script.append('#')
107 script.append('# project %s/:' % project.relpath)
108
109 b = {}
110 for branch in avail:
111 name = branch.name
112 date = branch.date
113 list = branch.commits
114
115 if b:
116 script.append('#')
117 script.append('# branch %s (%2d commit%s, %s):' % (
118 name,
119 len(list),
120 len(list) != 1 and 's' or '',
121 date))
122 for commit in list:
123 script.append('# %s' % commit)
124 b[name] = branch
125
126 projects[project.relpath] = project
127 branches[project.name] = b
128 script.append('')
129
130 script = Editor.EditString("\n".join(script)).split("\n")
131
132 project_re = re.compile(r'^#?\s*project\s*([^\s]+)/:$')
133 branch_re = re.compile(r'^\s*branch\s*([^\s(]+)\s*\(.*')
134
135 project = None
136 todo = []
137
138 for line in script:
139 m = project_re.match(line)
140 if m:
141 name = m.group(1)
142 project = projects.get(name)
143 if not project:
144 _die('project %s not available for upload', name)
145 continue
146
147 m = branch_re.match(line)
148 if m:
149 name = m.group(1)
150 if not project:
151 _die('project for branch %s not in script', name)
152 branch = branches[project.name].get(name)
153 if not branch:
154 _die('branch %s not in %s', name, project.relpath)
155 todo.append(branch)
156 if not todo:
157 _die("nothing uncommented for upload")
Joe Onorato2896a792008-11-17 16:56:36 -0500158 self._UploadAndReport(todo, people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700159
Shawn O. Pearcee92ceeb2008-11-24 15:51:25 -0800160 def _ReplaceBranch(self, project, people):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800161 branch = project.CurrentBranch
162 if not branch:
163 print >>sys.stdout, "no branches ready for upload"
164 return
165 branch = project.GetUploadableBranch(branch)
166 if not branch:
167 print >>sys.stdout, "no branches ready for upload"
168 return
169
170 script = []
171 script.append('# Replacing from branch %s' % branch.name)
172 for commit in branch.commits:
173 script.append('[ ] %s' % commit)
174 script.append('')
175 script.append('# Insert change numbers in the brackets to add a new patch set.')
176 script.append('# To create a new change record, leave the brackets empty.')
177
178 script = Editor.EditString("\n".join(script)).split("\n")
179
180 change_re = re.compile(r'^\[\s*(\d{1,})\s*\]\s*([0-9a-f]{1,}) .*$')
181 to_replace = dict()
182 full_hashes = branch.unabbrev_commits
183
184 for line in script:
185 m = change_re.match(line)
186 if m:
Shawn O. Pearce67092442008-12-12 08:01:12 -0800187 c = m.group(1)
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800188 f = m.group(2)
189 try:
190 f = full_hashes[f]
191 except KeyError:
192 print 'fh = %s' % full_hashes
193 print >>sys.stderr, "error: commit %s not found" % f
194 sys.exit(1)
Shawn O. Pearce67092442008-12-12 08:01:12 -0800195 if c in to_replace:
196 print >>sys.stderr,\
197 "error: change %s cannot accept multiple commits" % c
198 sys.exit(1)
199 to_replace[c] = f
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800200
201 if not to_replace:
202 print >>sys.stderr, "error: no replacements specified"
203 print >>sys.stderr, " use 'repo upload' without --replace"
204 sys.exit(1)
205
206 branch.replace_changes = to_replace
Joe Onorato2896a792008-11-17 16:56:36 -0500207 self._UploadAndReport([branch], people)
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800208
Joe Onorato2896a792008-11-17 16:56:36 -0500209 def _UploadAndReport(self, todo, people):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700210 have_errors = False
211 for branch in todo:
212 try:
Joe Onorato2896a792008-11-17 16:56:36 -0500213 branch.UploadForReview(people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700214 branch.uploaded = True
215 except UploadError, e:
216 branch.error = e
217 branch.uploaded = False
218 have_errors = True
219
220 print >>sys.stderr, ''
221 print >>sys.stderr, '--------------------------------------------'
222
223 if have_errors:
224 for branch in todo:
225 if not branch.uploaded:
226 print >>sys.stderr, '[FAILED] %-15s %-15s (%s)' % (
227 branch.project.relpath + '/', \
228 branch.name, \
229 branch.error)
230 print >>sys.stderr, ''
231
232 for branch in todo:
233 if branch.uploaded:
234 print >>sys.stderr, '[OK ] %-15s %s' % (
235 branch.project.relpath + '/',
236 branch.name)
237 print >>sys.stderr, '%s' % branch.tip_url
Shawn O. Pearce0758d2f2008-10-22 13:13:40 -0700238 print >>sys.stderr, '(as %s)' % branch.owner_email
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700239 print >>sys.stderr, ''
240
241 if have_errors:
242 sys.exit(1)
243
244 def Execute(self, opt, args):
245 project_list = self.GetProjects(args)
246 pending = []
Joe Onorato2896a792008-11-17 16:56:36 -0500247 reviewers = []
248 cc = []
249
250 if opt.reviewers:
251 reviewers = _SplitEmails(opt.reviewers)
252 if opt.cc:
253 cc = _SplitEmails(opt.cc)
254 people = (reviewers,cc)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700255
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800256 if opt.replace:
257 if len(project_list) != 1:
258 print >>sys.stderr, \
259 'error: --replace requires exactly one project'
260 sys.exit(1)
Shawn O. Pearcee92ceeb2008-11-24 15:51:25 -0800261 self._ReplaceBranch(project_list[0], people)
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800262 return
263
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700264 for project in project_list:
265 avail = project.GetUploadableBranches()
266 if avail:
267 pending.append((project, avail))
268
269 if not pending:
270 print >>sys.stdout, "no branches ready for upload"
271 elif len(pending) == 1 and len(pending[0][1]) == 1:
Joe Onorato2896a792008-11-17 16:56:36 -0500272 self._SingleBranch(pending[0][1][0], people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700273 else:
Joe Onorato2896a792008-11-17 16:56:36 -0500274 self._MultipleBranches(pending, people)