summaryrefslogtreecommitdiffstats
path: root/subcmds
diff options
context:
space:
mode:
Diffstat (limited to 'subcmds')
-rw-r--r--subcmds/branches.py12
-rw-r--r--subcmds/grep.py8
-rw-r--r--subcmds/init.py29
-rw-r--r--subcmds/rebase.py107
-rw-r--r--subcmds/smartsync.py33
-rw-r--r--subcmds/sync.py196
-rw-r--r--subcmds/upload.py164
7 files changed, 417 insertions, 132 deletions
diff --git a/subcmds/branches.py b/subcmds/branches.py
index 0e3ab3c2..a4f8d360 100644
--- a/subcmds/branches.py
+++ b/subcmds/branches.py
@@ -136,7 +136,7 @@ is shown, then the branch appears in all projects.
136 hdr('%c%c %-*s' % (current, published, width, name)) 136 hdr('%c%c %-*s' % (current, published, width, name))
137 out.write(' |') 137 out.write(' |')
138 138
139 if in_cnt < project_cnt and (in_cnt == 1): 139 if in_cnt < project_cnt:
140 fmt = out.write 140 fmt = out.write
141 paths = [] 141 paths = []
142 if in_cnt < project_cnt - in_cnt: 142 if in_cnt < project_cnt - in_cnt:
@@ -150,15 +150,17 @@ is shown, then the branch appears in all projects.
150 for b in i.projects: 150 for b in i.projects:
151 have.add(b.project) 151 have.add(b.project)
152 for p in projects: 152 for p in projects:
153 paths.append(p.relpath) 153 if not p in have:
154 paths.append(p.relpath)
154 155
155 s = ' %s %s' % (type, ', '.join(paths)) 156 s = ' %s %s' % (type, ', '.join(paths))
156 if width + 7 + len(s) < 80: 157 if width + 7 + len(s) < 80:
157 fmt(s) 158 fmt(s)
158 else: 159 else:
159 out.nl() 160 fmt(' %s:' % type)
160 fmt(' %s:' % type)
161 for p in paths: 161 for p in paths:
162 out.nl() 162 out.nl()
163 fmt(' %s' % p) 163 fmt(width*' ' + ' %s' % p)
164 else:
165 out.write(' in all projects')
164 out.nl() 166 out.nl()
diff --git a/subcmds/grep.py b/subcmds/grep.py
index 4f714271..1cb5650b 100644
--- a/subcmds/grep.py
+++ b/subcmds/grep.py
@@ -204,7 +204,7 @@ contain a line that matches both expressions:
204 else: 204 else:
205 out.project('--- project %s ---' % project.relpath) 205 out.project('--- project %s ---' % project.relpath)
206 out.nl() 206 out.nl()
207 out.write(p.stderr) 207 out.write("%s", p.stderr)
208 out.nl() 208 out.nl()
209 continue 209 continue
210 have_match = True 210 have_match = True
@@ -217,17 +217,17 @@ contain a line that matches both expressions:
217 if have_rev and full_name: 217 if have_rev and full_name:
218 for line in r: 218 for line in r:
219 rev, line = line.split(':', 1) 219 rev, line = line.split(':', 1)
220 out.write(rev) 220 out.write("%s", rev)
221 out.write(':') 221 out.write(':')
222 out.project(project.relpath) 222 out.project(project.relpath)
223 out.write('/') 223 out.write('/')
224 out.write(line) 224 out.write("%s", line)
225 out.nl() 225 out.nl()
226 elif full_name: 226 elif full_name:
227 for line in r: 227 for line in r:
228 out.project(project.relpath) 228 out.project(project.relpath)
229 out.write('/') 229 out.write('/')
230 out.write(line) 230 out.write("%s", line)
231 out.nl() 231 out.nl()
232 else: 232 else:
233 for line in r: 233 for line in r:
diff --git a/subcmds/init.py b/subcmds/init.py
index cdbbfdf7..2ca4e163 100644
--- a/subcmds/init.py
+++ b/subcmds/init.py
@@ -40,6 +40,17 @@ current working directory.
40The optional -b argument can be used to select the manifest branch 40The optional -b argument can be used to select the manifest branch
41to checkout and use. If no branch is specified, master is assumed. 41to checkout and use. If no branch is specified, master is assumed.
42 42
43The optional -m argument can be used to specify an alternate manifest
44to be used. If no manifest is specified, the manifest default.xml
45will be used.
46
47The --reference option can be used to point to a directory that
48has the content of a --mirror sync. This will make the working
49directory use as much data as possible from the local reference
50directory when fetching from the server. This will make the sync
51go a lot faster by reducing data traffic on the network.
52
53
43Switching Manifest Branches 54Switching Manifest Branches
44--------------------------- 55---------------------------
45 56
@@ -76,7 +87,9 @@ to update the working directory files.
76 g.add_option('--mirror', 87 g.add_option('--mirror',
77 dest='mirror', action='store_true', 88 dest='mirror', action='store_true',
78 help='mirror the forrest') 89 help='mirror the forrest')
79 90 g.add_option('--reference',
91 dest='reference',
92 help='location of mirror directory', metavar='DIR')
80 93
81 # Tool 94 # Tool
82 g = p.add_option_group('repo Version options') 95 g = p.add_option_group('repo Version options')
@@ -132,6 +145,9 @@ to update the working directory files.
132 r.ResetFetch() 145 r.ResetFetch()
133 r.Save() 146 r.Save()
134 147
148 if opt.reference:
149 m.config.SetString('repo.reference', opt.reference)
150
135 if opt.mirror: 151 if opt.mirror:
136 if is_new: 152 if is_new:
137 m.config.SetString('repo.mirror', 'true') 153 m.config.SetString('repo.mirror', 'true')
@@ -162,7 +178,11 @@ to update the working directory files.
162 syncbuf = SyncBuffer(m.config) 178 syncbuf = SyncBuffer(m.config)
163 m.Sync_LocalHalf(syncbuf) 179 m.Sync_LocalHalf(syncbuf)
164 syncbuf.Finish() 180 syncbuf.Finish()
181
182 if isinstance(self.manifest, XmlManifest):
183 self._LinkManifest(opt.manifest_name)
165 _ReloadManifest(self) 184 _ReloadManifest(self)
185
166 self._ApplyOptions(opt, is_new) 186 self._ApplyOptions(opt, is_new)
167 187
168 if not self.manifest.InitBranch(): 188 if not self.manifest.InitBranch():
@@ -200,8 +220,9 @@ to update the working directory files.
200 220
201 print '' 221 print ''
202 print 'Your identity is: %s <%s>' % (name, email) 222 print 'Your identity is: %s <%s>' % (name, email)
203 sys.stdout.write('is this correct [yes/no]? ') 223 sys.stdout.write('is this correct [y/n]? ')
204 if 'yes' == sys.stdin.readline().strip(): 224 a = sys.stdin.readline().strip()
225 if a in ('yes', 'y', 't', 'true'):
205 break 226 break
206 227
207 if name != mp.UserName: 228 if name != mp.UserName:
@@ -249,8 +270,6 @@ to update the working directory files.
249 def Execute(self, opt, args): 270 def Execute(self, opt, args):
250 git_require(MIN_GIT_VERSION, fail=True) 271 git_require(MIN_GIT_VERSION, fail=True)
251 self._SyncManifest(opt) 272 self._SyncManifest(opt)
252 if isinstance(self.manifest, XmlManifest):
253 self._LinkManifest(opt.manifest_name)
254 273
255 if os.isatty(0) and os.isatty(1) and not self.manifest.IsMirror: 274 if os.isatty(0) and os.isatty(1) and not self.manifest.IsMirror:
256 self._ConfigureUser() 275 self._ConfigureUser()
diff --git a/subcmds/rebase.py b/subcmds/rebase.py
new file mode 100644
index 00000000..e341296d
--- /dev/null
+++ b/subcmds/rebase.py
@@ -0,0 +1,107 @@
1#
2# Copyright (C) 2010 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 sys
17
18from command import Command
19from git_command import GitCommand
20from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB
21from error import GitError
22
23class Rebase(Command):
24 common = True
25 helpSummary = "Rebase local branches on upstream branch"
26 helpUsage = """
27%prog {[<project>...] | -i <project>...}
28"""
29 helpDescription = """
30'%prog' uses git rebase to move local changes in the current topic branch to
31the HEAD of the upstream history, useful when you have made commits in a topic
32branch but need to incorporate new upstream changes "underneath" them.
33"""
34
35 def _Options(self, p):
36 p.add_option('-i', '--interactive',
37 dest="interactive", action="store_true",
38 help="interactive rebase (single project only)")
39
40 p.add_option('-f', '--force-rebase',
41 dest='force_rebase', action='store_true',
42 help='Pass --force-rebase to git rebase')
43 p.add_option('--no-ff',
44 dest='no_ff', action='store_true',
45 help='Pass --no-ff to git rebase')
46 p.add_option('-q', '--quiet',
47 dest='quiet', action='store_true',
48 help='Pass --quiet to git rebase')
49 p.add_option('--autosquash',
50 dest='autosquash', action='store_true',
51 help='Pass --autosquash to git rebase')
52 p.add_option('--whitespace',
53 dest='whitespace', action='store', metavar='WS',
54 help='Pass --whitespace to git rebase')
55
56 def Execute(self, opt, args):
57 all = self.GetProjects(args)
58 one_project = len(all) == 1
59
60 if opt.interactive and not one_project:
61 print >>sys.stderr, 'error: interactive rebase not supported with multiple projects'
62 return -1
63
64 for project in all:
65 cb = project.CurrentBranch
66 if not cb:
67 if one_project:
68 print >>sys.stderr, "error: project %s has a detatched HEAD" % project.relpath
69 return -1
70 # ignore branches with detatched HEADs
71 continue
72
73 upbranch = project.GetBranch(cb)
74 if not upbranch.LocalMerge:
75 if one_project:
76 print >>sys.stderr, "error: project %s does not track any remote branches" % project.relpath
77 return -1
78 # ignore branches without remotes
79 continue
80
81 args = ["rebase"]
82
83 if opt.whitespace:
84 args.append('--whitespace=%s' % opt.whitespace)
85
86 if opt.quiet:
87 args.append('--quiet')
88
89 if opt.force_rebase:
90 args.append('--force-rebase')
91
92 if opt.no_ff:
93 args.append('--no-ff')
94
95 if opt.autosquash:
96 args.append('--autosquash')
97
98 if opt.interactive:
99 args.append("-i")
100
101 args.append(upbranch.LocalMerge)
102
103 print >>sys.stderr, '# %s: rebasing %s -> %s' % \
104 (project.relpath, cb, upbranch.LocalMerge)
105
106 if GitCommand(project, args).Wait() != 0:
107 return -1
diff --git a/subcmds/smartsync.py b/subcmds/smartsync.py
new file mode 100644
index 00000000..1edbd35b
--- /dev/null
+++ b/subcmds/smartsync.py
@@ -0,0 +1,33 @@
1#
2# Copyright (C) 2010 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
16from sync import Sync
17
18class Smartsync(Sync):
19 common = True
20 helpSummary = "Update working tree to the latest known good revision"
21 helpUsage = """
22%prog [<project>...]
23"""
24 helpDescription = """
25The '%prog' command is a shortcut for sync -s.
26"""
27
28 def _Options(self, p):
29 Sync._Options(self, p, show_smart=False)
30
31 def Execute(self, opt, args):
32 opt.smart_sync = True
33 Sync.Execute(self, opt, args)
diff --git a/subcmds/sync.py b/subcmds/sync.py
index d89c2b8c..7b77388b 100644
--- a/subcmds/sync.py
+++ b/subcmds/sync.py
@@ -17,11 +17,19 @@ from optparse import SUPPRESS_HELP
17import os 17import os
18import re 18import re
19import shutil 19import shutil
20import socket
20import subprocess 21import subprocess
21import sys 22import sys
22import time 23import time
24import xmlrpclib
25
26try:
27 import threading as _threading
28except ImportError:
29 import dummy_threading as _threading
23 30
24from git_command import GIT 31from git_command import GIT
32from git_refs import R_HEADS
25from project import HEAD 33from project import HEAD
26from project import Project 34from project import Project
27from project import RemoteSpec 35from project import RemoteSpec
@@ -32,6 +40,7 @@ from project import SyncBuffer
32from progress import Progress 40from progress import Progress
33 41
34class Sync(Command, MirrorSafeCommand): 42class Sync(Command, MirrorSafeCommand):
43 jobs = 1
35 common = True 44 common = True
36 helpSummary = "Update working tree to the latest revision" 45 helpSummary = "Update working tree to the latest revision"
37 helpUsage = """ 46 helpUsage = """
@@ -57,6 +66,13 @@ back to the manifest revision. This option is especially helpful
57if the project is currently on a topic branch, but the manifest 66if the project is currently on a topic branch, but the manifest
58revision is temporarily needed. 67revision is temporarily needed.
59 68
69The -s/--smart-sync option can be used to sync to a known good
70build as specified by the manifest-server element in the current
71manifest.
72
73The -f/--force-broken option can be used to proceed with syncing
74other projects if a project sync fails.
75
60SSH Connections 76SSH Connections
61--------------- 77---------------
62 78
@@ -87,7 +103,10 @@ later is required to fix a server side protocol bug.
87 103
88""" 104"""
89 105
90 def _Options(self, p): 106 def _Options(self, p, show_smart=True):
107 p.add_option('-f', '--force-broken',
108 dest='force_broken', action='store_true',
109 help="continue sync even if a project fails to sync")
91 p.add_option('-l','--local-only', 110 p.add_option('-l','--local-only',
92 dest='local_only', action='store_true', 111 dest='local_only', action='store_true',
93 help="only update working tree, don't fetch") 112 help="only update working tree, don't fetch")
@@ -97,6 +116,16 @@ later is required to fix a server side protocol bug.
97 p.add_option('-d','--detach', 116 p.add_option('-d','--detach',
98 dest='detach_head', action='store_true', 117 dest='detach_head', action='store_true',
99 help='detach projects back to manifest revision') 118 help='detach projects back to manifest revision')
119 p.add_option('-q','--quiet',
120 dest='quiet', action='store_true',
121 help='be more quiet')
122 p.add_option('-j','--jobs',
123 dest='jobs', action='store', type='int',
124 help="number of projects to fetch simultaneously")
125 if show_smart:
126 p.add_option('-s', '--smart-sync',
127 dest='smart_sync', action='store_true',
128 help='smart sync using manifest from a known good build')
100 129
101 g = p.add_option_group('repo Version options') 130 g = p.add_option_group('repo Version options')
102 g.add_option('--no-repo-verify', 131 g.add_option('--no-repo-verify',
@@ -106,16 +135,55 @@ later is required to fix a server side protocol bug.
106 dest='repo_upgraded', action='store_true', 135 dest='repo_upgraded', action='store_true',
107 help=SUPPRESS_HELP) 136 help=SUPPRESS_HELP)
108 137
109 def _Fetch(self, projects): 138 def _FetchHelper(self, opt, project, lock, fetched, pm, sem):
139 if not project.Sync_NetworkHalf(quiet=opt.quiet):
140 print >>sys.stderr, 'error: Cannot fetch %s' % project.name
141 if opt.force_broken:
142 print >>sys.stderr, 'warn: --force-broken, continuing to sync'
143 else:
144 sem.release()
145 sys.exit(1)
146
147 lock.acquire()
148 fetched.add(project.gitdir)
149 pm.update()
150 lock.release()
151 sem.release()
152
153 def _Fetch(self, projects, opt):
110 fetched = set() 154 fetched = set()
111 pm = Progress('Fetching projects', len(projects)) 155 pm = Progress('Fetching projects', len(projects))
112 for project in projects: 156
113 pm.update() 157 if self.jobs == 1:
114 if project.Sync_NetworkHalf(): 158 for project in projects:
115 fetched.add(project.gitdir) 159 pm.update()
116 else: 160 if project.Sync_NetworkHalf(quiet=opt.quiet):
117 print >>sys.stderr, 'error: Cannot fetch %s' % project.name 161 fetched.add(project.gitdir)
118 sys.exit(1) 162 else:
163 print >>sys.stderr, 'error: Cannot fetch %s' % project.name
164 if opt.force_broken:
165 print >>sys.stderr, 'warn: --force-broken, continuing to sync'
166 else:
167 sys.exit(1)
168 else:
169 threads = set()
170 lock = _threading.Lock()
171 sem = _threading.Semaphore(self.jobs)
172 for project in projects:
173 sem.acquire()
174 t = _threading.Thread(target = self._FetchHelper,
175 args = (opt,
176 project,
177 lock,
178 fetched,
179 pm,
180 sem))
181 threads.add(t)
182 t.start()
183
184 for t in threads:
185 t.join()
186
119 pm.end() 187 pm.end()
120 for project in projects: 188 for project in projects:
121 project.bare_git.gc('--auto') 189 project.bare_git.gc('--auto')
@@ -140,32 +208,36 @@ later is required to fix a server side protocol bug.
140 if not path: 208 if not path:
141 continue 209 continue
142 if path not in new_project_paths: 210 if path not in new_project_paths:
143 project = Project( 211 """If the path has already been deleted, we don't need to do it
144 manifest = self.manifest, 212 """
145 name = path, 213 if os.path.exists(self.manifest.topdir + '/' + path):
146 remote = RemoteSpec('origin'), 214 project = Project(
147 gitdir = os.path.join(self.manifest.topdir, 215 manifest = self.manifest,
148 path, '.git'), 216 name = path,
149 worktree = os.path.join(self.manifest.topdir, path), 217 remote = RemoteSpec('origin'),
150 relpath = path, 218 gitdir = os.path.join(self.manifest.topdir,
151 revisionExpr = 'HEAD', 219 path, '.git'),
152 revisionId = None) 220 worktree = os.path.join(self.manifest.topdir, path),
153 if project.IsDirty(): 221 relpath = path,
154 print >>sys.stderr, 'error: Cannot remove project "%s": \ 222 revisionExpr = 'HEAD',
223 revisionId = None)
224
225 if project.IsDirty():
226 print >>sys.stderr, 'error: Cannot remove project "%s": \
155uncommitted changes are present' % project.relpath 227uncommitted changes are present' % project.relpath
156 print >>sys.stderr, ' commit changes, then run sync again' 228 print >>sys.stderr, ' commit changes, then run sync again'
157 return -1 229 return -1
158 else: 230 else:
159 print >>sys.stderr, 'Deleting obsolete path %s' % project.worktree 231 print >>sys.stderr, 'Deleting obsolete path %s' % project.worktree
160 shutil.rmtree(project.worktree) 232 shutil.rmtree(project.worktree)
161 # Try deleting parent subdirs if they are empty 233 # Try deleting parent subdirs if they are empty
162 dir = os.path.dirname(project.worktree) 234 dir = os.path.dirname(project.worktree)
163 while dir != self.manifest.topdir: 235 while dir != self.manifest.topdir:
164 try: 236 try:
165 os.rmdir(dir) 237 os.rmdir(dir)
166 except OSError: 238 except OSError:
167 break 239 break
168 dir = os.path.dirname(dir) 240 dir = os.path.dirname(dir)
169 241
170 new_project_paths.sort() 242 new_project_paths.sort()
171 fd = open(file_path, 'w') 243 fd = open(file_path, 'w')
@@ -177,6 +249,8 @@ uncommitted changes are present' % project.relpath
177 return 0 249 return 0
178 250
179 def Execute(self, opt, args): 251 def Execute(self, opt, args):
252 if opt.jobs:
253 self.jobs = opt.jobs
180 if opt.network_only and opt.detach_head: 254 if opt.network_only and opt.detach_head:
181 print >>sys.stderr, 'error: cannot combine -n and -d' 255 print >>sys.stderr, 'error: cannot combine -n and -d'
182 sys.exit(1) 256 sys.exit(1)
@@ -184,6 +258,51 @@ uncommitted changes are present' % project.relpath
184 print >>sys.stderr, 'error: cannot combine -n and -l' 258 print >>sys.stderr, 'error: cannot combine -n and -l'
185 sys.exit(1) 259 sys.exit(1)
186 260
261 if opt.smart_sync:
262 if not self.manifest.manifest_server:
263 print >>sys.stderr, \
264 'error: cannot smart sync: no manifest server defined in manifest'
265 sys.exit(1)
266 try:
267 server = xmlrpclib.Server(self.manifest.manifest_server)
268 p = self.manifest.manifestProject
269 b = p.GetBranch(p.CurrentBranch)
270 branch = b.merge
271 if branch.startswith(R_HEADS):
272 branch = branch[len(R_HEADS):]
273
274 env = dict(os.environ)
275 if (env.has_key('TARGET_PRODUCT') and
276 env.has_key('TARGET_BUILD_VARIANT')):
277 target = '%s-%s' % (env['TARGET_PRODUCT'],
278 env['TARGET_BUILD_VARIANT'])
279 [success, manifest_str] = server.GetApprovedManifest(branch, target)
280 else:
281 [success, manifest_str] = server.GetApprovedManifest(branch)
282
283 if success:
284 manifest_name = "smart_sync_override.xml"
285 manifest_path = os.path.join(self.manifest.manifestProject.worktree,
286 manifest_name)
287 try:
288 f = open(manifest_path, 'w')
289 try:
290 f.write(manifest_str)
291 finally:
292 f.close()
293 except IOError:
294 print >>sys.stderr, 'error: cannot write manifest to %s' % \
295 manifest_path
296 sys.exit(1)
297 self.manifest.Override(manifest_name)
298 else:
299 print >>sys.stderr, 'error: %s' % manifest_str
300 sys.exit(1)
301 except socket.error:
302 print >>sys.stderr, 'error: cannot connect to manifest server %s' % (
303 self.manifest.manifest_server)
304 sys.exit(1)
305
187 rp = self.manifest.repoProject 306 rp = self.manifest.repoProject
188 rp.PreSync() 307 rp.PreSync()
189 308
@@ -194,7 +313,7 @@ uncommitted changes are present' % project.relpath
194 _PostRepoUpgrade(self.manifest) 313 _PostRepoUpgrade(self.manifest)
195 314
196 if not opt.local_only: 315 if not opt.local_only:
197 mp.Sync_NetworkHalf() 316 mp.Sync_NetworkHalf(quiet=opt.quiet)
198 317
199 if mp.HasChanges: 318 if mp.HasChanges:
200 syncbuf = SyncBuffer(mp.config) 319 syncbuf = SyncBuffer(mp.config)
@@ -211,7 +330,7 @@ uncommitted changes are present' % project.relpath
211 to_fetch.append(rp) 330 to_fetch.append(rp)
212 to_fetch.extend(all) 331 to_fetch.extend(all)
213 332
214 fetched = self._Fetch(to_fetch) 333 fetched = self._Fetch(to_fetch, opt)
215 _PostRepoFetch(rp, opt.no_repo_verify) 334 _PostRepoFetch(rp, opt.no_repo_verify)
216 if opt.network_only: 335 if opt.network_only:
217 # bail out now; the rest touches the working tree 336 # bail out now; the rest touches the working tree
@@ -230,7 +349,7 @@ uncommitted changes are present' % project.relpath
230 for project in all: 349 for project in all:
231 if project.gitdir not in fetched: 350 if project.gitdir not in fetched:
232 missing.append(project) 351 missing.append(project)
233 self._Fetch(missing) 352 self._Fetch(missing, opt)
234 353
235 if self.manifest.IsMirror: 354 if self.manifest.IsMirror:
236 # bail out now, we have no working tree 355 # bail out now, we have no working tree
@@ -258,6 +377,9 @@ def _ReloadManifest(cmd):
258 if old.__class__ != new.__class__: 377 if old.__class__ != new.__class__:
259 print >>sys.stderr, 'NOTICE: manifest format has changed ***' 378 print >>sys.stderr, 'NOTICE: manifest format has changed ***'
260 new.Upgrade_Local(old) 379 new.Upgrade_Local(old)
380 else:
381 if new.notice:
382 print new.notice
261 383
262def _PostRepoUpgrade(manifest): 384def _PostRepoUpgrade(manifest):
263 for project in manifest.projects.values(): 385 for project in manifest.projects.values():
diff --git a/subcmds/upload.py b/subcmds/upload.py
index 2ab6a484..20822096 100644
--- a/subcmds/upload.py
+++ b/subcmds/upload.py
@@ -13,6 +13,7 @@
13# See the License for the specific language governing permissions and 13# See the License for the specific language governing permissions and
14# limitations under the License. 14# limitations under the License.
15 15
16import copy
16import re 17import re
17import sys 18import sys
18 19
@@ -20,6 +21,17 @@ from command import InteractiveCommand
20from editor import Editor 21from editor import Editor
21from error import UploadError 22from error import UploadError
22 23
24UNUSUAL_COMMIT_THRESHOLD = 5
25
26def _ConfirmManyUploads(multiple_branches=False):
27 if multiple_branches:
28 print "ATTENTION: One or more branches has an unusually high number of commits."
29 else:
30 print "ATTENTION: You are uploading an unusually high number of commits."
31 print "YOU PROBABLY DO NOT MEAN TO DO THIS. (Did you rebase across branches?)"
32 answer = raw_input("If you are sure you intend to do this, type 'yes': ").strip()
33 return answer == "yes"
34
23def _die(fmt, *args): 35def _die(fmt, *args):
24 msg = fmt % args 36 msg = fmt % args
25 print >>sys.stderr, 'error: %s' % msg 37 print >>sys.stderr, 'error: %s' % msg
@@ -35,7 +47,7 @@ class Upload(InteractiveCommand):
35 common = True 47 common = True
36 helpSummary = "Upload changes for code review" 48 helpSummary = "Upload changes for code review"
37 helpUsage=""" 49 helpUsage="""
38%prog [--re --cc] {[<project>]... | --replace <project>} 50%prog [--re --cc] [<project>]...
39""" 51"""
40 helpDescription = """ 52 helpDescription = """
41The '%prog' command is used to send changes to the Gerrit Code 53The '%prog' command is used to send changes to the Gerrit Code
@@ -55,12 +67,6 @@ added to the respective list of users, and emails are sent to any
55new users. Users passed as --reviewers must already be registered 67new users. Users passed as --reviewers must already be registered
56with the code review system, or the upload will fail. 68with the code review system, or the upload will fail.
57 69
58If the --replace option (deprecated) is passed the user can designate
59which existing change(s) in Gerrit match up to the commits in the
60branch being uploaded. For each matched pair of change,commit the
61commit will be added as a new patch set, completely replacing the
62set of files and description associated with the change in Gerrit.
63
64Configuration 70Configuration
65------------- 71-------------
66 72
@@ -72,6 +78,19 @@ to "true" then repo will assume you always answer "y" at the prompt,
72and will not prompt you further. If it is set to "false" then repo 78and will not prompt you further. If it is set to "false" then repo
73will assume you always answer "n", and will abort. 79will assume you always answer "n", and will abort.
74 80
81review.URL.autocopy:
82
83To automatically copy a user or mailing list to all uploaded reviews,
84you can set a per-project or global Git option to do so. Specifically,
85review.URL.autocopy can be set to a comma separated list of reviewers
86who you always want copied on all uploads with a non-empty --re
87argument.
88
89review.URL.username:
90
91Override the username used to connect to Gerrit Code Review.
92By default the local part of the email address is used.
93
75The URL must match the review URL listed in the manifest XML file, 94The URL must match the review URL listed in the manifest XML file,
76or in the .git/config within the project. For example: 95or in the .git/config within the project. For example:
77 96
@@ -81,6 +100,7 @@ or in the .git/config within the project. For example:
81 100
82 [review "http://review.example.com/"] 101 [review "http://review.example.com/"]
83 autoupload = true 102 autoupload = true
103 autocopy = johndoe@company.com,my-team-alias@company.com
84 104
85References 105References
86---------- 106----------
@@ -90,9 +110,9 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
90""" 110"""
91 111
92 def _Options(self, p): 112 def _Options(self, p):
93 p.add_option('--replace', 113 p.add_option('-t',
94 dest='replace', action='store_true', 114 dest='auto_topic', action='store_true',
95 help='Upload replacement patchsets from this branch (deprecated)') 115 help='Send local branch name to Gerrit Code Review')
96 p.add_option('--re', '--reviewers', 116 p.add_option('--re', '--reviewers',
97 type='string', action='append', dest='reviewers', 117 type='string', action='append', dest='reviewers',
98 help='Request reviews from these people.') 118 help='Request reviews from these people.')
@@ -100,7 +120,7 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
100 type='string', action='append', dest='cc', 120 type='string', action='append', dest='cc',
101 help='Also send email to these email addresses.') 121 help='Also send email to these email addresses.')
102 122
103 def _SingleBranch(self, branch, people): 123 def _SingleBranch(self, opt, branch, people):
104 project = branch.project 124 project = branch.project
105 name = branch.name 125 name = branch.name
106 remote = project.GetBranch(name).remote 126 remote = project.GetBranch(name).remote
@@ -129,11 +149,15 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
129 answer = answer in ('y', 'Y', 'yes', '1', 'true', 't') 149 answer = answer in ('y', 'Y', 'yes', '1', 'true', 't')
130 150
131 if answer: 151 if answer:
132 self._UploadAndReport([branch], people) 152 if len(branch.commits) > UNUSUAL_COMMIT_THRESHOLD:
153 answer = _ConfirmManyUploads()
154
155 if answer:
156 self._UploadAndReport(opt, [branch], people)
133 else: 157 else:
134 _die("upload aborted by user") 158 _die("upload aborted by user")
135 159
136 def _MultipleBranches(self, pending, people): 160 def _MultipleBranches(self, opt, pending, people):
137 projects = {} 161 projects = {}
138 branches = {} 162 branches = {}
139 163
@@ -192,7 +216,30 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
192 todo.append(branch) 216 todo.append(branch)
193 if not todo: 217 if not todo:
194 _die("nothing uncommented for upload") 218 _die("nothing uncommented for upload")
195 self._UploadAndReport(todo, people) 219
220 many_commits = False
221 for branch in todo:
222 if len(branch.commits) > UNUSUAL_COMMIT_THRESHOLD:
223 many_commits = True
224 break
225 if many_commits:
226 if not _ConfirmManyUploads(multiple_branches=True):
227 _die("upload aborted by user")
228
229 self._UploadAndReport(opt, todo, people)
230
231 def _AppendAutoCcList(self, branch, people):
232 """
233 Appends the list of users in the CC list in the git project's config if a
234 non-empty reviewer list was found.
235 """
236
237 name = branch.name
238 project = branch.project
239 key = 'review.%s.autocopy' % project.GetBranch(name).remote.review
240 raw_list = project.config.GetString(key)
241 if not raw_list is None and len(people[0]) > 0:
242 people[1].extend([entry.strip() for entry in raw_list.split(',')])
196 243
197 def _FindGerritChange(self, branch): 244 def _FindGerritChange(self, branch):
198 last_pub = branch.project.WasPublished(branch.name) 245 last_pub = branch.project.WasPublished(branch.name)
@@ -206,66 +253,29 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
206 except: 253 except:
207 return "" 254 return ""
208 255
209 def _ReplaceBranch(self, project, people): 256 def _UploadAndReport(self, opt, todo, original_people):
210 branch = project.CurrentBranch
211 if not branch:
212 print >>sys.stdout, "no branches ready for upload"
213 return
214 branch = project.GetUploadableBranch(branch)
215 if not branch:
216 print >>sys.stdout, "no branches ready for upload"
217 return
218
219 script = []
220 script.append('# Replacing from branch %s' % branch.name)
221
222 if len(branch.commits) == 1:
223 change = self._FindGerritChange(branch)
224 script.append('[%-6s] %s' % (change, branch.commits[0]))
225 else:
226 for commit in branch.commits:
227 script.append('[ ] %s' % commit)
228
229 script.append('')
230 script.append('# Insert change numbers in the brackets to add a new patch set.')
231 script.append('# To create a new change record, leave the brackets empty.')
232
233 script = Editor.EditString("\n".join(script)).split("\n")
234
235 change_re = re.compile(r'^\[\s*(\d{1,})\s*\]\s*([0-9a-f]{1,}) .*$')
236 to_replace = dict()
237 full_hashes = branch.unabbrev_commits
238
239 for line in script:
240 m = change_re.match(line)
241 if m:
242 c = m.group(1)
243 f = m.group(2)
244 try:
245 f = full_hashes[f]
246 except KeyError:
247 print 'fh = %s' % full_hashes
248 print >>sys.stderr, "error: commit %s not found" % f
249 sys.exit(1)
250 if c in to_replace:
251 print >>sys.stderr,\
252 "error: change %s cannot accept multiple commits" % c
253 sys.exit(1)
254 to_replace[c] = f
255
256 if not to_replace:
257 print >>sys.stderr, "error: no replacements specified"
258 print >>sys.stderr, " use 'repo upload' without --replace"
259 sys.exit(1)
260
261 branch.replace_changes = to_replace
262 self._UploadAndReport([branch], people)
263
264 def _UploadAndReport(self, todo, people):
265 have_errors = False 257 have_errors = False
266 for branch in todo: 258 for branch in todo:
267 try: 259 try:
268 branch.UploadForReview(people) 260 people = copy.deepcopy(original_people)
261 self._AppendAutoCcList(branch, people)
262
263 # Check if there are local changes that may have been forgotten
264 if branch.project.HasChanges():
265 key = 'review.%s.autoupload' % branch.project.remote.review
266 answer = branch.project.config.GetBoolean(key)
267
268 # if they want to auto upload, let's not ask because it could be automated
269 if answer is None:
270 sys.stdout.write('Uncommitted changes in ' + branch.project.name + ' (did you forget to amend?). Continue uploading? (y/n) ')
271 a = sys.stdin.readline().strip().lower()
272 if a not in ('y', 'yes', 't', 'true', 'on'):
273 print >>sys.stderr, "skipping upload"
274 branch.uploaded = False
275 branch.error = 'User aborted'
276 continue
277
278 branch.UploadForReview(people, auto_topic=opt.auto_topic)
269 branch.uploaded = True 279 branch.uploaded = True
270 except UploadError, e: 280 except UploadError, e:
271 branch.error = e 281 branch.error = e
@@ -309,14 +319,6 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
309 cc = _SplitEmails(opt.cc) 319 cc = _SplitEmails(opt.cc)
310 people = (reviewers,cc) 320 people = (reviewers,cc)
311 321
312 if opt.replace:
313 if len(project_list) != 1:
314 print >>sys.stderr, \
315 'error: --replace requires exactly one project'
316 sys.exit(1)
317 self._ReplaceBranch(project_list[0], people)
318 return
319
320 for project in project_list: 322 for project in project_list:
321 avail = project.GetUploadableBranches() 323 avail = project.GetUploadableBranches()
322 if avail: 324 if avail:
@@ -325,6 +327,6 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
325 if not pending: 327 if not pending:
326 print >>sys.stdout, "no branches ready for upload" 328 print >>sys.stdout, "no branches ready for upload"
327 elif len(pending) == 1 and len(pending[0][1]) == 1: 329 elif len(pending) == 1 and len(pending[0][1]) == 1:
328 self._SingleBranch(pending[0][1][0], people) 330 self._SingleBranch(opt, pending[0][1][0], people)
329 else: 331 else:
330 self._MultipleBranches(pending, people) 332 self._MultipleBranches(opt, pending, people)