summaryrefslogtreecommitdiffstats
path: root/subcmds/sync.py
diff options
context:
space:
mode:
Diffstat (limited to 'subcmds/sync.py')
-rw-r--r--subcmds/sync.py110
1 files changed, 82 insertions, 28 deletions
diff --git a/subcmds/sync.py b/subcmds/sync.py
index 9124a653..bbb166c0 100644
--- a/subcmds/sync.py
+++ b/subcmds/sync.py
@@ -255,7 +255,7 @@ later is required to fix a server side protocol bug.
255 dest='repo_upgraded', action='store_true', 255 dest='repo_upgraded', action='store_true',
256 help=SUPPRESS_HELP) 256 help=SUPPRESS_HELP)
257 257
258 def _FetchProjectList(self, opt, projects, *args, **kwargs): 258 def _FetchProjectList(self, opt, projects, sem, *args, **kwargs):
259 """Main function of the fetch threads when jobs are > 1. 259 """Main function of the fetch threads when jobs are > 1.
260 260
261 Delegates most of the work to _FetchHelper. 261 Delegates most of the work to _FetchHelper.
@@ -263,15 +263,20 @@ later is required to fix a server side protocol bug.
263 Args: 263 Args:
264 opt: Program options returned from optparse. See _Options(). 264 opt: Program options returned from optparse. See _Options().
265 projects: Projects to fetch. 265 projects: Projects to fetch.
266 sem: We'll release() this semaphore when we exit so that another thread
267 can be started up.
266 *args, **kwargs: Remaining arguments to pass to _FetchHelper. See the 268 *args, **kwargs: Remaining arguments to pass to _FetchHelper. See the
267 _FetchHelper docstring for details. 269 _FetchHelper docstring for details.
268 """ 270 """
269 for project in projects: 271 try:
270 success = self._FetchHelper(opt, project, *args, **kwargs) 272 for project in projects:
271 if not success and not opt.force_broken: 273 success = self._FetchHelper(opt, project, *args, **kwargs)
272 break 274 if not success and not opt.force_broken:
275 break
276 finally:
277 sem.release()
273 278
274 def _FetchHelper(self, opt, project, lock, fetched, pm, sem, err_event): 279 def _FetchHelper(self, opt, project, lock, fetched, pm, err_event):
275 """Fetch git objects for a single project. 280 """Fetch git objects for a single project.
276 281
277 Args: 282 Args:
@@ -283,8 +288,6 @@ later is required to fix a server side protocol bug.
283 (with our lock held). 288 (with our lock held).
284 pm: Instance of a Project object. We will call pm.update() (with our 289 pm: Instance of a Project object. We will call pm.update() (with our
285 lock held). 290 lock held).
286 sem: We'll release() this semaphore when we exit so that another thread
287 can be started up.
288 err_event: We'll set this event in the case of an error (after printing 291 err_event: We'll set this event in the case of an error (after printing
289 out info about the error). 292 out info about the error).
290 293
@@ -340,7 +343,6 @@ later is required to fix a server side protocol bug.
340 finally: 343 finally:
341 if did_lock: 344 if did_lock:
342 lock.release() 345 lock.release()
343 sem.release()
344 346
345 return success 347 return success
346 348
@@ -365,10 +367,10 @@ later is required to fix a server side protocol bug.
365 sem.acquire() 367 sem.acquire()
366 kwargs = dict(opt=opt, 368 kwargs = dict(opt=opt,
367 projects=project_list, 369 projects=project_list,
370 sem=sem,
368 lock=lock, 371 lock=lock,
369 fetched=fetched, 372 fetched=fetched,
370 pm=pm, 373 pm=pm,
371 sem=sem,
372 err_event=err_event) 374 err_event=err_event)
373 if self.jobs > 1: 375 if self.jobs > 1:
374 t = _threading.Thread(target = self._FetchProjectList, 376 t = _threading.Thread(target = self._FetchProjectList,
@@ -397,9 +399,12 @@ later is required to fix a server side protocol bug.
397 return fetched 399 return fetched
398 400
399 def _GCProjects(self, projects): 401 def _GCProjects(self, projects):
400 gitdirs = {} 402 gc_gitdirs = {}
401 for project in projects: 403 for project in projects:
402 gitdirs[project.gitdir] = project.bare_git 404 if len(project.manifest.GetProjectsWithName(project.name)) > 1:
405 print('Shared project %s found, disabling pruning.' % project.name)
406 project.bare_git.config('--replace-all', 'gc.pruneExpire', 'never')
407 gc_gitdirs[project.gitdir] = project.bare_git
403 408
404 has_dash_c = git_require((1, 7, 2)) 409 has_dash_c = git_require((1, 7, 2))
405 if multiprocessing and has_dash_c: 410 if multiprocessing and has_dash_c:
@@ -409,7 +414,7 @@ later is required to fix a server side protocol bug.
409 jobs = min(self.jobs, cpu_count) 414 jobs = min(self.jobs, cpu_count)
410 415
411 if jobs < 2: 416 if jobs < 2:
412 for bare_git in gitdirs.values(): 417 for bare_git in gc_gitdirs.values():
413 bare_git.gc('--auto') 418 bare_git.gc('--auto')
414 return 419 return
415 420
@@ -431,7 +436,7 @@ later is required to fix a server side protocol bug.
431 finally: 436 finally:
432 sem.release() 437 sem.release()
433 438
434 for bare_git in gitdirs.values(): 439 for bare_git in gc_gitdirs.values():
435 if err_event.isSet(): 440 if err_event.isSet():
436 break 441 break
437 sem.acquire() 442 sem.acquire()
@@ -454,6 +459,65 @@ later is required to fix a server side protocol bug.
454 else: 459 else:
455 self.manifest._Unload() 460 self.manifest._Unload()
456 461
462 def _DeleteProject(self, path):
463 print('Deleting obsolete path %s' % path, file=sys.stderr)
464
465 # Delete the .git directory first, so we're less likely to have a partially
466 # working git repository around. There shouldn't be any git projects here,
467 # so rmtree works.
468 try:
469 shutil.rmtree(os.path.join(path, '.git'))
470 except OSError:
471 print('Failed to remove %s' % os.path.join(path, '.git'), file=sys.stderr)
472 print('error: Failed to delete obsolete path %s' % path, file=sys.stderr)
473 print(' remove manually, then run sync again', file=sys.stderr)
474 return -1
475
476 # Delete everything under the worktree, except for directories that contain
477 # another git project
478 dirs_to_remove = []
479 failed = False
480 for root, dirs, files in os.walk(path):
481 for f in files:
482 try:
483 os.remove(os.path.join(root, f))
484 except OSError:
485 print('Failed to remove %s' % os.path.join(root, f), file=sys.stderr)
486 failed = True
487 dirs[:] = [d for d in dirs
488 if not os.path.lexists(os.path.join(root, d, '.git'))]
489 dirs_to_remove += [os.path.join(root, d) for d in dirs
490 if os.path.join(root, d) not in dirs_to_remove]
491 for d in reversed(dirs_to_remove):
492 if os.path.islink(d):
493 try:
494 os.remove(d)
495 except OSError:
496 print('Failed to remove %s' % os.path.join(root, d), file=sys.stderr)
497 failed = True
498 elif len(os.listdir(d)) == 0:
499 try:
500 os.rmdir(d)
501 except OSError:
502 print('Failed to remove %s' % os.path.join(root, d), file=sys.stderr)
503 failed = True
504 continue
505 if failed:
506 print('error: Failed to delete obsolete path %s' % path, file=sys.stderr)
507 print(' remove manually, then run sync again', file=sys.stderr)
508 return -1
509
510 # Try deleting parent dirs if they are empty
511 project_dir = path
512 while project_dir != self.manifest.topdir:
513 if len(os.listdir(project_dir)) == 0:
514 os.rmdir(project_dir)
515 else:
516 break
517 project_dir = os.path.dirname(project_dir)
518
519 return 0
520
457 def UpdateProjectList(self): 521 def UpdateProjectList(self):
458 new_project_paths = [] 522 new_project_paths = []
459 for project in self.GetProjects(None, missing_ok=True): 523 for project in self.GetProjects(None, missing_ok=True):
@@ -474,8 +538,8 @@ later is required to fix a server side protocol bug.
474 continue 538 continue
475 if path not in new_project_paths: 539 if path not in new_project_paths:
476 # If the path has already been deleted, we don't need to do it 540 # If the path has already been deleted, we don't need to do it
477 if os.path.exists(self.manifest.topdir + '/' + path): 541 gitdir = os.path.join(self.manifest.topdir, path, '.git')
478 gitdir = os.path.join(self.manifest.topdir, path, '.git') 542 if os.path.exists(gitdir):
479 project = Project( 543 project = Project(
480 manifest = self.manifest, 544 manifest = self.manifest,
481 name = path, 545 name = path,
@@ -494,18 +558,8 @@ later is required to fix a server side protocol bug.
494 print(' commit changes, then run sync again', 558 print(' commit changes, then run sync again',
495 file=sys.stderr) 559 file=sys.stderr)
496 return -1 560 return -1
497 else: 561 elif self._DeleteProject(project.worktree):
498 print('Deleting obsolete path %s' % project.worktree, 562 return -1
499 file=sys.stderr)
500 shutil.rmtree(project.worktree)
501 # Try deleting parent subdirs if they are empty
502 project_dir = os.path.dirname(project.worktree)
503 while project_dir != self.manifest.topdir:
504 try:
505 os.rmdir(project_dir)
506 except OSError:
507 break
508 project_dir = os.path.dirname(project_dir)
509 563
510 new_project_paths.sort() 564 new_project_paths.sort()
511 fd = open(file_path, 'w') 565 fd = open(file_path, 'w')