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 <vapier@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
This commit is contained in:
Mike Frysinger
2020-02-09 02:28:34 -05:00
parent 56ce3468b4
commit 979d5bdc3e
6 changed files with 138 additions and 23 deletions

View File

@ -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)

View File

@ -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.rmtree(os.path.join(path, '.git'))
platform_utils.remove(dotgit)
except OSError:
pass
try:
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