diff --git a/hgext/largefiles/proto.py b/hgext/largefiles/proto.py --- a/hgext/largefiles/proto.py +++ b/hgext/largefiles/proto.py @@ -6,7 +6,7 @@ import os import urllib2 -from mercurial import error, httprepo, util, wireproto +from mercurial import error, httppeer, util, wireproto from mercurial.wireproto import batchable, future from mercurial.i18n import _ @@ -82,7 +82,7 @@ def wirereposetup(ui, repo): # unfortunately, httprepository._callpush tries to convert its # input file-like into a bundle before sending it, so we can't use # it ... - if issubclass(self.__class__, httprepo.httprepository): + if issubclass(self.__class__, httppeer.httppeer): res = None try: res = self._call('putlfile', data=fd, sha=sha, diff --git a/hgext/largefiles/uisetup.py b/hgext/largefiles/uisetup.py --- a/hgext/largefiles/uisetup.py +++ b/hgext/largefiles/uisetup.py @@ -9,7 +9,7 @@ '''setup for largefiles extension: uisetup''' from mercurial import archival, cmdutil, commands, extensions, filemerge, hg, \ - httprepo, localrepo, merge, sshrepo, sshserver, wireproto + httppeer, localrepo, merge, sshpeer, sshserver, wireproto from mercurial.i18n import _ from mercurial.hgweb import hgweb_mod, protocol, webcommands from mercurial.subrepo import hgsubrepo @@ -143,10 +143,10 @@ def uisetup(ui): # can't do this in reposetup because it needs to have happened before # wirerepo.__init__ is called - proto.ssholdcallstream = sshrepo.sshrepository._callstream - proto.httpoldcallstream = httprepo.httprepository._callstream - sshrepo.sshrepository._callstream = proto.sshrepocallstream - httprepo.httprepository._callstream = proto.httprepocallstream + proto.ssholdcallstream = sshpeer.sshpeer._callstream + proto.httpoldcallstream = httppeer.httppeer._callstream + sshpeer.sshpeer._callstream = proto.sshrepocallstream + httppeer.httppeer._callstream = proto.httprepocallstream # don't die on seeing a repo with the largefiles requirement localrepo.localrepository.supported |= set(['largefiles']) diff --git a/mercurial/commands.py b/mercurial/commands.py --- a/mercurial/commands.py +++ b/mercurial/commands.py @@ -16,7 +16,7 @@ import sshserver, hgweb, hgweb.server, c import merge as mergemod import minirst, revset, fileset import dagparser, context, simplemerge, graphmod -import random, setdiscovery, treediscovery, dagutil, pvec +import random, setdiscovery, treediscovery, dagutil, pvec, localrepo import phases, obsolete table = {} @@ -1789,6 +1789,9 @@ def debugdiscovery(ui, repo, remoteurl=" if localheads: raise util.Abort('cannot use localheads with old style ' 'discovery') + if not util.safehasattr(remote, 'branches'): + # enable in-client legacy support + remote = localrepo.locallegacypeer(remote.local()) common, _in, hds = treediscovery.findcommonincoming(repo, remote, force=True) common = set(common) diff --git a/mercurial/hg.py b/mercurial/hg.py --- a/mercurial/hg.py +++ b/mercurial/hg.py @@ -9,7 +9,7 @@ from i18n import _ from lock import release from node import hex, nullid -import localrepo, bundlerepo, httprepo, sshrepo, statichttprepo, bookmarks +import localrepo, bundlerepo, httppeer, sshpeer, statichttprepo, bookmarks import lock, util, extensions, error, node, scmutil import cmdutil, discovery import merge as mergemod @@ -65,9 +65,9 @@ def parseurl(path, branches=None): schemes = { 'bundle': bundlerepo, 'file': _local, - 'http': httprepo, - 'https': httprepo, - 'ssh': sshrepo, + 'http': httppeer, + 'https': httppeer, + 'ssh': sshpeer, 'static-http': statichttprepo, } diff --git a/mercurial/httprepo.py b/mercurial/httppeer.py rename from mercurial/httprepo.py rename to mercurial/httppeer.py --- a/mercurial/httprepo.py +++ b/mercurial/httppeer.py @@ -1,4 +1,4 @@ -# httprepo.py - HTTP repository proxy classes for mercurial +# httppeer.py - HTTP repository proxy classes for mercurial # # Copyright 2005, 2006 Matt Mackall # Copyright 2006 Vadim Gelfer @@ -23,7 +23,7 @@ def zgenerator(f): raise IOError(None, _('connection ended unexpectedly')) yield zd.flush() -class httprepository(wireproto.wirerepository): +class httppeer(wireproto.wirepeer): def __init__(self, ui, path): self.path = path self.caps = None @@ -56,7 +56,7 @@ class httprepository(wireproto.wirerepos def _fetchcaps(self): self.caps = set(self._call('capabilities').split()) - def get_caps(self): + def _capabilities(self): if self.caps is None: try: self._fetchcaps() @@ -66,8 +66,6 @@ class httprepository(wireproto.wirerepos (' '.join(self.caps or ['none']))) return self.caps - capabilities = property(get_caps) - def lock(self): raise util.Abort(_('operation not supported over http')) @@ -215,21 +213,21 @@ class httprepository(wireproto.wirerepos def _decompress(self, stream): return util.chunkbuffer(zgenerator(stream)) -class httpsrepository(httprepository): +class httpspeer(httppeer): def __init__(self, ui, path): if not url.has_https: raise util.Abort(_('Python support for SSL and HTTPS ' 'is not installed')) - httprepository.__init__(self, ui, path) + httppeer.__init__(self, ui, path) def instance(ui, path, create): if create: raise util.Abort(_('cannot create new http repository')) try: if path.startswith('https:'): - inst = httpsrepository(ui, path) + inst = httpspeer(ui, path) else: - inst = httprepository(ui, path) + inst = httppeer(ui, path) try: # Try to do useful work when checking compatibility. # Usually saves a roundtrip since we want the caps anyway. diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py --- a/mercurial/localrepo.py +++ b/mercurial/localrepo.py @@ -6,7 +6,7 @@ # GNU General Public License version 2 or any later version. from node import bin, hex, nullid, nullrev, short from i18n import _ -import repo, changegroup, subrepo, discovery, pushkey, obsolete +import peer, changegroup, subrepo, discovery, pushkey, obsolete import changelog, dirstate, filelog, manifest, context, bookmarks, phases import lock, transaction, store, encoding, base85 import scmutil, util, extensions, hook, error, revset @@ -23,9 +23,90 @@ class storecache(filecache): def join(self, obj, fname): return obj.sjoin(fname) -class localrepository(repo.repository): - capabilities = set(('lookup', 'changegroupsubset', 'branchmap', 'pushkey', - 'known', 'getbundle')) +MODERNCAPS = set(('lookup', 'branchmap', 'pushkey', 'known', 'getbundle')) +LEGACYCAPS = MODERNCAPS.union(set(['changegroupsubset'])) + +class localpeer(peer.peerrepository): + '''peer for a local repo; reflects only the most recent API''' + + def __init__(self, repo, caps=MODERNCAPS): + peer.peerrepository.__init__(self) + self._repo = repo + self.ui = repo.ui + self._caps = repo._restrictcapabilities(caps) + self.requirements = repo.requirements + self.supportedformats = repo.supportedformats + + def close(self): + self._repo.close() + + def _capabilities(self): + return self._caps + + def local(self): + return self._repo + + def cancopy(self): + return self._repo.cancopy() # so bundlerepo can override + + def url(self): + return self._repo.url() + + def lookup(self, key): + return self._repo.lookup(key) + + def branchmap(self): + return self._repo.branchmap() + + def heads(self): + return self._repo.heads() + + def known(self, nodes): + return self._repo.known(nodes) + + def getbundle(self, source, heads=None, common=None): + return self._repo.getbundle(source, heads=heads, common=common) + + # TODO We might want to move the next two calls into legacypeer and add + # unbundle instead. + + def lock(self): + return self._repo.lock() + + def addchangegroup(self, cg, source, url): + return self._repo.addchangegroup(cg, source, url) + + def pushkey(self, namespace, key, old, new): + return self._repo.pushkey(namespace, key, old, new) + + def listkeys(self, namespace): + return self._repo.listkeys(namespace) + + def debugwireargs(self, one, two, three=None, four=None, five=None): + '''used to test argument passing over the wire''' + return "%s %s %s %s %s" % (one, two, three, four, five) + +class locallegacypeer(localpeer): + '''peer extension which implements legacy methods too; used for tests with + restricted capabilities''' + + def __init__(self, repo): + localpeer.__init__(self, repo, caps=LEGACYCAPS) + + def branches(self, nodes): + return self._repo.branches(nodes) + + def between(self, pairs): + return self._repo.between(pairs) + + def changegroup(self, basenodes, source): + return self._repo.changegroup(basenodes, source) + + def changegroupsubset(self, bases, heads, source): + return self._repo.changegroupsubset(bases, heads, source) + +class localrepository(object): + supportedformats = set(('revlogv1', 'generaldelta')) supported = supportedformats | set(('store', 'fncache', 'shared', 'dotencode')) @@ -36,7 +117,6 @@ class localrepository(repo.repository): return self.requirements[:] def __init__(self, baseui, path=None, create=False): - repo.repository.__init__(self) self.wopener = scmutil.opener(path, expand=True) self.wvfs = self.wopener self.root = self.wvfs.base @@ -126,6 +206,12 @@ class localrepository(repo.repository): # Maps a property name to its util.filecacheentry self._filecache = {} + def close(self): + pass + + def _restrictcapabilities(self, caps): + return caps + def _applyrequirements(self, requirements): self.requirements = requirements self.sopener.options = dict((r, 1) for r in requirements @@ -175,6 +261,9 @@ class localrepository(repo.repository): parts.pop() return False + def peer(self): + return localpeer(self) # not cached to avoid reference cycle + @filecache('bookmarks') def _bookmarks(self): return bookmarks.read(self) @@ -668,6 +757,9 @@ class localrepository(repo.repository): def local(self): return self + def cancopy(self): + return self.local() # so statichttprepo's override of local() works + def join(self, f): return os.path.join(self.path, f) diff --git a/mercurial/repo.py b/mercurial/peer.py rename from mercurial/repo.py rename to mercurial/peer.py --- a/mercurial/repo.py +++ b/mercurial/peer.py @@ -1,4 +1,4 @@ -# repo.py - repository base classes for mercurial +# peer.py - repository base classes for mercurial # # Copyright 2005, 2006 Matt Mackall # Copyright 2006 Vadim Gelfer @@ -9,16 +9,18 @@ from i18n import _ import error -class repository(object): +class peerrepository(object): + def capable(self, name): '''tell whether repo supports named capability. return False if not supported. if boolean capability, return True. if string capability, return string.''' - if name in self.capabilities: + caps = self._capabilities() + if name in caps: return True name_eq = name + '=' - for cap in self.capabilities: + for cap in caps: if cap.startswith(name_eq): return cap[len(name_eq):] return False @@ -31,13 +33,17 @@ class repository(object): 'support the %r capability') % (purpose, name)) def local(self): - return False + '''return peer as a localrepo, or None''' + return None + + def peer(self): + return self def peer(self): return self def cancopy(self): - return self.local() + return False def close(self): pass diff --git a/mercurial/sshrepo.py b/mercurial/sshpeer.py rename from mercurial/sshrepo.py rename to mercurial/sshpeer.py --- a/mercurial/sshrepo.py +++ b/mercurial/sshpeer.py @@ -1,4 +1,4 @@ -# sshrepo.py - ssh repository proxy class for mercurial +# sshpeer.py - ssh repository proxy class for mercurial # # Copyright 2005, 2006 Matt Mackall # @@ -25,7 +25,7 @@ def _serverquote(s): return s return "'%s'" % s.replace("'", "'\\''") -class sshrepository(wireproto.wirerepository): +class sshpeer(wireproto.wirepeer): def __init__(self, ui, path, create=False): self._url = path self.ui = ui @@ -90,12 +90,15 @@ class sshrepository(wireproto.wirereposi self._abort(error.RepoError(_('no suitable response from ' 'remote hg'))) - self.capabilities = set() + self._caps = set() for l in reversed(lines): if l.startswith("capabilities:"): - self.capabilities.update(l[:-1].split(":")[1].split()) + self._caps.update(l[:-1].split(":")[1].split()) break + def _capabilities(self): + return self._caps + def readerr(self): while True: size = util.fstat(self.pipee).st_size @@ -233,4 +236,4 @@ class sshrepository(wireproto.wirereposi except ValueError: self._abort(error.ResponseError(_("unexpected response:"), r)) -instance = sshrepository +instance = sshpeer diff --git a/mercurial/statichttprepo.py b/mercurial/statichttprepo.py --- a/mercurial/statichttprepo.py +++ b/mercurial/statichttprepo.py @@ -76,6 +76,10 @@ def build_opener(ui, authinfo): return statichttpopener +class statichttppeer(localrepo.localpeer): + def local(self): + return None + class statichttprepository(localrepo.localrepository): def __init__(self, ui, path): self._url = path @@ -116,6 +120,7 @@ class statichttprepository(localrepo.loc self.svfs = self.sopener self.sjoin = self.store.join self._filecache = {} + self.requirements = requirements self.manifest = manifest.manifest(self.sopener) self.changelog = changelog.changelog(self.sopener) @@ -125,7 +130,9 @@ class statichttprepository(localrepo.loc self._branchcachetip = None self.encodepats = None self.decodepats = None - self.capabilities.difference_update(["pushkey"]) + + def _restrictcapabilities(self, caps): + return caps.difference(["pushkey"]) def url(self): return self._url @@ -133,6 +140,9 @@ class statichttprepository(localrepo.loc def local(self): return False + def peer(self): + return statichttppeer(self) + def lock(self, wait=True): raise util.Abort(_('cannot lock static-http repository')) diff --git a/mercurial/wireproto.py b/mercurial/wireproto.py --- a/mercurial/wireproto.py +++ b/mercurial/wireproto.py @@ -9,7 +9,7 @@ import urllib, tempfile, os, sys from i18n import _ from node import bin, hex import changegroup as changegroupmod -import repo, error, encoding, util, store +import peer, error, encoding, util, store import phases # abstract batching support @@ -149,7 +149,7 @@ def unescapearg(escaped): def todict(**args): return args -class wirerepository(repo.repository): +class wirepeer(peer.peerrepository): def batch(self): return remotebatch(self) diff --git a/tests/notcapable b/tests/notcapable --- a/tests/notcapable +++ b/tests/notcapable @@ -6,13 +6,18 @@ then fi cat > notcapable-$CAP.py << EOF -from mercurial import extensions, repo +from mercurial import extensions, peer, localrepo def extsetup(): - extensions.wrapfunction(repo.repository, 'capable', wrapper) -def wrapper(orig, self, name, *args, **kwargs): + extensions.wrapfunction(peer.peerrepository, 'capable', wrapcapable) + extensions.wrapfunction(localrepo.localrepository, 'peer', wrappeer) +def wrapcapable(orig, self, name, *args, **kwargs): if name in '$CAP'.split(' '): return False return orig(self, name, *args, **kwargs) +def wrappeer(orig, self): + # Since we're disabling some newer features, we need to make sure local + # repos add in the legacy features again. + return localrepo.locallegacypeer(self) EOF echo '[extensions]' >> $HGRCPATH diff --git a/tests/test-wireproto.py b/tests/test-wireproto.py --- a/tests/test-wireproto.py +++ b/tests/test-wireproto.py @@ -9,7 +9,7 @@ class proto(object): names = spec.split() return [args[n] for n in names] -class clientrepo(wireproto.wirerepository): +class clientpeer(wireproto.wirepeer): def __init__(self, serverrepo): self.serverrepo = serverrepo def _call(self, cmd, **args): @@ -36,7 +36,7 @@ def greet(repo, proto, name): wireproto.commands['greet'] = (greet, 'name',) srv = serverrepo() -clt = clientrepo(srv) +clt = clientpeer(srv) print clt.greet("Foobar") b = clt.batch()