hg: don't reuse repo instance after unshare()
Unsharing a repository is a pretty invasive procedure and fundamentally
changes the behavior of the repository.
Currently, hg.unshare() calls into localrepository.__init__ to
re-initialize the repository instance. This is a bit hacky. And
future commits that refactor how localrepository instances are
constructed will make this difficult to support.
This commit changes unshare() so it constructs a new repo instance
once the unshare I/O has completed. It then poisons the old repo
instance so any further use will result in error.
Surprisingly, nothing in core appears to access a repo instance
after it has been unshared!
.. api::
``hg.unshare()`` now poisons the repo instance so it can't be used.
It also returns a new repo instance suitable for interacting with
the unshared repository.
Differential Revision: https://phab.mercurial-scm.org/D4557
--- a/mercurial/hg.py Tue Sep 11 20:06:39 2018 -0700
+++ b/mercurial/hg.py Wed Sep 12 19:00:46 2018 -0700
@@ -307,6 +307,11 @@
"""convert a shared repository to a normal one
Copy the store data to the repo and remove the sharedpath data.
+
+ Returns a new repository object representing the unshared repository.
+
+ The passed repository object is not usable after this function is
+ called.
"""
destlock = lock = None
@@ -329,16 +334,22 @@
destlock and destlock.release()
lock and lock.release()
- # update store, spath, svfs and sjoin of repo
- repo.unfiltered().__init__(repo.baseui, repo.root)
+ # Removing share changes some fundamental properties of the repo instance.
+ # So we instantiate a new repo object and operate on it rather than
+ # try to keep the existing repo usable.
+ newrepo = repository(repo.baseui, repo.root, create=False)
# TODO: figure out how to access subrepos that exist, but were previously
# removed from .hgsub
- c = repo['.']
+ c = newrepo['.']
subs = c.substate
for s in sorted(subs):
c.sub(s).unshare()
+ localrepo.poisonrepository(repo)
+
+ return newrepo
+
def postshare(sourcerepo, destrepo, bookmarks=True, defaultpath=None):
"""Called after a new shared repo is created.
--- a/mercurial/localrepo.py Tue Sep 11 20:06:39 2018 -0700
+++ b/mercurial/localrepo.py Wed Sep 12 19:00:46 2018 -0700
@@ -2503,3 +2503,28 @@
b'layout')
scmutil.writerequires(hgvfs, requirements)
+
+def poisonrepository(repo):
+ """Poison a repository instance so it can no longer be used."""
+ # Perform any cleanup on the instance.
+ repo.close()
+
+ # Our strategy is to replace the type of the object with one that
+ # has all attribute lookups result in error.
+ #
+ # But we have to allow the close() method because some constructors
+ # of repos call close() on repo references.
+ class poisonedrepository(object):
+ def __getattribute__(self, item):
+ if item == r'close':
+ return object.__getattribute__(self, item)
+
+ raise error.ProgrammingError('repo instances should not be used '
+ 'after unshare')
+
+ def close(self):
+ pass
+
+ # We may have a repoview, which intercepts __setattr__. So be sure
+ # we operate at the lowest level possible.
+ object.__setattr__(repo, r'__class__', poisonedrepository)