bundlerepo.py
665 lines
| 23.7 KiB
| text/x-python
|
PythonLexer
/ mercurial / bundlerepo.py
|
r8226 | # bundlerepo.py - repository class for viewing uncompressed bundles | ||
# | ||||
# Copyright 2006, 2007 Benoit Boissinot <bboissin@gmail.com> | ||||
# | ||||
# This software may be used and distributed according to the terms of the | ||||
|
r10263 | # GNU General Public License version 2 or any later version. | ||
|
r1942 | |||
|
r8227 | """Repository class for viewing uncompressed bundles. | ||
This provides a read-only repository interface to bundles as if they | ||||
were part of the actual repository. | ||||
""" | ||||
|
r25920 | from __future__ import absolute_import | ||
import os | ||||
import shutil | ||||
from .i18n import _ | ||||
from .node import nullid | ||||
from . import ( | ||||
bundle2, | ||||
changegroup, | ||||
changelog, | ||||
cmdutil, | ||||
discovery, | ||||
|
r39843 | encoding, | ||
|
r25920 | error, | ||
exchange, | ||||
filelog, | ||||
localrepo, | ||||
manifest, | ||||
mdiff, | ||||
|
r28714 | node as nodemod, | ||
|
r25920 | pathutil, | ||
phases, | ||||
|
r30519 | pycompat, | ||
|
r25920 | revlog, | ||
util, | ||||
|
r31240 | vfs as vfsmod, | ||
|
r25920 | ) | ||
|
r1942 | |||
|
r1946 | class bundlerevlog(revlog.revlog): | ||
|
r35075 | def __init__(self, opener, indexfile, cgunpacker, linkmapper): | ||
|
r1942 | # How it works: | ||
|
r18410 | # To retrieve a revision, we need to know the offset of the revision in | ||
# the bundle (an unbundle object). We store this offset in the index | ||||
|
r18643 | # (start). The base of the delta is stored in the base field. | ||
|
r1942 | # | ||
|
r18410 | # To differentiate a rev in the bundle from a rev in the revlog, we | ||
|
r18643 | # check revision against repotiprev. | ||
|
r31240 | opener = vfsmod.readonlyvfs(opener) | ||
|
r4257 | revlog.revlog.__init__(self, opener, indexfile) | ||
|
r35075 | self.bundle = cgunpacker | ||
|
r6750 | n = len(self) | ||
|
r18643 | self.repotiprev = n - 1 | ||
|
r18411 | self.bundlerevs = set() # used by 'bundle()' revset expression | ||
|
r35075 | for deltadata in cgunpacker.deltaiter(): | ||
|
r34294 | node, p1, p2, cs, deltabase, delta, flags = deltadata | ||
|
r14142 | |||
size = len(delta) | ||||
|
r35075 | start = cgunpacker.tell() - size | ||
|
r14142 | |||
link = linkmapper(cs) | ||||
|
r1942 | if node in self.nodemap: | ||
|
r14142 | # this can happen if two branches make the same change | ||
|
r18411 | self.bundlerevs.add(self.nodemap[node]) | ||
|
r1942 | continue | ||
|
r14142 | |||
|
r1942 | for p in (p1, p2): | ||
|
r16686 | if p not in self.nodemap: | ||
|
r9650 | raise error.LookupError(p, self.indexfile, | ||
|
r7633 | _("unknown parent")) | ||
|
r18416 | |||
if deltabase not in self.nodemap: | ||||
raise LookupError(deltabase, self.indexfile, | ||||
_('unknown delta base')) | ||||
baserev = self.rev(deltabase) | ||||
|
r5167 | # start, size, full unc. size, base (unused), link, p1, p2, node | ||
|
r31835 | e = (revlog.offset_type(start, flags), size, -1, baserev, link, | ||
|
r4979 | self.rev(p1), self.rev(p2), node) | ||
|
r38886 | self.index.append(e) | ||
|
r1942 | self.nodemap[node] = n | ||
|
r18411 | self.bundlerevs.add(n) | ||
|
r1942 | n += 1 | ||
|
r35038 | def _chunk(self, rev, df=None): | ||
|
r18643 | # Warning: in case of bundle, the diff is against what we stored as | ||
# delta base, not against rev - 1 | ||||
|
r1942 | # XXX: could use some caching | ||
|
r18643 | if rev <= self.repotiprev: | ||
|
r9676 | return revlog.revlog._chunk(self, rev) | ||
|
r12332 | self.bundle.seek(self.start(rev)) | ||
return self.bundle.read(self.length(rev)) | ||||
|
r1942 | |||
def revdiff(self, rev1, rev2): | ||||
"""return or calculate a delta between two revisions""" | ||||
|
r18643 | if rev1 > self.repotiprev and rev2 > self.repotiprev: | ||
|
r1942 | # hot path for bundle | ||
|
r18643 | revb = self.index[rev2][3] | ||
|
r1942 | if revb == rev1: | ||
|
r9676 | return self._chunk(rev2) | ||
|
r18643 | elif rev1 <= self.repotiprev and rev2 <= self.repotiprev: | ||
|
r4028 | return revlog.revlog.revdiff(self, rev1, rev2) | ||
|
r1942 | |||
|
r31837 | return mdiff.textdiff(self.revision(rev1, raw=True), | ||
self.revision(rev2, raw=True)) | ||||
|
r1942 | |||
|
r35038 | def revision(self, nodeorrev, _df=None, raw=False): | ||
|
r16435 | """return an uncompressed revision of a given node or revision | ||
number. | ||||
""" | ||||
|
r16375 | if isinstance(nodeorrev, int): | ||
rev = nodeorrev | ||||
node = self.node(rev) | ||||
else: | ||||
node = nodeorrev | ||||
rev = self.rev(node) | ||||
|
r10282 | if node == nullid: | ||
return "" | ||||
|
r1942 | |||
|
r31836 | rawtext = None | ||
|
r1942 | chain = [] | ||
|
r18415 | iterrev = rev | ||
|
r1942 | # reconstruct the revision if it is from a changegroup | ||
|
r18643 | while iterrev > self.repotiprev: | ||
|
r40088 | if self._revisioncache and self._revisioncache[1] == iterrev: | ||
rawtext = self._revisioncache[2] | ||||
|
r1942 | break | ||
|
r18415 | chain.append(iterrev) | ||
|
r18643 | iterrev = self.index[iterrev][3] | ||
|
r31836 | if rawtext is None: | ||
rawtext = self.baserevision(iterrev) | ||||
|
r1942 | |||
while chain: | ||||
|
r9676 | delta = self._chunk(chain.pop()) | ||
|
r31836 | rawtext = mdiff.patches(rawtext, [delta]) | ||
|
r1942 | |||
|
r31836 | text, validatehash = self._processflags(rawtext, self.flags(rev), | ||
|
r30745 | 'read', raw=raw) | ||
if validatehash: | ||||
self.checkhash(text, node, rev=rev) | ||||
|
r40088 | self._revisioncache = (node, rev, rawtext) | ||
|
r1942 | return text | ||
|
r19629 | def baserevision(self, nodeorrev): | ||
# Revlog subclasses may override 'revision' method to modify format of | ||||
# content retrieved from revlog. To use bundlerevlog with such class one | ||||
# needs to override 'baserevision' and make more specific call here. | ||||
|
r31834 | return revlog.revlog.revision(self, nodeorrev, raw=True) | ||
|
r19629 | |||
|
r35038 | def addrevision(self, *args, **kwargs): | ||
raise NotImplementedError | ||||
def addgroup(self, *args, **kwargs): | ||||
|
r1942 | raise NotImplementedError | ||
|
r35038 | |||
def strip(self, *args, **kwargs): | ||||
|
r1942 | raise NotImplementedError | ||
|
r35038 | |||
|
r1942 | def checksize(self): | ||
raise NotImplementedError | ||||
|
r1946 | class bundlechangelog(bundlerevlog, changelog.changelog): | ||
|
r35075 | def __init__(self, opener, cgunpacker): | ||
|
r1946 | changelog.changelog.__init__(self, opener) | ||
|
r14142 | linkmapper = lambda x: x | ||
|
r35075 | bundlerevlog.__init__(self, opener, self.indexfile, cgunpacker, | ||
|
r14142 | linkmapper) | ||
|
r1942 | |||
|
r19629 | def baserevision(self, nodeorrev): | ||
# Although changelog doesn't override 'revision' method, some extensions | ||||
# may replace this class with another that does. Same story with | ||||
# manifest and filelog classes. | ||||
|
r24882 | |||
# This bypasses filtering on changelog.node() and rev() because we need | ||||
# revision text of the bundle base even if it is hidden. | ||||
oldfilter = self.filteredrevs | ||||
try: | ||||
self.filteredrevs = () | ||||
|
r31834 | return changelog.changelog.revision(self, nodeorrev, raw=True) | ||
|
r24882 | finally: | ||
self.filteredrevs = oldfilter | ||||
|
r19629 | |||
|
r30373 | class bundlemanifest(bundlerevlog, manifest.manifestrevlog): | ||
|
r35075 | def __init__(self, opener, cgunpacker, linkmapper, dirlogstarts=None, | ||
dir=''): | ||||
|
r39279 | manifest.manifestrevlog.__init__(self, opener, tree=dir) | ||
|
r35075 | bundlerevlog.__init__(self, opener, self.indexfile, cgunpacker, | ||
|
r4257 | linkmapper) | ||
|
r29715 | if dirlogstarts is None: | ||
dirlogstarts = {} | ||||
if self.bundle.version == "03": | ||||
dirlogstarts = _getfilestarts(self.bundle) | ||||
self._dirlogstarts = dirlogstarts | ||||
self._linkmapper = linkmapper | ||||
|
r1942 | |||
|
r19629 | def baserevision(self, nodeorrev): | ||
|
r26399 | node = nodeorrev | ||
if isinstance(node, int): | ||||
node = self.node(node) | ||||
|
r29925 | if node in self.fulltextcache: | ||
|
r31346 | result = '%s' % self.fulltextcache[node] | ||
|
r26399 | else: | ||
|
r31834 | result = manifest.manifestrevlog.revision(self, nodeorrev, raw=True) | ||
|
r26399 | return result | ||
|
r19629 | |||
|
r29715 | def dirlog(self, d): | ||
if d in self._dirlogstarts: | ||||
self.bundle.seek(self._dirlogstarts[d]) | ||||
return bundlemanifest( | ||||
self.opener, self.bundle, self._linkmapper, | ||||
self._dirlogstarts, dir=d) | ||||
return super(bundlemanifest, self).dirlog(d) | ||||
|
r37515 | class bundlefilelog(filelog.filelog): | ||
|
r35075 | def __init__(self, opener, path, cgunpacker, linkmapper): | ||
|
r1946 | filelog.filelog.__init__(self, opener, path) | ||
|
r37515 | self._revlog = bundlerevlog(opener, self.indexfile, | ||
cgunpacker, linkmapper) | ||||
|
r14287 | |||
|
r19629 | def baserevision(self, nodeorrev): | ||
|
r31834 | return filelog.filelog.revision(self, nodeorrev, raw=True) | ||
|
r19629 | |||
|
r17193 | class bundlepeer(localrepo.localpeer): | ||
def canpush(self): | ||||
return False | ||||
|
r23631 | class bundlephasecache(phases.phasecache): | ||
def __init__(self, *args, **kwargs): | ||||
super(bundlephasecache, self).__init__(*args, **kwargs) | ||||
if util.safehasattr(self, 'opener'): | ||||
|
r31240 | self.opener = vfsmod.readonlyvfs(self.opener) | ||
|
r23631 | |||
def write(self): | ||||
raise NotImplementedError | ||||
def _write(self, fp): | ||||
raise NotImplementedError | ||||
def _updateroots(self, phase, newroots, tr): | ||||
self.phaseroots[phase] = newroots | ||||
self.invalidate() | ||||
self.dirty = True | ||||
|
r35075 | def _getfilestarts(cgunpacker): | ||
|
r35076 | filespos = {} | ||
|
r35075 | for chunkdata in iter(cgunpacker.filelogheader, {}): | ||
|
r29712 | fname = chunkdata['filename'] | ||
|
r35076 | filespos[fname] = cgunpacker.tell() | ||
|
r35075 | for chunk in iter(lambda: cgunpacker.deltachunk(None), {}): | ||
|
r29712 | pass | ||
|
r35076 | return filespos | ||
|
r29712 | |||
|
r39640 | class bundlerepository(object): | ||
|
r35041 | """A repository instance that is a union of a local repo and a bundle. | ||
Instances represent a read-only repository composed of a local repository | ||||
with the contents of a bundle file applied. The repository instance is | ||||
conceptually similar to the state of a repository after an | ||||
``hg unbundle`` operation. However, the contents of the bundle are never | ||||
applied to the actual base repository. | ||||
|
r39640 | |||
Instances constructed directly are not usable as repository objects. | ||||
Use instance() or makebundlerepository() to create instances. | ||||
|
r35041 | """ | ||
|
r39640 | def __init__(self, bundlepath, url, tempparent): | ||
self._tempparent = tempparent | ||||
self._url = url | ||||
|
r20790 | self.ui.setconfig('phases', 'publish', False, 'bundlerepo') | ||
|
r2673 | |||
|
r2273 | self.tempfile = None | ||
|
r35040 | f = util.posixfile(bundlepath, "rb") | ||
|
r39640 | bundle = exchange.readbundle(self.ui, f, bundlepath) | ||
|
r12044 | |||
|
r35044 | if isinstance(bundle, bundle2.unbundle20): | ||
self._bundlefile = bundle | ||||
|
r35045 | self._cgunpacker = None | ||
|
r35044 | |||
|
r35112 | cgpart = None | ||
|
r35113 | for part in bundle.iterparts(seekable=True): | ||
|
r26803 | if part.type == 'changegroup': | ||
|
r35112 | if cgpart: | ||
|
r26803 | raise NotImplementedError("can't process " | ||
"multiple changegroups") | ||||
|
r35112 | cgpart = part | ||
|
r24073 | |||
|
r35044 | self._handlebundle2part(bundle, part) | ||
|
r24073 | |||
|
r35112 | if not cgpart: | ||
|
r33890 | raise error.Abort(_("No changegroups found")) | ||
|
r35112 | |||
# This is required to placate a later consumer, which expects | ||||
# the payload offset to be at the beginning of the changegroup. | ||||
# We need to do this after the iterparts() generator advances | ||||
# because iterparts() will seek to end of payload after the | ||||
# generator returns control to iterparts(). | ||||
cgpart.seek(0, os.SEEK_SET) | ||||
|
r35044 | elif isinstance(bundle, changegroup.cg1unpacker): | ||
if bundle.compressed(): | ||||
f = self._writetempbundle(bundle.read, '.hg10un', | ||||
|
r35042 | header='HG10UN') | ||
|
r39640 | bundle = exchange.readbundle(self.ui, f, bundlepath, self.vfs) | ||
|
r35044 | |||
self._bundlefile = bundle | ||||
|
r35045 | self._cgunpacker = bundle | ||
|
r35042 | else: | ||
raise error.Abort(_('bundle type %s cannot be read') % | ||||
|
r35044 | type(bundle)) | ||
|
r26801 | |||
|
r35076 | # dict with the mapping 'filename' -> position in the changegroup. | ||
self._cgfilespos = {} | ||||
|
r5262 | |||
|
r23632 | self.firstnewrev = self.changelog.repotiprev + 1 | ||
phases.retractboundary(self, None, phases.draft, | ||||
[ctx.node() for ctx in self[self.firstnewrev:]]) | ||||
|
r35044 | def _handlebundle2part(self, bundle, part): | ||
|
r35074 | if part.type != 'changegroup': | ||
return | ||||
|
r33890 | |||
|
r35074 | cgstream = part | ||
version = part.params.get('version', '01') | ||||
legalcgvers = changegroup.supportedincomingversions(self) | ||||
if version not in legalcgvers: | ||||
msg = _('Unsupported changegroup version: %s') | ||||
raise error.Abort(msg % version) | ||||
if bundle.compressed(): | ||||
cgstream = self._writetempbundle(part.read, '.cg%sun' % version) | ||||
self._cgunpacker = changegroup.getunbundler(version, cgstream, 'UN') | ||||
|
r33890 | |||
|
r33888 | def _writetempbundle(self, readfn, suffix, header=''): | ||
"""Write a temporary file to disk | ||||
""" | ||||
fdtemp, temp = self.vfs.mkstemp(prefix="hg-bundle-", | ||||
|
r35039 | suffix=suffix) | ||
|
r33888 | self.tempfile = temp | ||
|
r36853 | with os.fdopen(fdtemp, r'wb') as fptemp: | ||
|
r33888 | fptemp.write(header) | ||
while True: | ||||
chunk = readfn(2**18) | ||||
if not chunk: | ||||
break | ||||
fptemp.write(chunk) | ||||
return self.vfs.open(self.tempfile, mode="rb") | ||||
|
r18014 | @localrepo.unfilteredpropertycache | ||
|
r23631 | def _phasecache(self): | ||
return bundlephasecache(self, self._phasedefaults) | ||||
@localrepo.unfilteredpropertycache | ||||
|
r8260 | def changelog(self): | ||
|
r14144 | # consume the header if it exists | ||
|
r35045 | self._cgunpacker.changelogheader() | ||
c = bundlechangelog(self.svfs, self._cgunpacker) | ||||
self.manstart = self._cgunpacker.tell() | ||||
|
r8260 | return c | ||
r42710 | def _refreshchangelog(self): | |||
# changelog for bundle repo are not filecache, this method is not | ||||
# applicable. | ||||
pass | ||||
|
r39799 | @localrepo.unfilteredpropertycache | ||
def manifestlog(self): | ||||
|
r35045 | self._cgunpacker.seek(self.manstart) | ||
|
r14144 | # consume the header if it exists | ||
|
r35045 | self._cgunpacker.manifestheader() | ||
|
r28221 | linkmapper = self.unfiltered().changelog.rev | ||
|
r39799 | rootstore = bundlemanifest(self.svfs, self._cgunpacker, linkmapper) | ||
|
r35045 | self.filestart = self._cgunpacker.tell() | ||
|
r39799 | |||
|
r41067 | return manifest.manifestlog(self.svfs, self, rootstore, | ||
self.narrowmatch()) | ||||
|
r8260 | |||
|
r35012 | def _consumemanifest(self): | ||
"""Consumes the manifest portion of the bundle, setting filestart so the | ||||
file portion can be read.""" | ||||
|
r35045 | self._cgunpacker.seek(self.manstart) | ||
self._cgunpacker.manifestheader() | ||||
for delta in self._cgunpacker.deltaiter(): | ||||
|
r35012 | pass | ||
|
r35045 | self.filestart = self._cgunpacker.tell() | ||
|
r35012 | |||
|
r18014 | @localrepo.unfilteredpropertycache | ||
|
r8260 | def manstart(self): | ||
self.changelog | ||||
return self.manstart | ||||
|
r18014 | @localrepo.unfilteredpropertycache | ||
|
r8260 | def filestart(self): | ||
|
r30375 | self.manifestlog | ||
|
r35012 | |||
# If filestart was not set by self.manifestlog, that means the | ||||
# manifestlog implementation did not consume the manifests from the | ||||
# changegroup (ex: it might be consuming trees from a separate bundle2 | ||||
# part instead). So we need to manually consume it. | ||||
|
r35851 | if r'filestart' not in self.__dict__: | ||
|
r35012 | self._consumemanifest() | ||
|
r8260 | return self.filestart | ||
|
r1942 | |||
|
r2673 | def url(self): | ||
return self._url | ||||
|
r1942 | def file(self, f): | ||
|
r35076 | if not self._cgfilespos: | ||
|
r35045 | self._cgunpacker.seek(self.filestart) | ||
|
r35076 | self._cgfilespos = _getfilestarts(self._cgunpacker) | ||
|
r5262 | |||
|
r35076 | if f in self._cgfilespos: | ||
self._cgunpacker.seek(self._cgfilespos[f]) | ||||
|
r28186 | linkmapper = self.unfiltered().changelog.rev | ||
|
r35045 | return bundlefilelog(self.svfs, f, self._cgunpacker, linkmapper) | ||
|
r1942 | else: | ||
|
r37347 | return super(bundlerepository, self).file(f) | ||
|
r1942 | |||
|
r12347 | def close(self): | ||
"""Close assigned bundle file immediately.""" | ||||
|
r35043 | self._bundlefile.close() | ||
|
r12962 | if self.tempfile is not None: | ||
|
r20981 | self.vfs.unlink(self.tempfile) | ||
|
r6314 | if self._tempparent: | ||
shutil.rmtree(self._tempparent, True) | ||||
|
r2740 | |||
|
r6315 | def cancopy(self): | ||
return False | ||||
|
r17193 | def peer(self): | ||
return bundlepeer(self) | ||||
|
r7435 | def getcwd(self): | ||
|
r39843 | return encoding.getcwd() # always outside the repo | ||
|
r7435 | |||
|
r28714 | # Check if parents exist in localrepo before setting | ||
def setparents(self, p1, p2=nullid): | ||||
p1rev = self.changelog.rev(p1) | ||||
p2rev = self.changelog.rev(p2) | ||||
msg = _("setting parent to node %s that only exists in the bundle\n") | ||||
if self.changelog.repotiprev < p1rev: | ||||
self.ui.warn(msg % nodemod.hex(p1)) | ||||
if self.changelog.repotiprev < p2rev: | ||||
self.ui.warn(msg % nodemod.hex(p2)) | ||||
return super(bundlerepository, self).setparents(p1, p2) | ||||
|
r15597 | |||
|
r39585 | def instance(ui, path, create, intents=None, createopts=None): | ||
|
r2740 | if create: | ||
|
r26587 | raise error.Abort(_('cannot create new bundle repository')) | ||
|
r25830 | # internal config: bundle.mainreporoot | ||
r33179 | parentpath = ui.config("bundle", "mainreporoot") | |||
|
r16042 | if not parentpath: | ||
# try to find the correct path to the working directory repo | ||||
|
r39843 | parentpath = cmdutil.findrepo(encoding.getcwd()) | ||
|
r16042 | if parentpath is None: | ||
parentpath = '' | ||||
|
r5664 | if parentpath: | ||
# Try to make the full path relative so we get a nice, short URL. | ||||
# In particular, we don't want temp dir names in test outputs. | ||||
|
r39843 | cwd = encoding.getcwd() | ||
|
r5664 | if parentpath == cwd: | ||
parentpath = '' | ||||
else: | ||||
|
r24834 | cwd = pathutil.normasprefix(cwd) | ||
|
r5664 | if parentpath.startswith(cwd): | ||
parentpath = parentpath[len(cwd):] | ||||
|
r14076 | u = util.url(path) | ||
|
r13826 | path = u.localpath() | ||
if u.scheme == 'bundle': | ||||
|
r2740 | s = path.split("+", 1) | ||
if len(s) == 1: | ||||
|
r5664 | repopath, bundlename = parentpath, s[0] | ||
|
r2740 | else: | ||
repopath, bundlename = s | ||||
else: | ||||
|
r5664 | repopath, bundlename = parentpath, path | ||
|
r39639 | |||
return makebundlerepository(ui, repopath, bundlename) | ||||
def makebundlerepository(ui, repopath, bundlepath): | ||||
"""Make a bundle repository object based on repo and bundle paths.""" | ||||
|
r39640 | if repopath: | ||
url = 'bundle:%s+%s' % (util.expandpath(repopath), bundlepath) | ||||
else: | ||||
url = 'bundle:%s' % bundlepath | ||||
# Because we can't make any guarantees about the type of the base | ||||
# repository, we can't have a static class representing the bundle | ||||
# repository. We also can't make any guarantees about how to even | ||||
# call the base repository's constructor! | ||||
# | ||||
# So, our strategy is to go through ``localrepo.instance()`` to construct | ||||
# a repo instance. Then, we dynamically create a new type derived from | ||||
# both it and our ``bundlerepository`` class which overrides some | ||||
# functionality. We then change the type of the constructed repository | ||||
# to this new type and initialize the bundle-specific bits of it. | ||||
try: | ||||
|
r39980 | repo = localrepo.instance(ui, repopath, create=False) | ||
|
r39640 | tempparent = None | ||
except error.RepoError: | ||||
tempparent = pycompat.mkdtemp() | ||||
try: | ||||
|
r39980 | repo = localrepo.instance(ui, tempparent, create=True) | ||
|
r39640 | except Exception: | ||
shutil.rmtree(tempparent) | ||||
raise | ||||
|
r39980 | class derivedbundlerepository(bundlerepository, repo.__class__): | ||
|
r39640 | pass | ||
repo.__class__ = derivedbundlerepository | ||||
bundlerepository.__init__(repo, bundlepath, url, tempparent) | ||||
return repo | ||||
|
r12734 | |||
|
r23633 | class bundletransactionmanager(object): | ||
def transaction(self): | ||||
return None | ||||
def close(self): | ||||
raise NotImplementedError | ||||
def release(self): | ||||
raise NotImplementedError | ||||
|
r37660 | def getremotechanges(ui, repo, peer, onlyheads=None, bundlename=None, | ||
|
r14073 | force=False): | ||
|
r37660 | '''obtains a bundle of changes incoming from peer | ||
|
r14161 | |||
"onlyheads" restricts the returned changes to those reachable from the | ||||
specified heads. | ||||
"bundlename", if given, stores the bundle to this file path permanently; | ||||
|
r14190 | otherwise it's stored to a temp file and gets deleted again when you call | ||
the returned "cleanupfn". | ||||
|
r14161 | "force" indicates whether to proceed on unrelated repos. | ||
Returns a tuple (local, csets, cleanupfn): | ||||
|
r16683 | "local" is a local repo from which to obtain the actual incoming | ||
changesets; it is a bundlerepo for the obtained bundle when the | ||||
|
r37660 | original "peer" is remote. | ||
|
r14161 | "csets" lists the incoming changeset node ids. | ||
|
r16683 | "cleanupfn" must be called without arguments when you're done processing | ||
|
r37660 | the changes; it closes both the original "peer" and the one returned | ||
|
r16683 | here. | ||
|
r14161 | ''' | ||
|
r37660 | tmp = discovery.findcommonincoming(repo, peer, heads=onlyheads, | ||
|
r16683 | force=force) | ||
|
r12734 | common, incoming, rheads = tmp | ||
if not incoming: | ||||
try: | ||||
|
r15091 | if bundlename: | ||
|
r21694 | os.unlink(bundlename) | ||
|
r14004 | except OSError: | ||
|
r12734 | pass | ||
|
r37660 | return repo, [], peer.close | ||
|
r12734 | |||
|
r22182 | commonset = set(common) | ||
rheads = [x for x in rheads if x not in commonset] | ||||
|
r12734 | bundle = None | ||
|
r14161 | bundlerepo = None | ||
|
r37660 | localrepo = peer.local() | ||
|
r17191 | if bundlename or not localrepo: | ||
|
r37660 | # create a bundle (uncompressed if peer repo is not local) | ||
|
r12734 | |||
|
r29684 | # developer config: devel.legacy.exchange | ||
legexc = ui.configlist('devel', 'legacy.exchange') | ||||
|
r29689 | forcebundle1 = 'bundle2' not in legexc and 'bundle1' in legexc | ||
|
r29684 | canbundle2 = (not forcebundle1 | ||
|
r37660 | and peer.capable('getbundle') | ||
and peer.capable('bundle2')) | ||||
|
r26544 | if canbundle2: | ||
|
r37661 | with peer.commandexecutor() as e: | ||
b2 = e.callcommand('getbundle', { | ||||
'source': 'incoming', | ||||
'common': common, | ||||
'heads': rheads, | ||||
'bundlecaps': exchange.caps20to10(repo, role='client'), | ||||
'cg': True, | ||||
}).result() | ||||
fname = bundle = changegroup.writechunks(ui, | ||||
b2._forwardchunks(), | ||||
bundlename) | ||||
|
r26544 | else: | ||
|
r37660 | if peer.capable('getbundle'): | ||
|
r37661 | with peer.commandexecutor() as e: | ||
cg = e.callcommand('getbundle', { | ||||
'source': 'incoming', | ||||
'common': common, | ||||
'heads': rheads, | ||||
}).result() | ||||
|
r37660 | elif onlyheads is None and not peer.capable('changegroupsubset'): | ||
|
r26543 | # compat with older servers when pulling all remote heads | ||
|
r37653 | |||
|
r37660 | with peer.commandexecutor() as e: | ||
|
r37653 | cg = e.callcommand('changegroup', { | ||
'nodes': incoming, | ||||
'source': 'incoming', | ||||
}).result() | ||||
|
r26543 | rheads = None | ||
else: | ||||
|
r37660 | with peer.commandexecutor() as e: | ||
|
r37653 | cg = e.callcommand('changegroupsubset', { | ||
'bases': incoming, | ||||
'heads': rheads, | ||||
'source': 'incoming', | ||||
}).result() | ||||
|
r26543 | if localrepo: | ||
bundletype = "HG10BZ" | ||||
else: | ||||
bundletype = "HG10UN" | ||||
|
r28666 | fname = bundle = bundle2.writebundle(ui, cg, bundlename, | ||
|
r26543 | bundletype) | ||
|
r12734 | # keep written bundle? | ||
if bundlename: | ||||
bundle = None | ||||
|
r17191 | if not localrepo: | ||
|
r12734 | # use the created uncompressed bundlerepo | ||
|
r39639 | localrepo = bundlerepo = makebundlerepository(repo. baseui, | ||
repo.root, | ||||
fname) | ||||
|
r37660 | # this repo contains local and peer now, so filter out local again | ||
|
r14161 | common = repo.heads() | ||
|
r18568 | if localrepo: | ||
# Part of common may be remotely filtered | ||||
# So use an unfiltered version | ||||
# The discovery process probably need cleanup to avoid that | ||||
localrepo = localrepo.unfiltered() | ||||
|
r14161 | |||
|
r14412 | csets = localrepo.changelog.findmissing(common, rheads) | ||
|
r12734 | |||
|
r23633 | if bundlerepo: | ||
reponodes = [ctx.node() for ctx in bundlerepo[bundlerepo.firstnewrev:]] | ||||
|
r37661 | |||
with peer.commandexecutor() as e: | ||||
remotephases = e.callcommand('listkeys', { | ||||
'namespace': 'phases', | ||||
}).result() | ||||
|
r23633 | |||
|
r37660 | pullop = exchange.pulloperation(bundlerepo, peer, heads=reponodes) | ||
|
r23633 | pullop.trmanager = bundletransactionmanager() | ||
exchange._pullapplyphases(pullop, remotephases) | ||||
|
r14161 | def cleanup(): | ||
if bundlerepo: | ||||
bundlerepo.close() | ||||
if bundle: | ||||
|
r21694 | os.unlink(bundle) | ||
|
r37660 | peer.close() | ||
|
r14161 | |||
return (localrepo, csets, cleanup) | ||||