comparison mercurial/bundlerepo.py @ 39604:335ae4d0a552

bundlerepo: dynamically create repository type from base repository Previously, bundlerepository inherited from localrepo.localrepository. You simply instantiated a bundlerepository and its __init__ called localrepo.localrepository.__init__. Things were simple. Unfortunately, this strategy is limiting because it assumes that the base repository is a localrepository instance. And it assumes various properties of localrepository, such as the arguments its __init__ takes. And it prevents us from changing behavior of localrepository.__init__ without also having to change derived classes. Previous and ongoing work to abstract storage revealed these limitations. This commit changes the initialization strategy of bundle repositories to dynamically create a type to represent the repository. Instead of a static type, we instantiate a new local repo instance via localrepo.instance(). We then combine its __class__ with bundlerepository to produce a new type. This ensures that no matter how localrepo.instance() decides to create a repository object, we can derive a bundle repo object from it. i.e. localrepo.instance() could return a type that isn't a localrepository and it would "just work." Well, it would "just work" if bundlerepository's custom implementations only accessed attributes in the documented repository interface. I'm pretty sure it violates the interface contract in a handful of places. But we can worry about that another day. This change gets us closer to doing more clever things around instantiating repository instances without having to worry about teaching bundlerepository about them. .. api:: ``bundlerepo.bundlerepository`` is no longer usable on its own. The class is combined with the class of the base repository it is associated with at run-time. New bundlerepository instances can be obtained by calling ``bundlerepo.instance()`` or ``bundlerepo.makebundlerepository()``. Differential Revision: https://phab.mercurial-scm.org/D4555
author Gregory Szorc <gregory.szorc@gmail.com>
date Tue, 11 Sep 2018 19:50:07 -0700
parents a8d2faeca49e
children 5ccd791344f3
comparison
equal deleted inserted replaced
39603:a8d2faeca49e 39604:335ae4d0a552
253 filespos[fname] = cgunpacker.tell() 253 filespos[fname] = cgunpacker.tell()
254 for chunk in iter(lambda: cgunpacker.deltachunk(None), {}): 254 for chunk in iter(lambda: cgunpacker.deltachunk(None), {}):
255 pass 255 pass
256 return filespos 256 return filespos
257 257
258 class bundlerepository(localrepo.localrepository): 258 class bundlerepository(object):
259 """A repository instance that is a union of a local repo and a bundle. 259 """A repository instance that is a union of a local repo and a bundle.
260 260
261 Instances represent a read-only repository composed of a local repository 261 Instances represent a read-only repository composed of a local repository
262 with the contents of a bundle file applied. The repository instance is 262 with the contents of a bundle file applied. The repository instance is
263 conceptually similar to the state of a repository after an 263 conceptually similar to the state of a repository after an
264 ``hg unbundle`` operation. However, the contents of the bundle are never 264 ``hg unbundle`` operation. However, the contents of the bundle are never
265 applied to the actual base repository. 265 applied to the actual base repository.
266
267 Instances constructed directly are not usable as repository objects.
268 Use instance() or makebundlerepository() to create instances.
266 """ 269 """
267 def __init__(self, ui, repopath, bundlepath): 270 def __init__(self, bundlepath, url, tempparent):
268 self._tempparent = None 271 self._tempparent = tempparent
269 try: 272 self._url = url
270 localrepo.localrepository.__init__(self, ui, repopath) 273
271 except error.RepoError:
272 self._tempparent = pycompat.mkdtemp()
273 localrepo.instance(ui, self._tempparent, create=True)
274 localrepo.localrepository.__init__(self, ui, self._tempparent)
275 self.ui.setconfig('phases', 'publish', False, 'bundlerepo') 274 self.ui.setconfig('phases', 'publish', False, 'bundlerepo')
276
277 if repopath:
278 self._url = 'bundle:' + util.expandpath(repopath) + '+' + bundlepath
279 else:
280 self._url = 'bundle:' + bundlepath
281 275
282 self.tempfile = None 276 self.tempfile = None
283 f = util.posixfile(bundlepath, "rb") 277 f = util.posixfile(bundlepath, "rb")
284 bundle = exchange.readbundle(ui, f, bundlepath) 278 bundle = exchange.readbundle(self.ui, f, bundlepath)
285 279
286 if isinstance(bundle, bundle2.unbundle20): 280 if isinstance(bundle, bundle2.unbundle20):
287 self._bundlefile = bundle 281 self._bundlefile = bundle
288 self._cgunpacker = None 282 self._cgunpacker = None
289 283
309 303
310 elif isinstance(bundle, changegroup.cg1unpacker): 304 elif isinstance(bundle, changegroup.cg1unpacker):
311 if bundle.compressed(): 305 if bundle.compressed():
312 f = self._writetempbundle(bundle.read, '.hg10un', 306 f = self._writetempbundle(bundle.read, '.hg10un',
313 header='HG10UN') 307 header='HG10UN')
314 bundle = exchange.readbundle(ui, f, bundlepath, self.vfs) 308 bundle = exchange.readbundle(self.ui, f, bundlepath, self.vfs)
315 309
316 self._bundlefile = bundle 310 self._bundlefile = bundle
317 self._cgunpacker = bundle 311 self._cgunpacker = bundle
318 else: 312 else:
319 raise error.Abort(_('bundle type %s cannot be read') % 313 raise error.Abort(_('bundle type %s cannot be read') %
482 476
483 return makebundlerepository(ui, repopath, bundlename) 477 return makebundlerepository(ui, repopath, bundlename)
484 478
485 def makebundlerepository(ui, repopath, bundlepath): 479 def makebundlerepository(ui, repopath, bundlepath):
486 """Make a bundle repository object based on repo and bundle paths.""" 480 """Make a bundle repository object based on repo and bundle paths."""
487 return bundlerepository(ui, repopath, bundlepath) 481 if repopath:
482 url = 'bundle:%s+%s' % (util.expandpath(repopath), bundlepath)
483 else:
484 url = 'bundle:%s' % bundlepath
485
486 # Because we can't make any guarantees about the type of the base
487 # repository, we can't have a static class representing the bundle
488 # repository. We also can't make any guarantees about how to even
489 # call the base repository's constructor!
490 #
491 # So, our strategy is to go through ``localrepo.instance()`` to construct
492 # a repo instance. Then, we dynamically create a new type derived from
493 # both it and our ``bundlerepository`` class which overrides some
494 # functionality. We then change the type of the constructed repository
495 # to this new type and initialize the bundle-specific bits of it.
496
497 try:
498 parentrepo = localrepo.instance(ui, repopath, create=False)
499 tempparent = None
500 except error.RepoError:
501 tempparent = pycompat.mkdtemp()
502 try:
503 parentrepo = localrepo.instance(ui, tempparent, create=True)
504 except Exception:
505 shutil.rmtree(tempparent)
506 raise
507
508 class derivedbundlerepository(bundlerepository, parentrepo.__class__):
509 pass
510
511 repo = parentrepo
512 repo.__class__ = derivedbundlerepository
513 bundlerepository.__init__(repo, bundlepath, url, tempparent)
514
515 return repo
488 516
489 class bundletransactionmanager(object): 517 class bundletransactionmanager(object):
490 def transaction(self): 518 def transaction(self):
491 return None 519 return None
492 520