blob: 4e4bdbe0fd788a3eeebae777620c2565fa1de459 [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:
Shawn O. Pearce67092442008-12-12 08:01:12 -0800181 c = m.group(1)
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800182 f = m.group(2)
183 try:
184 f = full_hashes[f]
185 except KeyError:
186 print 'fh = %s' % full_hashes
187 print >>sys.stderr, "error: commit %s not found" % f
188 sys.exit(1)
Shawn O. Pearce67092442008-12-12 08:01:12 -0800189 if c in to_replace:
190 print >>sys.stderr,\
191 "error: change %s cannot accept multiple commits" % c
192 sys.exit(1)
193 to_replace[c] = f
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800194
195 if not to_replace:
196 print >>sys.stderr, "error: no replacements specified"
197 print >>sys.stderr, " use 'repo upload' without --replace"
198 sys.exit(1)
199
200 branch.replace_changes = to_replace
Joe Onorato2896a792008-11-17 16:56:36 -0500201 self._UploadAndReport([branch], people)
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800202
Joe Onorato2896a792008-11-17 16:56:36 -0500203 def _UploadAndReport(self, todo, people):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700204 have_errors = False
205 for branch in todo:
206 try:
Joe Onorato2896a792008-11-17 16:56:36 -0500207 branch.UploadForReview(people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700208 branch.uploaded = True
209 except UploadError, e:
210 branch.error = e
211 branch.uploaded = False
212 have_errors = True
213
214 print >>sys.stderr, ''
215 print >>sys.stderr, '--------------------------------------------'
216
217 if have_errors:
218 for branch in todo:
219 if not branch.uploaded:
220 print >>sys.stderr, '[FAILED] %-15s %-15s (%s)' % (
221 branch.project.relpath + '/', \
222 branch.name, \
223 branch.error)
224 print >>sys.stderr, ''
225
226 for branch in todo:
227 if branch.uploaded:
228 print >>sys.stderr, '[OK ] %-15s %s' % (
229 branch.project.relpath + '/',
230 branch.name)
231 print >>sys.stderr, '%s' % branch.tip_url
Shawn O. Pearce0758d2f2008-10-22 13:13:40 -0700232 print >>sys.stderr, '(as %s)' % branch.owner_email
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700233 print >>sys.stderr, ''
234
235 if have_errors:
236 sys.exit(1)
237
238 def Execute(self, opt, args):
239 project_list = self.GetProjects(args)
240 pending = []
Joe Onorato2896a792008-11-17 16:56:36 -0500241 reviewers = []
242 cc = []
243
244 if opt.reviewers:
245 reviewers = _SplitEmails(opt.reviewers)
246 if opt.cc:
247 cc = _SplitEmails(opt.cc)
248 people = (reviewers,cc)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700249
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800250 if opt.replace:
251 if len(project_list) != 1:
252 print >>sys.stderr, \
253 'error: --replace requires exactly one project'
254 sys.exit(1)
Shawn O. Pearcee92ceeb2008-11-24 15:51:25 -0800255 self._ReplaceBranch(project_list[0], people)
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800256 return
257
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700258 for project in project_list:
259 avail = project.GetUploadableBranches()
260 if avail:
261 pending.append((project, avail))
262
263 if not pending:
264 print >>sys.stdout, "no branches ready for upload"
265 elif len(pending) == 1 and len(pending[0][1]) == 1:
Joe Onorato2896a792008-11-17 16:56:36 -0500266 self._SingleBranch(pending[0][1][0], people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700267 else:
Joe Onorato2896a792008-11-17 16:56:36 -0500268 self._MultipleBranches(pending, people)