diff --git a/project.py b/project.py index 9e8ee868..868425ce 100644 --- a/project.py +++ b/project.py @@ -32,7 +32,7 @@ import traceback from color import Coloring from git_command import GitCommand, git_require from git_config import GitConfig, IsId, GetSchemeFromUrl, ID_RE -from error import GitError, HookError, UploadError +from error import GitError, HookError, UploadError, DownloadError from error import ManifestInvalidRevisionError from error import NoManifestException from trace import IsTrace, Trace @@ -1101,6 +1101,7 @@ class Project(object): quiet=False, is_new=None, current_branch_only=False, + force_sync=False, clone_bundle=True, no_tags=False, archive=False, @@ -1140,7 +1141,7 @@ class Project(object): if is_new is None: is_new = not self.Exists if is_new: - self._InitGitDir() + self._InitGitDir(force_sync=force_sync) else: self._UpdateHooks() self._InitRemote() @@ -1233,11 +1234,11 @@ class Project(object): 'revision %s in %s not found' % (self.revisionExpr, self.name)) - def Sync_LocalHalf(self, syncbuf): + def Sync_LocalHalf(self, syncbuf, force_sync=False): """Perform only the local IO portion of the sync process. Network access is not required. """ - self._InitWorkTree() + self._InitWorkTree(force_sync=force_sync) all_refs = self.bare_ref.all self.CleanPublishedCache(all_refs) revid = self.GetRevisionId(all_refs) @@ -2164,7 +2165,7 @@ class Project(object): if GitCommand(self, cmd).Wait() != 0: raise GitError('%s merge %s ' % (self.name, head)) - def _InitGitDir(self, mirror_git=None): + def _InitGitDir(self, mirror_git=None, force_sync=False): init_git_dir = not os.path.exists(self.gitdir) init_obj_dir = not os.path.exists(self.objdir) try: @@ -2181,7 +2182,20 @@ class Project(object): if init_obj_dir or init_git_dir: self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False, copy_all=True) - self._CheckDirReference(self.objdir, self.gitdir, share_refs=False) + try: + self._CheckDirReference(self.objdir, self.gitdir, share_refs=False) + except GitError as e: + print("Retrying clone after deleting %s" % force_sync, file=sys.stderr) + if force_sync: + try: + shutil.rmtree(os.path.realpath(self.gitdir)) + if self.worktree and os.path.exists( + os.path.realpath(self.worktree)): + shutil.rmtree(os.path.realpath(self.worktree)) + return self._InitGitDir(mirror_git=mirror_git, force_sync=False) + except: + raise e + raise e if init_git_dir: mp = self.manifest.manifestProject @@ -2308,7 +2322,8 @@ class Project(object): src = os.path.realpath(os.path.join(srcdir, name)) # Fail if the links are pointing to the wrong place if src != dst: - raise GitError('cannot overwrite a local work tree') + raise GitError('--force-sync not enabled; cannot overwrite a local ' + 'work tree') def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all): """Update |dotgit| to reference |gitdir|, using symlinks where possible. @@ -2361,11 +2376,11 @@ class Project(object): shutil.copy(src, dst) except OSError as e: if e.errno == errno.EPERM: - raise GitError('filesystem must support symlinks') + raise DownloadError('filesystem must support symlinks') else: raise - def _InitWorkTree(self): + def _InitWorkTree(self, force_sync=False): dotgit = os.path.join(self.worktree, '.git') init_dotgit = not os.path.exists(dotgit) try: @@ -2374,7 +2389,16 @@ class Project(object): self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True, copy_all=False) - self._CheckDirReference(self.gitdir, dotgit, share_refs=True) + try: + self._CheckDirReference(self.gitdir, dotgit, share_refs=True) + except GitError as e: + if force_sync: + try: + shutil.rmtree(dotgit) + return self._InitWorkTree(force_sync=False) + except: + raise e + raise e if init_dotgit: _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId()) diff --git a/subcmds/sync.py b/subcmds/sync.py index ec333ae7..a8074a40 100644 --- a/subcmds/sync.py +++ b/subcmds/sync.py @@ -119,6 +119,11 @@ credentials. The -f/--force-broken option can be used to proceed with syncing other projects if a project sync fails. +The --force-sync option can be used to overwrite existing git +directories if they have previously been linked to a different +object direcotry. WARNING: This may cause data to be lost since +refs may be removed when overwriting. + The --no-clone-bundle option disables any attempt to use $URL/clone.bundle to bootstrap a new Git repository from a resumeable bundle file on a content delivery network. This @@ -174,6 +179,11 @@ later is required to fix a server side protocol bug. p.add_option('-f', '--force-broken', dest='force_broken', action='store_true', help="continue sync even if a project fails to sync") + p.add_option('--force-sync', + dest='force_sync', action='store_true', + help="overwrite an existing git directory if it needs to " + "point to a different object directory. WARNING: this " + "may cause loss of data") p.add_option('-l', '--local-only', dest='local_only', action='store_true', help="only update working tree, don't fetch") @@ -281,6 +291,7 @@ later is required to fix a server side protocol bug. success = project.Sync_NetworkHalf( quiet=opt.quiet, current_branch_only=opt.current_branch_only, + force_sync=opt.force_sync, clone_bundle=not opt.no_clone_bundle, no_tags=opt.no_tags, archive=self.manifest.IsArchive, optimized_fetch=opt.optimized_fetch) @@ -696,7 +707,7 @@ later is required to fix a server side protocol bug. for project in all_projects: pm.update() if project.worktree: - project.Sync_LocalHalf(syncbuf) + project.Sync_LocalHalf(syncbuf, force_sync=opt.force_sync) pm.end() print(file=sys.stderr) if not syncbuf.Finish():