From 979d5bdc3ebe45998a76dbbaff46c33d4e59683b Mon Sep 17 00:00:00 2001 From: Mike Frysinger Date: Sun, 9 Feb 2020 02:28:34 -0500 Subject: add experimental git worktree support This provides initial support for using git worktrees internally instead of our own ad-hoc symlink tree. It's been lightly tested which is why it's not currently exposed via --help. When people opt-in to worktrees in an existing repo client checkout, no projects are migrated. Instead, only new projects will use the worktree method. This allows for limited testing/opting in without having to completely blow things away or get a second checkout. Bug: https://crbug.com/gerrit/11486 Change-Id: Ic3ff891b30940a6ba497b406b2a387e0a8517ed8 Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254075 Tested-by: Mike Frysinger Reviewed-by: Mike Frysinger --- subcmds/init.py | 24 ++++++++++++++++++++++++ subcmds/sync.py | 24 ++++++++++++++++++------ 2 files changed, 42 insertions(+), 6 deletions(-) (limited to 'subcmds') diff --git a/subcmds/init.py b/subcmds/init.py index 3c68c2c3..8a29321e 100644 --- a/subcmds/init.py +++ b/subcmds/init.py @@ -15,6 +15,8 @@ # limitations under the License. from __future__ import print_function + +import optparse import os import platform import re @@ -128,6 +130,10 @@ to update the working directory files. g.add_option('--clone-filter', action='store', default='blob:none', dest='clone_filter', help='filter for use with --partial-clone [default: %default]') + # TODO(vapier): Expose option with real help text once this has been in the + # wild for a while w/out significant bug reports. Goal is by ~Sep 2020. + g.add_option('--worktree', action='store_true', + help=optparse.SUPPRESS_HELP) g.add_option('--archive', dest='archive', action='store_true', help='checkout an archive instead of a git repository for ' @@ -246,6 +252,20 @@ to update the working directory files. if opt.dissociate: m.config.SetString('repo.dissociate', 'true') + if opt.worktree: + if opt.mirror: + print('fatal: --mirror and --worktree are incompatible', + file=sys.stderr) + sys.exit(1) + if opt.submodules: + print('fatal: --submodules and --worktree are incompatible', + file=sys.stderr) + sys.exit(1) + m.config.SetString('repo.worktree', 'true') + if is_new: + m.use_git_worktrees = True + print('warning: --worktree is experimental!', file=sys.stderr) + if opt.archive: if is_new: m.config.SetString('repo.archive', 'true') @@ -459,6 +479,10 @@ to update the working directory files. % ('.'.join(str(x) for x in MIN_GIT_VERSION_SOFT),), file=sys.stderr) + if opt.worktree: + # Older versions of git supported worktree, but had dangerous gc bugs. + git_require((2, 15, 0), fail=True, msg='git gc worktree corruption') + self._SyncManifest(opt) self._LinkManifest(opt.manifest_name) diff --git a/subcmds/sync.py b/subcmds/sync.py index 0ac308e6..49867a97 100644 --- a/subcmds/sync.py +++ b/subcmds/sync.py @@ -15,6 +15,8 @@ # limitations under the License. from __future__ import print_function + +import errno import json import netrc from optparse import SUPPRESS_HELP @@ -569,7 +571,8 @@ later is required to fix a server side protocol bug. gc_gitdirs = {} for project in projects: # Make sure pruning never kicks in with shared projects. - if len(project.manifest.GetProjectsWithName(project.name)) > 1: + if (not project.use_git_worktrees and + len(project.manifest.GetProjectsWithName(project.name)) > 1): print('%s: Shared project %s found, disabling pruning.' % (project.relpath, project.name)) if git_require((2, 7, 0)): @@ -637,13 +640,22 @@ later is required to fix a server side protocol bug. # Delete the .git directory first, so we're less likely to have a partially # working git repository around. There shouldn't be any git projects here, # so rmtree works. + dotgit = os.path.join(path, '.git') + # Try to remove plain files first in case of git worktrees. If this fails + # for any reason, we'll fall back to rmtree, and that'll display errors if + # it can't remove things either. + try: + platform_utils.remove(dotgit) + except OSError: + pass try: - platform_utils.rmtree(os.path.join(path, '.git')) + platform_utils.rmtree(dotgit) except OSError as e: - print('Failed to remove %s (%s)' % (os.path.join(path, '.git'), str(e)), file=sys.stderr) - print('error: Failed to delete obsolete path %s' % path, file=sys.stderr) - print(' remove manually, then run sync again', file=sys.stderr) - return 1 + if e.errno != errno.ENOENT: + print('error: %s: %s' % (dotgit, str(e)), file=sys.stderr) + print('error: %s: Failed to delete obsolete path; remove manually, then ' + 'run sync again' % (path,), file=sys.stderr) + return 1 # Delete everything under the worktree, except for directories that contain # another git project -- cgit v1.2.3-54-g00ecf