mirror of
https://gerrit.googlesource.com/git-repo
synced 2025-01-02 16:14:25 +00:00
project/sync: move DeleteProject helper to Project
Since deleting a source checkout involves a good bit of internal knowledge of .repo/, move the DeleteProject helper out of the sync code and into the Project class itself. This allows us to add git worktree support to it so we can unlock/unlink project checkouts. Change-Id: If9af8bd4a9c7e29743827d8166bc3db81547ca50 Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/256072 Reviewed-by: Jonathan Nieder <jrn@google.com> Tested-by: Mike Frysinger <vapier@google.com>
This commit is contained in:
parent
f81c72ed77
commit
c0d1866b35
116
project.py
116
project.py
@ -1832,6 +1832,122 @@ class Project(object):
|
|||||||
patch_id,
|
patch_id,
|
||||||
self.bare_git.rev_parse('FETCH_HEAD'))
|
self.bare_git.rev_parse('FETCH_HEAD'))
|
||||||
|
|
||||||
|
def DeleteWorktree(self, quiet=False, force=False):
|
||||||
|
"""Delete the source checkout and any other housekeeping tasks.
|
||||||
|
|
||||||
|
This currently leaves behind the internal .repo/ cache state. This helps
|
||||||
|
when switching branches or manifest changes get reverted as we don't have
|
||||||
|
to redownload all the git objects. But we should do some GC at some point.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
quiet: Whether to hide normal messages.
|
||||||
|
force: Always delete tree even if dirty.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if the worktree was completely cleaned out.
|
||||||
|
"""
|
||||||
|
if self.IsDirty():
|
||||||
|
if force:
|
||||||
|
print('warning: %s: Removing dirty project: uncommitted changes lost.' %
|
||||||
|
(self.relpath,), file=sys.stderr)
|
||||||
|
else:
|
||||||
|
print('error: %s: Cannot remove project: uncommitted changes are '
|
||||||
|
'present.\n' % (self.relpath,), file=sys.stderr)
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not quiet:
|
||||||
|
print('%s: Deleting obsolete checkout.' % (self.relpath,))
|
||||||
|
|
||||||
|
# Unlock and delink from the main worktree. We don't use git's worktree
|
||||||
|
# remove because it will recursively delete projects -- we handle that
|
||||||
|
# ourselves below. https://crbug.com/git/48
|
||||||
|
if self.use_git_worktrees:
|
||||||
|
needle = platform_utils.realpath(self.gitdir)
|
||||||
|
# Find the git worktree commondir under .repo/worktrees/.
|
||||||
|
output = self.bare_git.worktree('list', '--porcelain').splitlines()[0]
|
||||||
|
assert output.startswith('worktree '), output
|
||||||
|
commondir = output[9:]
|
||||||
|
# Walk each of the git worktrees to see where they point.
|
||||||
|
configs = os.path.join(commondir, 'worktrees')
|
||||||
|
for name in os.listdir(configs):
|
||||||
|
gitdir = os.path.join(configs, name, 'gitdir')
|
||||||
|
with open(gitdir) as fp:
|
||||||
|
relpath = fp.read().strip()
|
||||||
|
# Resolve the checkout path and see if it matches this project.
|
||||||
|
fullpath = platform_utils.realpath(os.path.join(configs, name, relpath))
|
||||||
|
if fullpath == needle:
|
||||||
|
platform_utils.rmtree(os.path.join(configs, name))
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
# 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(self.gitdir)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
platform_utils.rmtree(self.gitdir)
|
||||||
|
except OSError as e:
|
||||||
|
if e.errno != errno.ENOENT:
|
||||||
|
print('error: %s: %s' % (self.gitdir, e), file=sys.stderr)
|
||||||
|
print('error: %s: Failed to delete obsolete checkout; remove manually, '
|
||||||
|
'then run `repo sync -l`.' % (self.relpath,), file=sys.stderr)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Delete everything under the worktree, except for directories that contain
|
||||||
|
# another git project.
|
||||||
|
dirs_to_remove = []
|
||||||
|
failed = False
|
||||||
|
for root, dirs, files in platform_utils.walk(self.worktree):
|
||||||
|
for f in files:
|
||||||
|
path = os.path.join(root, f)
|
||||||
|
try:
|
||||||
|
platform_utils.remove(path)
|
||||||
|
except OSError as e:
|
||||||
|
if e.errno != errno.ENOENT:
|
||||||
|
print('error: %s: Failed to remove: %s' % (path, e), file=sys.stderr)
|
||||||
|
failed = True
|
||||||
|
dirs[:] = [d for d in dirs
|
||||||
|
if not os.path.lexists(os.path.join(root, d, '.git'))]
|
||||||
|
dirs_to_remove += [os.path.join(root, d) for d in dirs
|
||||||
|
if os.path.join(root, d) not in dirs_to_remove]
|
||||||
|
for d in reversed(dirs_to_remove):
|
||||||
|
if platform_utils.islink(d):
|
||||||
|
try:
|
||||||
|
platform_utils.remove(d)
|
||||||
|
except OSError as e:
|
||||||
|
if e.errno != errno.ENOENT:
|
||||||
|
print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
|
||||||
|
failed = True
|
||||||
|
elif not platform_utils.listdir(d):
|
||||||
|
try:
|
||||||
|
platform_utils.rmdir(d)
|
||||||
|
except OSError as e:
|
||||||
|
if e.errno != errno.ENOENT:
|
||||||
|
print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
|
||||||
|
failed = True
|
||||||
|
if failed:
|
||||||
|
print('error: %s: Failed to delete obsolete checkout.' % (self.relpath,),
|
||||||
|
file=sys.stderr)
|
||||||
|
print(' Remove manually, then run `repo sync -l`.', file=sys.stderr)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Try deleting parent dirs if they are empty.
|
||||||
|
path = self.worktree
|
||||||
|
while path != self.manifest.topdir:
|
||||||
|
try:
|
||||||
|
platform_utils.rmdir(path)
|
||||||
|
except OSError as e:
|
||||||
|
if e.errno != errno.ENOENT:
|
||||||
|
break
|
||||||
|
path = os.path.dirname(path)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
# Branch Management ##
|
# Branch Management ##
|
||||||
def GetHeadPath(self):
|
def GetHeadPath(self):
|
||||||
"""Return the full path to the HEAD ref."""
|
"""Return the full path to the HEAD ref."""
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
import errno
|
|
||||||
import json
|
import json
|
||||||
import netrc
|
import netrc
|
||||||
from optparse import SUPPRESS_HELP
|
from optparse import SUPPRESS_HELP
|
||||||
@ -633,74 +632,6 @@ later is required to fix a server side protocol bug.
|
|||||||
else:
|
else:
|
||||||
self.manifest._Unload()
|
self.manifest._Unload()
|
||||||
|
|
||||||
def _DeleteProject(self, path):
|
|
||||||
print('Deleting obsolete path %s' % path, file=sys.stderr)
|
|
||||||
|
|
||||||
# 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(dotgit)
|
|
||||||
except OSError as e:
|
|
||||||
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
|
|
||||||
dirs_to_remove = []
|
|
||||||
failed = False
|
|
||||||
for root, dirs, files in platform_utils.walk(path):
|
|
||||||
for f in files:
|
|
||||||
try:
|
|
||||||
platform_utils.remove(os.path.join(root, f))
|
|
||||||
except OSError as e:
|
|
||||||
print('Failed to remove %s (%s)' % (os.path.join(root, f), str(e)), file=sys.stderr)
|
|
||||||
failed = True
|
|
||||||
dirs[:] = [d for d in dirs
|
|
||||||
if not os.path.lexists(os.path.join(root, d, '.git'))]
|
|
||||||
dirs_to_remove += [os.path.join(root, d) for d in dirs
|
|
||||||
if os.path.join(root, d) not in dirs_to_remove]
|
|
||||||
for d in reversed(dirs_to_remove):
|
|
||||||
if platform_utils.islink(d):
|
|
||||||
try:
|
|
||||||
platform_utils.remove(d)
|
|
||||||
except OSError as e:
|
|
||||||
print('Failed to remove %s (%s)' % (os.path.join(root, d), str(e)), file=sys.stderr)
|
|
||||||
failed = True
|
|
||||||
elif len(platform_utils.listdir(d)) == 0:
|
|
||||||
try:
|
|
||||||
platform_utils.rmdir(d)
|
|
||||||
except OSError as e:
|
|
||||||
print('Failed to remove %s (%s)' % (os.path.join(root, d), str(e)), file=sys.stderr)
|
|
||||||
failed = True
|
|
||||||
continue
|
|
||||||
if failed:
|
|
||||||
print('error: Failed to delete obsolete path %s' % path, file=sys.stderr)
|
|
||||||
print(' remove manually, then run sync again', file=sys.stderr)
|
|
||||||
return 1
|
|
||||||
|
|
||||||
# Try deleting parent dirs if they are empty
|
|
||||||
project_dir = path
|
|
||||||
while project_dir != self.manifest.topdir:
|
|
||||||
if len(platform_utils.listdir(project_dir)) == 0:
|
|
||||||
platform_utils.rmdir(project_dir)
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
project_dir = os.path.dirname(project_dir)
|
|
||||||
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def UpdateProjectList(self, opt):
|
def UpdateProjectList(self, opt):
|
||||||
new_project_paths = []
|
new_project_paths = []
|
||||||
for project in self.GetProjects(None, missing_ok=True):
|
for project in self.GetProjects(None, missing_ok=True):
|
||||||
@ -727,23 +658,15 @@ later is required to fix a server side protocol bug.
|
|||||||
remote=RemoteSpec('origin'),
|
remote=RemoteSpec('origin'),
|
||||||
gitdir=gitdir,
|
gitdir=gitdir,
|
||||||
objdir=gitdir,
|
objdir=gitdir,
|
||||||
|
use_git_worktrees=os.path.isfile(gitdir),
|
||||||
worktree=os.path.join(self.manifest.topdir, path),
|
worktree=os.path.join(self.manifest.topdir, path),
|
||||||
relpath=path,
|
relpath=path,
|
||||||
revisionExpr='HEAD',
|
revisionExpr='HEAD',
|
||||||
revisionId=None,
|
revisionId=None,
|
||||||
groups=None)
|
groups=None)
|
||||||
|
if not project.DeleteWorktree(
|
||||||
if project.IsDirty() and opt.force_remove_dirty:
|
quiet=opt.quiet,
|
||||||
print('WARNING: Removing dirty project "%s": uncommitted changes '
|
force=opt.force_remove_dirty):
|
||||||
'erased' % project.relpath, file=sys.stderr)
|
|
||||||
self._DeleteProject(project.worktree)
|
|
||||||
elif project.IsDirty():
|
|
||||||
print('error: Cannot remove project "%s": uncommitted changes '
|
|
||||||
'are present' % project.relpath, file=sys.stderr)
|
|
||||||
print(' commit changes, then run sync again',
|
|
||||||
file=sys.stderr)
|
|
||||||
return 1
|
|
||||||
elif self._DeleteProject(project.worktree):
|
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
new_project_paths.sort()
|
new_project_paths.sort()
|
||||||
|
Loading…
Reference in New Issue
Block a user