blob: 11f035d75bcd191760007cef60fd34de1abae5d0 [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
28class Upload(InteractiveCommand):
29 common = True
30 helpSummary = "Upload changes for code review"
31 helpUsage="""
Shawn O. Pearcec99883f2008-11-11 17:12:43 -080032%prog {[<project>]... | --replace <project>}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070033"""
34 helpDescription = """
35The '%prog' command is used to send changes to the Gerrit code
36review system. It searches for changes in local projects that do
37not yet exist in the corresponding remote repository. If multiple
38changes are found, '%prog' opens an editor to allow the
39user to choose which change to upload. After a successful upload,
40repo prints the URL for the change in the Gerrit code review system.
41
42'%prog' searches for uploadable changes in all projects listed
43at the command line. Projects can be specified either by name, or
44by a relative or absolute path to the project's local directory. If
45no projects are specified, '%prog' will search for uploadable
46changes in all projects listed in the manifest.
47"""
48
Shawn O. Pearcec99883f2008-11-11 17:12:43 -080049 def _Options(self, p):
50 p.add_option('--replace',
51 dest='replace', action='store_true',
52 help='Upload replacement patchesets from this branch')
53
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070054 def _SingleBranch(self, branch):
55 project = branch.project
56 name = branch.name
57 date = branch.date
58 list = branch.commits
59
60 print 'Upload project %s/:' % project.relpath
61 print ' branch %s (%2d commit%s, %s):' % (
62 name,
63 len(list),
64 len(list) != 1 and 's' or '',
65 date)
66 for commit in list:
67 print ' %s' % commit
68
69 sys.stdout.write('(y/n)? ')
70 answer = sys.stdin.readline().strip()
71 if answer in ('y', 'Y', 'yes', '1', 'true', 't'):
72 self._UploadAndReport([branch])
73 else:
74 _die("upload aborted by user")
75
76 def _MultipleBranches(self, pending):
77 projects = {}
78 branches = {}
79
80 script = []
81 script.append('# Uncomment the branches to upload:')
82 for project, avail in pending:
83 script.append('#')
84 script.append('# project %s/:' % project.relpath)
85
86 b = {}
87 for branch in avail:
88 name = branch.name
89 date = branch.date
90 list = branch.commits
91
92 if b:
93 script.append('#')
94 script.append('# branch %s (%2d commit%s, %s):' % (
95 name,
96 len(list),
97 len(list) != 1 and 's' or '',
98 date))
99 for commit in list:
100 script.append('# %s' % commit)
101 b[name] = branch
102
103 projects[project.relpath] = project
104 branches[project.name] = b
105 script.append('')
106
107 script = Editor.EditString("\n".join(script)).split("\n")
108
109 project_re = re.compile(r'^#?\s*project\s*([^\s]+)/:$')
110 branch_re = re.compile(r'^\s*branch\s*([^\s(]+)\s*\(.*')
111
112 project = None
113 todo = []
114
115 for line in script:
116 m = project_re.match(line)
117 if m:
118 name = m.group(1)
119 project = projects.get(name)
120 if not project:
121 _die('project %s not available for upload', name)
122 continue
123
124 m = branch_re.match(line)
125 if m:
126 name = m.group(1)
127 if not project:
128 _die('project for branch %s not in script', name)
129 branch = branches[project.name].get(name)
130 if not branch:
131 _die('branch %s not in %s', name, project.relpath)
132 todo.append(branch)
133 if not todo:
134 _die("nothing uncommented for upload")
135 self._UploadAndReport(todo)
136
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800137 def _ReplaceBranch(self, project):
138 branch = project.CurrentBranch
139 if not branch:
140 print >>sys.stdout, "no branches ready for upload"
141 return
142 branch = project.GetUploadableBranch(branch)
143 if not branch:
144 print >>sys.stdout, "no branches ready for upload"
145 return
146
147 script = []
148 script.append('# Replacing from branch %s' % branch.name)
149 for commit in branch.commits:
150 script.append('[ ] %s' % commit)
151 script.append('')
152 script.append('# Insert change numbers in the brackets to add a new patch set.')
153 script.append('# To create a new change record, leave the brackets empty.')
154
155 script = Editor.EditString("\n".join(script)).split("\n")
156
157 change_re = re.compile(r'^\[\s*(\d{1,})\s*\]\s*([0-9a-f]{1,}) .*$')
158 to_replace = dict()
159 full_hashes = branch.unabbrev_commits
160
161 for line in script:
162 m = change_re.match(line)
163 if m:
164 f = m.group(2)
165 try:
166 f = full_hashes[f]
167 except KeyError:
168 print 'fh = %s' % full_hashes
169 print >>sys.stderr, "error: commit %s not found" % f
170 sys.exit(1)
171 to_replace[m.group(1)] = f
172
173 if not to_replace:
174 print >>sys.stderr, "error: no replacements specified"
175 print >>sys.stderr, " use 'repo upload' without --replace"
176 sys.exit(1)
177
178 branch.replace_changes = to_replace
179 self._UploadAndReport([branch])
180
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700181 def _UploadAndReport(self, todo):
182 have_errors = False
183 for branch in todo:
184 try:
185 branch.UploadForReview()
186 branch.uploaded = True
187 except UploadError, e:
188 branch.error = e
189 branch.uploaded = False
190 have_errors = True
191
192 print >>sys.stderr, ''
193 print >>sys.stderr, '--------------------------------------------'
194
195 if have_errors:
196 for branch in todo:
197 if not branch.uploaded:
198 print >>sys.stderr, '[FAILED] %-15s %-15s (%s)' % (
199 branch.project.relpath + '/', \
200 branch.name, \
201 branch.error)
202 print >>sys.stderr, ''
203
204 for branch in todo:
205 if branch.uploaded:
206 print >>sys.stderr, '[OK ] %-15s %s' % (
207 branch.project.relpath + '/',
208 branch.name)
209 print >>sys.stderr, '%s' % branch.tip_url
Shawn O. Pearce0758d2f2008-10-22 13:13:40 -0700210 print >>sys.stderr, '(as %s)' % branch.owner_email
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700211 print >>sys.stderr, ''
212
213 if have_errors:
214 sys.exit(1)
215
216 def Execute(self, opt, args):
217 project_list = self.GetProjects(args)
218 pending = []
219
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800220 if opt.replace:
221 if len(project_list) != 1:
222 print >>sys.stderr, \
223 'error: --replace requires exactly one project'
224 sys.exit(1)
225 self._ReplaceBranch(project_list[0])
226 return
227
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700228 for project in project_list:
229 avail = project.GetUploadableBranches()
230 if avail:
231 pending.append((project, avail))
232
233 if not pending:
234 print >>sys.stdout, "no branches ready for upload"
235 elif len(pending) == 1 and len(pending[0][1]) == 1:
236 self._SingleBranch(pending[0][1][0])
237 else:
238 self._MultipleBranches(pending)