diff options
Diffstat (limited to 'subcmds/sync.py')
-rw-r--r-- | subcmds/sync.py | 110 |
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') |