blob: 5e36db61f691574cbb7fdf043ccc9b2d93ade8e9 [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.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070058"""
59
Shawn O. Pearcec99883f2008-11-11 17:12:43 -080060 def _Options(self, p):
61 p.add_option('--replace',
62 dest='replace', action='store_true',
63 help='Upload replacement patchesets from this branch')
Joe Onorato2896a792008-11-17 16:56:36 -050064 p.add_option('--re', '--reviewers',
65 type='string', action='append', dest='reviewers',
66 help='Request reviews from these people.')
67 p.add_option('--cc',
68 type='string', action='append', dest='cc',
69 help='Also send email to these email addresses.')
Shawn O. Pearcec99883f2008-11-11 17:12:43 -080070
Joe Onorato2896a792008-11-17 16:56:36 -050071 def _SingleBranch(self, branch, people):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070072 project = branch.project
73 name = branch.name
74 date = branch.date
75 list = branch.commits
76
77 print 'Upload project %s/:' % project.relpath
78 print ' branch %s (%2d commit%s, %s):' % (
79 name,
80 len(list),
81 len(list) != 1 and 's' or '',
82 date)
83 for commit in list:
84 print ' %s' % commit
85
86 sys.stdout.write('(y/n)? ')
87 answer = sys.stdin.readline().strip()
88 if answer in ('y', 'Y', 'yes', '1', 'true', 't'):
Joe Onorato2896a792008-11-17 16:56:36 -050089 self._UploadAndReport([branch], people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070090 else:
91 _die("upload aborted by user")
92
Joe Onorato2896a792008-11-17 16:56:36 -050093 def _MultipleBranches(self, pending, people):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070094 projects = {}
95 branches = {}
96
97 script = []
98 script.append('# Uncomment the branches to upload:')
99 for project, avail in pending:
100 script.append('#')
101 script.append('# project %s/:' % project.relpath)
102
103 b = {}
104 for branch in avail:
105 name = branch.name
106 date = branch.date
107 list = branch.commits
108
109 if b:
110 script.append('#')
111 script.append('# branch %s (%2d commit%s, %s):' % (
112 name,
113 len(list),
114 len(list) != 1 and 's' or '',
115 date))
116 for commit in list:
117 script.append('# %s' % commit)
118 b[name] = branch
119
120 projects[project.relpath] = project
121 branches[project.name] = b
122 script.append('')
123
124 script = Editor.EditString("\n".join(script)).split("\n")
125
126 project_re = re.compile(r'^#?\s*project\s*([^\s]+)/:$')
127 branch_re = re.compile(r'^\s*branch\s*([^\s(]+)\s*\(.*')
128
129 project = None
130 todo = []
131
132 for line in script:
133 m = project_re.match(line)
134 if m:
135 name = m.group(1)
136 project = projects.get(name)
137 if not project:
138 _die('project %s not available for upload', name)
139 continue
140
141 m = branch_re.match(line)
142 if m:
143 name = m.group(1)
144 if not project:
145 _die('project for branch %s not in script', name)
146 branch = branches[project.name].get(name)
147 if not branch:
148 _die('branch %s not in %s', name, project.relpath)
149 todo.append(branch)
150 if not todo:
151 _die("nothing uncommented for upload")
Joe Onorato2896a792008-11-17 16:56:36 -0500152 self._UploadAndReport(todo, people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700153
Shawn O. Pearcee92ceeb2008-11-24 15:51:25 -0800154 def _ReplaceBranch(self, project, people):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800155 branch = project.CurrentBranch
156 if not branch:
157 print >>sys.stdout, "no branches ready for upload"
158 return
159 branch = project.GetUploadableBranch(branch)
160 if not branch:
161 print >>sys.stdout, "no branches ready for upload"
162 return
163
164 script = []
165 script.append('# Replacing from branch %s' % branch.name)
166 for commit in branch.commits:
167 script.append('[ ] %s' % commit)
168 script.append('')
169 script.append('# Insert change numbers in the brackets to add a new patch set.')
170 script.append('# To create a new change record, leave the brackets empty.')
171
172 script = Editor.EditString("\n".join(script)).split("\n")
173
174 change_re = re.compile(r'^\[\s*(\d{1,})\s*\]\s*([0-9a-f]{1,}) .*$')
175 to_replace = dict()
176 full_hashes = branch.unabbrev_commits
177
178 for line in script:
179 m = change_re.match(line)
180 if m:
181 f = m.group(2)
182 try:
183 f = full_hashes[f]
184 except KeyError:
185 print 'fh = %s' % full_hashes
186 print >>sys.stderr, "error: commit %s not found" % f
187 sys.exit(1)
188 to_replace[m.group(1)] = f
189
190 if not to_replace:
191 print >>sys.stderr, "error: no replacements specified"
192 print >>sys.stderr, " use 'repo upload' without --replace"
193 sys.exit(1)
194
195 branch.replace_changes = to_replace
Joe Onorato2896a792008-11-17 16:56:36 -0500196 self._UploadAndReport([branch], people)
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800197
Joe Onorato2896a792008-11-17 16:56:36 -0500198 def _UploadAndReport(self, todo, people):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700199 have_errors = False
200 for branch in todo:
201 try:
Joe Onorato2896a792008-11-17 16:56:36 -0500202 branch.UploadForReview(people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700203 branch.uploaded = True
204 except UploadError, e:
205 branch.error = e
206 branch.uploaded = False
207 have_errors = True
208
209 print >>sys.stderr, ''
210 print >>sys.stderr, '--------------------------------------------'
211
212 if have_errors:
213 for branch in todo:
214 if not branch.uploaded:
215 print >>sys.stderr, '[FAILED] %-15s %-15s (%s)' % (
216 branch.project.relpath + '/', \
217 branch.name, \
218 branch.error)
219 print >>sys.stderr, ''
220
221 for branch in todo:
222 if branch.uploaded:
223 print >>sys.stderr, '[OK ] %-15s %s' % (
224 branch.project.relpath + '/',
225 branch.name)
226 print >>sys.stderr, '%s' % branch.tip_url
Shawn O. Pearce0758d2f2008-10-22 13:13:40 -0700227 print >>sys.stderr, '(as %s)' % branch.owner_email
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700228 print >>sys.stderr, ''
229
230 if have_errors:
231 sys.exit(1)
232
233 def Execute(self, opt, args):
234 project_list = self.GetProjects(args)
235 pending = []
Joe Onorato2896a792008-11-17 16:56:36 -0500236 reviewers = []
237 cc = []
238
239 if opt.reviewers:
240 reviewers = _SplitEmails(opt.reviewers)
241 if opt.cc:
242 cc = _SplitEmails(opt.cc)
243 people = (reviewers,cc)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700244
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800245 if opt.replace:
246 if len(project_list) != 1:
247 print >>sys.stderr, \
248 'error: --replace requires exactly one project'
249 sys.exit(1)
Shawn O. Pearcee92ceeb2008-11-24 15:51:25 -0800250 self._ReplaceBranch(project_list[0], people)
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800251 return
252
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700253 for project in project_list:
254 avail = project.GetUploadableBranches()
255 if avail:
256 pending.append((project, avail))
257
258 if not pending:
259 print >>sys.stdout, "no branches ready for upload"
260 elif len(pending) == 1 and len(pending[0][1]) == 1:
Joe Onorato2896a792008-11-17 16:56:36 -0500261 self._SingleBranch(pending[0][1][0], people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700262 else:
Joe Onorato2896a792008-11-17 16:56:36 -0500263 self._MultipleBranches(pending, people)