summaryrefslogtreecommitdiffstats
path: root/subcmds/sync.py
diff options
context:
space:
mode:
Diffstat (limited to 'subcmds/sync.py')
-rw-r--r--subcmds/sync.py200
1 files changed, 148 insertions, 52 deletions
diff --git a/subcmds/sync.py b/subcmds/sync.py
index 16f1d189..bfe146b6 100644
--- a/subcmds/sync.py
+++ b/subcmds/sync.py
@@ -28,6 +28,14 @@ try:
28except ImportError: 28except ImportError:
29 import dummy_threading as _threading 29 import dummy_threading as _threading
30 30
31try:
32 import resource
33 def _rlimit_nofile():
34 return resource.getrlimit(resource.RLIMIT_NOFILE)
35except ImportError:
36 def _rlimit_nofile():
37 return (256, 256)
38
31from git_command import GIT 39from git_command import GIT
32from git_refs import R_HEADS 40from git_refs import R_HEADS
33from project import HEAD 41from project import HEAD
@@ -39,6 +47,10 @@ from project import R_HEADS
39from project import SyncBuffer 47from project import SyncBuffer
40from progress import Progress 48from progress import Progress
41 49
50class _FetchError(Exception):
51 """Internal error thrown in _FetchHelper() when we don't want stack trace."""
52 pass
53
42class Sync(Command, MirrorSafeCommand): 54class Sync(Command, MirrorSafeCommand):
43 jobs = 1 55 jobs = 1
44 common = True 56 common = True
@@ -68,11 +80,18 @@ revision is temporarily needed.
68 80
69The -s/--smart-sync option can be used to sync to a known good 81The -s/--smart-sync option can be used to sync to a known good
70build as specified by the manifest-server element in the current 82build as specified by the manifest-server element in the current
71manifest. 83manifest. The -t/--smart-tag option is similar and allows you to
84specify a custom tag/label.
72 85
73The -f/--force-broken option can be used to proceed with syncing 86The -f/--force-broken option can be used to proceed with syncing
74other projects if a project sync fails. 87other projects if a project sync fails.
75 88
89The --no-clone-bundle option disables any attempt to use
90$URL/clone.bundle to bootstrap a new Git repository from a
91resumeable bundle file on a content delivery network. This
92may be necessary if there are problems with the local Python
93HTTP client or proxy configuration, but the Git binary works.
94
76SSH Connections 95SSH Connections
77--------------- 96---------------
78 97
@@ -104,6 +123,8 @@ later is required to fix a server side protocol bug.
104""" 123"""
105 124
106 def _Options(self, p, show_smart=True): 125 def _Options(self, p, show_smart=True):
126 self.jobs = self.manifest.default.sync_j
127
107 p.add_option('-f', '--force-broken', 128 p.add_option('-f', '--force-broken',
108 dest='force_broken', action='store_true', 129 dest='force_broken', action='store_true',
109 help="continue sync even if a project fails to sync") 130 help="continue sync even if a project fails to sync")
@@ -116,16 +137,28 @@ later is required to fix a server side protocol bug.
116 p.add_option('-d','--detach', 137 p.add_option('-d','--detach',
117 dest='detach_head', action='store_true', 138 dest='detach_head', action='store_true',
118 help='detach projects back to manifest revision') 139 help='detach projects back to manifest revision')
140 p.add_option('-c','--current-branch',
141 dest='current_branch_only', action='store_true',
142 help='fetch only current branch from server')
119 p.add_option('-q','--quiet', 143 p.add_option('-q','--quiet',
120 dest='quiet', action='store_true', 144 dest='quiet', action='store_true',
121 help='be more quiet') 145 help='be more quiet')
122 p.add_option('-j','--jobs', 146 p.add_option('-j','--jobs',
123 dest='jobs', action='store', type='int', 147 dest='jobs', action='store', type='int',
124 help="number of projects to fetch simultaneously") 148 help="projects to fetch simultaneously (default %d)" % self.jobs)
149 p.add_option('-m', '--manifest-name',
150 dest='manifest_name',
151 help='temporary manifest to use for this sync', metavar='NAME.xml')
152 p.add_option('--no-clone-bundle',
153 dest='no_clone_bundle', action='store_true',
154 help='disable use of /clone.bundle on HTTP/HTTPS')
125 if show_smart: 155 if show_smart:
126 p.add_option('-s', '--smart-sync', 156 p.add_option('-s', '--smart-sync',
127 dest='smart_sync', action='store_true', 157 dest='smart_sync', action='store_true',
128 help='smart sync using manifest from a known good build') 158 help='smart sync using manifest from a known good build')
159 p.add_option('-t', '--smart-tag',
160 dest='smart_tag', action='store',
161 help='smart sync using manifest from a known tag')
129 162
130 g = p.add_option_group('repo Version options') 163 g = p.add_option_group('repo Version options')
131 g.add_option('--no-repo-verify', 164 g.add_option('--no-repo-verify',
@@ -135,20 +168,60 @@ later is required to fix a server side protocol bug.
135 dest='repo_upgraded', action='store_true', 168 dest='repo_upgraded', action='store_true',
136 help=SUPPRESS_HELP) 169 help=SUPPRESS_HELP)
137 170
138 def _FetchHelper(self, opt, project, lock, fetched, pm, sem): 171 def _FetchHelper(self, opt, project, lock, fetched, pm, sem, err_event):
139 if not project.Sync_NetworkHalf(quiet=opt.quiet): 172 """Main function of the fetch threads when jobs are > 1.
140 print >>sys.stderr, 'error: Cannot fetch %s' % project.name 173
141 if opt.force_broken: 174 Args:
142 print >>sys.stderr, 'warn: --force-broken, continuing to sync' 175 opt: Program options returned from optparse. See _Options().
143 else: 176 project: Project object for the project to fetch.
144 sem.release() 177 lock: Lock for accessing objects that are shared amongst multiple
145 sys.exit(1) 178 _FetchHelper() threads.
179 fetched: set object that we will add project.gitdir to when we're done
180 (with our lock held).
181 pm: Instance of a Project object. We will call pm.update() (with our
182 lock held).
183 sem: We'll release() this semaphore when we exit so that another thread
184 can be started up.
185 err_event: We'll set this event in the case of an error (after printing
186 out info about the error).
187 """
188 # We'll set to true once we've locked the lock.
189 did_lock = False
190
191 # Encapsulate everything in a try/except/finally so that:
192 # - We always set err_event in the case of an exception.
193 # - We always make sure we call sem.release().
194 # - We always make sure we unlock the lock if we locked it.
195 try:
196 try:
197 success = project.Sync_NetworkHalf(
198 quiet=opt.quiet,
199 current_branch_only=opt.current_branch_only,
200 clone_bundle=not opt.no_clone_bundle)
201
202 # Lock around all the rest of the code, since printing, updating a set
203 # and Progress.update() are not thread safe.
204 lock.acquire()
205 did_lock = True
206
207 if not success:
208 print >>sys.stderr, 'error: Cannot fetch %s' % project.name
209 if opt.force_broken:
210 print >>sys.stderr, 'warn: --force-broken, continuing to sync'
211 else:
212 raise _FetchError()
146 213
147 lock.acquire() 214 fetched.add(project.gitdir)
148 fetched.add(project.gitdir) 215 pm.update()
149 pm.update() 216 except _FetchError:
150 lock.release() 217 err_event.set()
151 sem.release() 218 except:
219 err_event.set()
220 raise
221 finally:
222 if did_lock:
223 lock.release()
224 sem.release()
152 225
153 def _Fetch(self, projects, opt): 226 def _Fetch(self, projects, opt):
154 fetched = set() 227 fetched = set()
@@ -157,7 +230,10 @@ later is required to fix a server side protocol bug.
157 if self.jobs == 1: 230 if self.jobs == 1:
158 for project in projects: 231 for project in projects:
159 pm.update() 232 pm.update()
160 if project.Sync_NetworkHalf(quiet=opt.quiet): 233 if project.Sync_NetworkHalf(
234 quiet=opt.quiet,
235 current_branch_only=opt.current_branch_only,
236 clone_bundle=not opt.no_clone_bundle):
161 fetched.add(project.gitdir) 237 fetched.add(project.gitdir)
162 else: 238 else:
163 print >>sys.stderr, 'error: Cannot fetch %s' % project.name 239 print >>sys.stderr, 'error: Cannot fetch %s' % project.name
@@ -169,7 +245,13 @@ later is required to fix a server side protocol bug.
169 threads = set() 245 threads = set()
170 lock = _threading.Lock() 246 lock = _threading.Lock()
171 sem = _threading.Semaphore(self.jobs) 247 sem = _threading.Semaphore(self.jobs)
248 err_event = _threading.Event()
172 for project in projects: 249 for project in projects:
250 # Check for any errors before starting any new threads.
251 # ...we'll let existing threads finish, though.
252 if err_event.isSet():
253 break
254
173 sem.acquire() 255 sem.acquire()
174 t = _threading.Thread(target = self._FetchHelper, 256 t = _threading.Thread(target = self._FetchHelper,
175 args = (opt, 257 args = (opt,
@@ -177,13 +259,19 @@ later is required to fix a server side protocol bug.
177 lock, 259 lock,
178 fetched, 260 fetched,
179 pm, 261 pm,
180 sem)) 262 sem,
263 err_event))
181 threads.add(t) 264 threads.add(t)
182 t.start() 265 t.start()
183 266
184 for t in threads: 267 for t in threads:
185 t.join() 268 t.join()
186 269
270 # If we saw an error, exit with code 1 so that other scripts can check.
271 if err_event.isSet():
272 print >>sys.stderr, '\nerror: Exited sync due to fetch errors'
273 sys.exit(1)
274
187 pm.end() 275 pm.end()
188 for project in projects: 276 for project in projects:
189 project.bare_git.gc('--auto') 277 project.bare_git.gc('--auto')
@@ -191,7 +279,7 @@ later is required to fix a server side protocol bug.
191 279
192 def UpdateProjectList(self): 280 def UpdateProjectList(self):
193 new_project_paths = [] 281 new_project_paths = []
194 for project in self.manifest.projects.values(): 282 for project in self.GetProjects(None, missing_ok=True):
195 if project.relpath: 283 if project.relpath:
196 new_project_paths.append(project.relpath) 284 new_project_paths.append(project.relpath)
197 file_name = 'project.list' 285 file_name = 'project.list'
@@ -220,7 +308,8 @@ later is required to fix a server side protocol bug.
220 worktree = os.path.join(self.manifest.topdir, path), 308 worktree = os.path.join(self.manifest.topdir, path),
221 relpath = path, 309 relpath = path,
222 revisionExpr = 'HEAD', 310 revisionExpr = 'HEAD',
223 revisionId = None) 311 revisionId = None,
312 groups = None)
224 313
225 if project.IsDirty(): 314 if project.IsDirty():
226 print >>sys.stderr, 'error: Cannot remove project "%s": \ 315 print >>sys.stderr, 'error: Cannot remove project "%s": \
@@ -251,34 +340,51 @@ uncommitted changes are present' % project.relpath
251 def Execute(self, opt, args): 340 def Execute(self, opt, args):
252 if opt.jobs: 341 if opt.jobs:
253 self.jobs = opt.jobs 342 self.jobs = opt.jobs
343 if self.jobs > 1:
344 soft_limit, _ = _rlimit_nofile()
345 self.jobs = min(self.jobs, (soft_limit - 5) / 3)
346
254 if opt.network_only and opt.detach_head: 347 if opt.network_only and opt.detach_head:
255 print >>sys.stderr, 'error: cannot combine -n and -d' 348 print >>sys.stderr, 'error: cannot combine -n and -d'
256 sys.exit(1) 349 sys.exit(1)
257 if opt.network_only and opt.local_only: 350 if opt.network_only and opt.local_only:
258 print >>sys.stderr, 'error: cannot combine -n and -l' 351 print >>sys.stderr, 'error: cannot combine -n and -l'
259 sys.exit(1) 352 sys.exit(1)
353 if opt.manifest_name and opt.smart_sync:
354 print >>sys.stderr, 'error: cannot combine -m and -s'
355 sys.exit(1)
356 if opt.manifest_name and opt.smart_tag:
357 print >>sys.stderr, 'error: cannot combine -m and -t'
358 sys.exit(1)
260 359
261 if opt.smart_sync: 360 if opt.manifest_name:
361 self.manifest.Override(opt.manifest_name)
362
363 if opt.smart_sync or opt.smart_tag:
262 if not self.manifest.manifest_server: 364 if not self.manifest.manifest_server:
263 print >>sys.stderr, \ 365 print >>sys.stderr, \
264 'error: cannot smart sync: no manifest server defined in manifest' 366 'error: cannot smart sync: no manifest server defined in manifest'
265 sys.exit(1) 367 sys.exit(1)
266 try: 368 try:
267 server = xmlrpclib.Server(self.manifest.manifest_server) 369 server = xmlrpclib.Server(self.manifest.manifest_server)
268 p = self.manifest.manifestProject 370 if opt.smart_sync:
269 b = p.GetBranch(p.CurrentBranch) 371 p = self.manifest.manifestProject
270 branch = b.merge 372 b = p.GetBranch(p.CurrentBranch)
271 if branch.startswith(R_HEADS): 373 branch = b.merge
272 branch = branch[len(R_HEADS):] 374 if branch.startswith(R_HEADS):
273 375 branch = branch[len(R_HEADS):]
274 env = os.environ.copy() 376
275 if (env.has_key('TARGET_PRODUCT') and 377 env = os.environ.copy()
276 env.has_key('TARGET_BUILD_VARIANT')): 378 if (env.has_key('TARGET_PRODUCT') and
277 target = '%s-%s' % (env['TARGET_PRODUCT'], 379 env.has_key('TARGET_BUILD_VARIANT')):
278 env['TARGET_BUILD_VARIANT']) 380 target = '%s-%s' % (env['TARGET_PRODUCT'],
279 [success, manifest_str] = server.GetApprovedManifest(branch, target) 381 env['TARGET_BUILD_VARIANT'])
382 [success, manifest_str] = server.GetApprovedManifest(branch, target)
383 else:
384 [success, manifest_str] = server.GetApprovedManifest(branch)
280 else: 385 else:
281 [success, manifest_str] = server.GetApprovedManifest(branch) 386 assert(opt.smart_tag)
387 [success, manifest_str] = server.GetManifest(opt.smart_tag)
282 388
283 if success: 389 if success:
284 manifest_name = "smart_sync_override.xml" 390 manifest_name = "smart_sync_override.xml"
@@ -313,7 +419,8 @@ uncommitted changes are present' % project.relpath
313 _PostRepoUpgrade(self.manifest) 419 _PostRepoUpgrade(self.manifest)
314 420
315 if not opt.local_only: 421 if not opt.local_only:
316 mp.Sync_NetworkHalf(quiet=opt.quiet) 422 mp.Sync_NetworkHalf(quiet=opt.quiet,
423 current_branch_only=opt.current_branch_only)
317 424
318 if mp.HasChanges: 425 if mp.HasChanges:
319 syncbuf = SyncBuffer(mp.config) 426 syncbuf = SyncBuffer(mp.config)
@@ -321,6 +428,8 @@ uncommitted changes are present' % project.relpath
321 if not syncbuf.Finish(): 428 if not syncbuf.Finish():
322 sys.exit(1) 429 sys.exit(1)
323 self.manifest._Unload() 430 self.manifest._Unload()
431 if opt.jobs is None:
432 self.jobs = self.manifest.default.sync_j
324 all = self.GetProjects(args, missing_ok=True) 433 all = self.GetProjects(args, missing_ok=True)
325 434
326 if not opt.local_only: 435 if not opt.local_only:
@@ -336,14 +445,7 @@ uncommitted changes are present' % project.relpath
336 # bail out now; the rest touches the working tree 445 # bail out now; the rest touches the working tree
337 return 446 return
338 447
339 if mp.HasChanges: 448 self.manifest._Unload()
340 syncbuf = SyncBuffer(mp.config)
341 mp.Sync_LocalHalf(syncbuf)
342 if not syncbuf.Finish():
343 sys.exit(1)
344 _ReloadManifest(self)
345 mp = self.manifest.manifestProject
346
347 all = self.GetProjects(args, missing_ok=True) 449 all = self.GetProjects(args, missing_ok=True)
348 missing = [] 450 missing = []
349 for project in all: 451 for project in all:
@@ -370,16 +472,10 @@ uncommitted changes are present' % project.relpath
370 if not syncbuf.Finish(): 472 if not syncbuf.Finish():
371 sys.exit(1) 473 sys.exit(1)
372 474
373def _ReloadManifest(cmd): 475 # If there's a notice that's supposed to print at the end of the sync, print
374 old = cmd.manifest 476 # it now...
375 new = cmd.GetManifest(reparse=True) 477 if self.manifest.notice:
376 478 print self.manifest.notice
377 if old.__class__ != new.__class__:
378 print >>sys.stderr, 'NOTICE: manifest format has changed ***'
379 new.Upgrade_Local(old)
380 else:
381 if new.notice:
382 print new.notice
383 479
384def _PostRepoUpgrade(manifest): 480def _PostRepoUpgrade(manifest):
385 for project in manifest.projects.values(): 481 for project in manifest.projects.values():