[PATCH 6 of 6] peer: introduce peer and local/http/ssh-peer

Peter Arrenbrecht peter.arrenbrecht at gmail.com
Wed Jun 8 11:45:33 CDT 2011


# HG changeset patch
# User Peter Arrenbrecht <peter.arrenbrecht at gmail.com>
# Date 1307551088 -7200
peer: introduce peer and local/http/ssh-peer

This change actually separates peer implementations from the repository
implementation. localpeer currently is a simple pass-through to
localrepository, except for legacy calls, which have already been
removed from localpeer. This ensures that the local client code only
uses the most modern peer API when talking to local repos.

diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -10,7 +10,7 @@
 from i18n import _, gettext
 import os, re, sys, difflib, time, tempfile, errno
 import hg, scmutil, util, revlog, extensions, copies, error, bookmarks
-import patch, help, url, encoding, templatekw, discovery
+import patch, help, url, encoding, templatekw, discovery, localrepo
 import archival, changegroup, cmdutil, sshserver, hbisect, hgweb, hgweb.server
 import merge as mergemod
 import minirst, revset, fileset
@@ -1548,10 +1548,13 @@
     # make sure tests are repeatable
     random.seed(12323)
 
-    def doit(localheads, remoteheads):
+    def doit(localheads, remoteheads, remote=remote):
         if opts.get('old'):
             if localheads:
                 raise util.Abort('cannot use localheads with old style discovery')
+            if not hasattr(remote, 'branches'):
+                # enable in-client legacy support
+                remote = localrepo.locallegacypeer(remote.repo())
             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
 import cmdutil, discovery
 import merge as mergemod
@@ -35,7 +35,7 @@
 
     def primary(branch):
         if branch == '.':
-            if not lrepo or not lrepo.local():
+            if not lrepo:
                 raise util.Abort(_("dirstate branch not accessible"))
             branch = lrepo.dirstate.branch()
         if branch in branchmap:
@@ -65,9 +65,9 @@
 schemes = {
     'bundle': bundlerepo,
     'file': _local,
-    'http': httprepo,
-    'https': httprepo,
-    'ssh': sshrepo,
+    'http': httppeer,
+    'https': httppeer,
+    'ssh': sshpeer,
     'static-http': statichttprepo,
 }
 
@@ -130,7 +130,7 @@
 
 def peer(ui, opts, url, create=False):
     """return a repository peer for the specified url"""
-    return _repoorpeer(_peerui(ui, opts), url, create)
+    return _repoorpeer(_peerui(ui, opts), url, create).peer()
 
 def repository(ui, path='', create=False):
     """return a repository object for the specified path"""
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 <mpm at selenic.com>
 # Copyright 2006 Vadim Gelfer <vadim.gelfer at gmail.com>
@@ -23,7 +23,7 @@
         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
@@ -55,7 +55,7 @@
     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()
@@ -65,8 +65,6 @@
                           (' '.join(self.caps or ['none'])))
         return self.caps
 
-    capabilities = property(get_caps)
-
     def lock(self):
         raise util.Abort(_('operation not supported over http'))
 
@@ -206,21 +204,21 @@
     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
@@ -7,7 +7,7 @@
 
 from node import bin, hex, nullid, nullrev, short
 from i18n import _
-import repo, changegroup, subrepo, discovery, pushkey
+import peer, changegroup, subrepo, discovery, pushkey
 import changelog, dirstate, filelog, manifest, context, bookmarks
 import lock, transaction, store, encoding
 import scmutil, util, extensions, hook, error
@@ -18,15 +18,98 @@
 import weakref, errno, os, time, inspect
 propertycache = util.propertycache
 
-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 repo(self):
+        return self._repo
+
+    def close(self):
+        self._repo.close()
+
+    def _capabilities(self):
+        return self._caps
+
+    def local(self):
+        return self._repo.local() # so statichttprepo can override
+
+    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, lock=None):
+        return self._repo.addchangegroup(cg, source, url, lock=lock)
+
+    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'))
 
     def __init__(self, baseui, path=None, create=False):
-        repo.repository.__init__(self)
         self.root = os.path.realpath(util.expandpath(path))
         self.path = os.path.join(self.root, ".hg")
         self.origroot = path
@@ -110,6 +193,15 @@
         self._datafilters = {}
         self._transref = self._lockref = self._wlockref = None
 
+    def repo(self):
+        return self
+
+    def close(self):
+        pass
+
+    def _restrictcapabilities(self, caps):
+        return caps
+
     def _applyrequirements(self, requirements):
         self.requirements = requirements
         openerreqs = set(('revlogv1', 'generaldelta'))
@@ -159,6 +251,9 @@
                 parts.pop()
         return False
 
+    def peer(self):
+        return localpeer(self) # not cached to avoid reference cycle
+
     @util.propertycache
     def _bookmarks(self):
         return bookmarks.read(self)
@@ -565,6 +660,9 @@
     def local(self):
         return True
 
+    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
@@ -9,14 +9,9 @@
 from i18n import _
 import error
 
-class repository(object):
-
-    def repo(self):
-        '''return peer as a repository - currently a no-op'''
-        return self
+class peerrepository(object):
 
     def peer(self):
-        '''return the peer view of this repo - currently a no-op'''
         return self
 
     def capable(self, name):
@@ -24,10 +19,11 @@
         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
@@ -43,7 +39,7 @@
         return False
 
     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
@@ -18,7 +18,7 @@
         if self.repo:
             self.release()
 
-class sshrepository(wireproto.wirerepository):
+class sshpeer(wireproto.wirepeer):
     def __init__(self, ui, path, create=False):
         self._url = path
         self.ui = ui
@@ -81,12 +81,15 @@
         else:
             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
@@ -211,4 +214,4 @@
         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
@@ -115,6 +115,7 @@
         self.spath = self.store.path
         self.sopener = self.store.opener
         self.sjoin = self.store.join
+        self.requirements = requirements
 
         self.manifest = manifest.manifest(self.sopener)
         self.changelog = changelog.changelog(self.sopener)
@@ -124,7 +125,9 @@
         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
diff --git a/mercurial/wireproto.py b/mercurial/wireproto.py
--- a/mercurial/wireproto.py
+++ b/mercurial/wireproto.py
@@ -9,7 +9,7 @@
 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 pushkey as pushkeymod
 
 # list of nodes encoding / decoding
@@ -24,7 +24,8 @@
 
 # client side
 
-class wirerepository(repo.repository):
+class wirepeer(peer.peerrepository):
+
     def lookup(self, key):
         self.requirecap('lookup', _('look up remote revision'))
         d = self._call("lookup", key=encoding.fromlocal(key))
diff --git a/tests/notcapable b/tests/notcapable
--- a/tests/notcapable
+++ b/tests/notcapable
@@ -6,13 +6,18 @@
 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


More information about the Mercurial-devel mailing list