[PATCH RFC] repo: differentiate between repos and proxies for potentially remotes

Peter Arrenbrecht peter.arrenbrecht at gmail.com
Fri Jun 3 13:37:46 CDT 2011


# HG changeset patch
# User Peter Arrenbrecht <peter.arrenbrecht at gmail.com>
# Date 1307126195 -7200
# Node ID 063cd4fae0ee0606678ed22dbaf1c77948301662
# Parent  1ffeeb91c55d0b00445ceabde14a0d0faf906a33
repo: differentiate between repos and proxies for potentially remotes

Currently, there is no clear distinction between working with a
known-to-be local repo and proxies for remote repos. So if tests don't
actually exercise code that could accept remote proxies with actual remote
repos, we won't catch improper calls.

This patch introduces localproxy, which has basically the same contract
as wirerepository, but for localrepository instances. Commands which want
to work with possibly remote repos now have to use hg.repoproxy() instead
of hg.repository(). The latter newly enforces that it only returns local
repos.

I also split the local proxy into localproxy and localwireproto. The
former only exposes the most modern methods to ensure that these are the
only ones used against servers supporting the most modern set.

Then there is localwireproto which adds in the legacy commands so the
server can still expose them to older clients.

The last change in sshrepo.py actually fixes a bug with new hg against
old ssh servers (those not supporting unbundle), I think.

diff --git a/hgext/convert/hg.py b/hgext/convert/hg.py
--- a/hgext/convert/hg.py
+++ b/hgext/convert/hg.py
@@ -112,7 +112,7 @@
             self.after()
             for pbranch, heads in missings.iteritems():
                 pbranchpath = os.path.join(self.path, pbranch)
-                prepo = hg.repository(self.ui, pbranchpath)
+                prepo = hg.repoproxy(self.ui, pbranchpath)
                 self.ui.note(_('pulling from %s into %s\n') % (pbranch, branch))
                 self.repo.pull(prepo, [prepo.lookup(h) for h in heads])
             self.before()
diff --git a/hgext/fetch.py b/hgext/fetch.py
--- a/hgext/fetch.py
+++ b/hgext/fetch.py
@@ -63,8 +63,8 @@
             raise util.Abort(_('multiple heads in this branch '
                                '(use "hg heads ." and "hg merge" to merge)'))
 
-        other = hg.repository(hg.remoteui(repo, opts),
-                              ui.expandpath(source))
+        other = hg.repoproxy(hg.remoteui(repo, opts),
+                             ui.expandpath(source))
         ui.status(_('pulling from %s\n') %
                   util.hidepassword(ui.expandpath(source)))
         revs = None
diff --git a/hgext/mq.py b/hgext/mq.py
--- a/hgext/mq.py
+++ b/hgext/mq.py
@@ -2060,24 +2060,25 @@
         return url + '/.hg/patches'
     if dest is None:
         dest = hg.defaultdest(source)
-    sr = hg.repository(hg.remoteui(ui, opts), ui.expandpath(source))
+    sr = hg.repoproxy(hg.remoteui(ui, opts), ui.expandpath(source))
     if opts.get('patches'):
         patchespath = ui.expandpath(opts.get('patches'))
     else:
         patchespath = patchdir(sr)
     try:
-        hg.repository(ui, patchespath)
+        hg.repoproxy(ui, patchespath)
     except error.RepoError:
         raise util.Abort(_('versioned patch repository not found'
                            ' (see init --mq)'))
     qbase, destrev = None, None
     if sr.local():
-        if sr.mq.applied:
-            qbase = sr.mq.applied[0].node
+        repo = sr.repo
+        if repo.mq.applied:
+            qbase = repo.mq.applied[0].node
             if not hg.islocal(dest):
-                heads = set(sr.heads())
-                destrev = list(heads.difference(sr.heads(qbase)))
-                destrev.append(sr.changelog.parents(qbase)[0])
+                heads = set(repo.heads())
+                destrev = list(heads.difference(repo.heads(qbase)))
+                destrev.append(repo.changelog.parents(qbase)[0])
     elif sr.capable('lookup'):
         try:
             qbase = sr.lookup('qbase')
@@ -2094,13 +2095,14 @@
              pull=opts.get('pull'), update=not opts.get('noupdate'),
              stream=opts.get('uncompressed'))
     if dr.local():
+        repo = dr.repo
         if qbase:
             ui.note(_('stripping applied patches from destination '
                       'repository\n'))
-            dr.mq.strip(dr, [qbase], update=False, backup=None)
+            repo.mq.strip(repo, [qbase], update=False, backup=None)
         if not opts.get('noupdate'):
             ui.note(_('updating destination repository\n'))
-            hg.update(dr, dr.changelog.tip())
+            hg.update(repo, repo.changelog.tip())
 
 @command("qcommit|qci",
          commands.table["^commit|ci"][1],
diff --git a/hgext/patchbomb.py b/hgext/patchbomb.py
--- a/hgext/patchbomb.py
+++ b/hgext/patchbomb.py
@@ -276,7 +276,7 @@
         dest = ui.expandpath(dest or 'default-push', dest or 'default')
         dest, branches = hg.parseurl(dest)
         revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
-        other = hg.repository(hg.remoteui(repo, opts), dest)
+        other = hg.repoproxy(hg.remoteui(repo, opts), dest)
         ui.status(_('comparing with %s\n') % util.hidepassword(dest))
         common, _anyinc, _heads = discovery.findcommonincoming(repo, other)
         nodes = revs and map(repo.lookup, revs) or revs
diff --git a/hgext/relink.py b/hgext/relink.py
--- a/hgext/relink.py
+++ b/hgext/relink.py
@@ -41,8 +41,6 @@
     src = hg.repository(hg.remoteui(repo, opts),
                         ui.expandpath(origin or 'default-relink',
                                       origin or 'default'))
-    if not src.local():
-        raise util.Abort(_('must specify local origin repository'))
     ui.status(_('relinking %s to %s\n') % (src.store.path, repo.store.path))
     if repo.root == src.root:
         ui.status(_('there is nothing to relink\n'))
diff --git a/hgext/transplant.py b/hgext/transplant.py
--- a/hgext/transplant.py
+++ b/hgext/transplant.py
@@ -128,7 +128,7 @@
                         continue
                     if pulls:
                         if source != repo:
-                            repo.pull(source, heads=pulls)
+                            repo.pull(source.proxy(), heads=pulls)
                         merge.update(repo, pulls[-1], False, False, None)
                         p1, p2 = repo.dirstate.parents()
                         pulls = []
@@ -173,7 +173,7 @@
                         if patchfile:
                             os.unlink(patchfile)
             if pulls:
-                repo.pull(source, heads=pulls)
+                repo.pull(source.proxy(), heads=pulls)
                 merge.update(repo, pulls[-1], False, False, None)
         finally:
             self.saveseries(revmap, merges)
@@ -561,10 +561,11 @@
 
     sourcerepo = opts.get('source')
     if sourcerepo:
-        source = hg.repository(ui, ui.expandpath(sourcerepo))
-        branches = map(source.lookup, opts.get('branch', ()))
-        source, csets, cleanupfn = bundlerepo.getremotechanges(ui, repo, source,
+        proxy = hg.repoproxy(hg.remoteui(ui, opts), ui.expandpath(sourcerepo))
+        branches = map(proxy.lookup, opts.get('branch', ()))
+        source, csets, cleanupfn = bundlerepo.getremotechanges(ui, repo, proxy,
                                     onlyheads=branches, force=True)
+        source = hasattr(source, 'localrepo') and source.localrepo() or source
     else:
         source = repo
         branches = map(source.lookup, opts.get('branch', ()))
diff --git a/mercurial/bundlerepo.py b/mercurial/bundlerepo.py
--- a/mercurial/bundlerepo.py
+++ b/mercurial/bundlerepo.py
@@ -322,8 +322,8 @@
 
     bundle = None
     bundlerepo = None
-    localrepo = other
-    if bundlename or not other.local():
+    localrepo = other.local() and other.repo or None
+    if bundlename or not localrepo:
         # create a bundle (uncompressed if other repo is not local)
 
         if other.capable('getbundle'):
@@ -334,17 +334,16 @@
             rheads = None
         else:
             cg = other.changegroupsubset(incoming, rheads, 'incoming')
-        bundletype = other.local() and "HG10BZ" or "HG10UN"
+        bundletype = localrepo and "HG10BZ" or "HG10UN"
         fname = bundle = changegroup.writebundle(cg, bundlename, bundletype)
         # keep written bundle?
         if bundlename:
             bundle = None
-        if not other.local():
+        if not localrepo:
             # use the created uncompressed bundlerepo
             localrepo = bundlerepo = bundlerepository(ui, repo.root, fname)
             # this repo contains local and other now, so filter out local again
             common = repo.heads()
-
     csets = localrepo.changelog.findmissing(common, rheads)
 
     def cleanup():
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
@@ -891,14 +891,14 @@
     else:
         dest = ui.expandpath(dest or 'default-push', dest or 'default')
         dest, branches = hg.parseurl(dest, opts.get('branch'))
-        other = hg.repository(hg.remoteui(repo, opts), dest)
+        other = hg.repoproxy(hg.remoteui(repo, opts), dest)
         revs, checkout = hg.addbranchrevs(repo, other, branches, revs)
         heads = revs and map(repo.lookup, revs) or revs
         common, outheads = discovery.findcommonoutgoing(repo, other,
                                                         onlyheads=heads,
                                                         force=opts.get('force'))
 
-    cg = repo.getbundle('bundle', common=common, heads=heads)
+    cg = repo._getbundle('bundle', common=common, heads=heads)
     if not cg:
         ui.status(_("no changes found\n"))
         return 1
@@ -1542,16 +1542,19 @@
 def debugdiscovery(ui, repo, remoteurl="default", **opts):
     """runs the changeset discovery protocol in isolation"""
     remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl), opts.get('branch'))
-    remote = hg.repository(hg.remoteui(repo, opts), remoteurl)
+    remote = hg.repoproxy(hg.remoteui(repo, opts), remoteurl)
     ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
 
     # 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'):
+                # re-enable legacy support
+                remote = localrepo.localwireproto(remote.repo)
             common, _in, hds = treediscovery.findcommonincoming(repo, remote,
                                                                 force=True)
             common = set(common)
@@ -1618,7 +1621,7 @@
     Every ID must be a full-length hex node id string. Saves the bundle to the
     given file.
     """
-    repo = hg.repository(ui, repopath)
+    repo = hg.repoproxy(hg.remoteui(ui, opts), repopath)
     if not repo.capable('getbundle'):
         raise util.Abort("getbundle() not supported by target repository")
     args = {}
@@ -1793,14 +1796,14 @@
     Every ID must be a full-length hex node id string. Returns a list of 0s and 1s
     indicating unknown/known.
     """
-    repo = hg.repository(ui, repopath)
+    repo = hg.repoproxy(hg.remoteui(ui, opts), repopath)
     if not repo.capable('known'):
         raise util.Abort("known() not supported by target repository")
     flags = repo.known([bin(s) for s in ids])
     ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
 
 @command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]'))
-def debugpushkey(ui, repopath, namespace, *keyinfo):
+def debugpushkey(ui, repopath, namespace, *keyinfo, **opts):
     '''access the pushkey key/value protocol
 
     With two args, list the keys in the given namespace.
@@ -1809,7 +1812,7 @@
     Reports success or failure.
     '''
 
-    target = hg.repository(ui, repopath)
+    target = hg.repoproxy(hg.remoteui(ui, opts), repopath)
     if keyinfo:
         key, old, new = keyinfo
         r = target.pushkey(namespace, key, old, new)
@@ -2106,7 +2109,7 @@
     ] + remoteopts,
     _('REPO [OPTIONS]... [ONE [TWO]]'))
 def debugwireargs(ui, repopath, *vals, **opts):
-    repo = hg.repository(hg.remoteui(ui, opts), repopath)
+    repo = hg.repoproxy(hg.remoteui(ui, opts), repopath)
     for opt in remoteopts:
         del opts[opt[1]]
     args = {}
@@ -2903,10 +2906,11 @@
 
     if source:
         source, branches = hg.parseurl(ui.expandpath(source))
-        repo = hg.repository(ui, source)
-        revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
-
-    if not repo.local():
+        proxy = hg.repoproxy(ui, source)
+        repo = proxy.local() and proxy.repo or None
+        revs, checkout = hg.addbranchrevs(repo, proxy, branches, None)
+
+    if not repo:
         if num or branch or tags:
             raise util.Abort(
                 _("can't query remote revision number, branch, or tags"))
@@ -2915,16 +2919,16 @@
         if not rev:
             rev = "tip"
 
-        remoterev = repo.lookup(rev)
+        remoterev = proxy.lookup(rev)
         if default or id:
             output = [hexfunc(remoterev)]
 
         def getbms():
             bms = []
 
-            if 'bookmarks' in repo.listkeys('namespaces'):
+            if 'bookmarks' in proxy.listkeys('namespaces'):
                 hexremoterev = hex(remoterev)
-                bms = [bm for bm, bmr in repo.listkeys('bookmarks').iteritems()
+                bms = [bm for bm, bmr in proxy.listkeys('bookmarks').iteritems()
                        if bmr == hexremoterev]
 
             return bms
@@ -3188,7 +3192,7 @@
     if opts.get('bookmarks'):
         source, branches = hg.parseurl(ui.expandpath(source),
                                        opts.get('branch'))
-        other = hg.repository(hg.remoteui(repo, opts), source)
+        other = hg.repoproxy(hg.remoteui(repo, opts), source)
         if 'bookmarks' not in other.listkeys('namespaces'):
             ui.warn(_("remote doesn't support bookmarks\n"))
             return 0
@@ -3216,7 +3220,7 @@
 
     Returns 0 on success.
     """
-    hg.repository(hg.remoteui(ui, opts), ui.expandpath(dest), create=True)
+    hg.repoproxy(hg.remoteui(ui, opts), ui.expandpath(dest), create=True)
 
 @command('locate',
     [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
@@ -3550,7 +3554,7 @@
     if opts.get('bookmarks'):
         dest = ui.expandpath(dest or 'default-push', dest or 'default')
         dest, branches = hg.parseurl(dest, opts.get('branch'))
-        other = hg.repository(hg.remoteui(repo, opts), dest)
+        other = hg.repoproxy(hg.remoteui(repo, opts), dest)
         if 'bookmarks' not in other.listkeys('namespaces'):
             ui.warn(_("remote doesn't support bookmarks\n"))
             return 0
@@ -3702,7 +3706,7 @@
     Returns 0 on success, 1 if an update had unresolved files.
     """
     source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
-    other = hg.repository(hg.remoteui(repo, opts), source)
+    other = hg.repoproxy(hg.remoteui(repo, opts), source).proxy()
     ui.status(_('pulling from %s\n') % util.hidepassword(source))
     revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
 
@@ -3799,7 +3803,7 @@
     dest, branches = hg.parseurl(dest, opts.get('branch'))
     ui.status(_('pushing to %s\n') % util.hidepassword(dest))
     revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
-    other = hg.repository(hg.remoteui(repo, opts), dest)
+    other = hg.repoproxy(hg.remoteui(repo, opts), dest).proxy()
     if revs:
         revs = [repo.lookup(rev) for rev in revs]
 
@@ -4749,7 +4753,7 @@
     if opts.get('remote'):
         t = []
         source, branches = hg.parseurl(ui.expandpath('default'))
-        other = hg.repository(hg.remoteui(repo, {}), source)
+        other = hg.repoproxy(hg.remoteui(repo, {}), source)
         revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
         ui.debug('comparing with %s\n' % util.hidepassword(source))
         repo.ui.pushbuffer()
@@ -4762,7 +4766,7 @@
         dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
         revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
         if source != dest:
-            other = hg.repository(hg.remoteui(repo, {}), dest)
+            other = hg.repoproxy(hg.remoteui(repo, {}), dest)
             commoninc = None
             ui.debug('comparing with %s\n' % util.hidepassword(dest))
         repo.ui.pushbuffer()
diff --git a/mercurial/discovery.py b/mercurial/discovery.py
--- a/mercurial/discovery.py
+++ b/mercurial/discovery.py
@@ -186,5 +186,5 @@
         # use the fast path, no race possible on push
         cg = repo._changegroup(outg, 'push')
     else:
-        cg = repo.getbundle('push', heads=revs, common=common)
+        cg = repo._getbundle('push', heads=revs, common=common)
     return cg, remoteheads
diff --git a/mercurial/dispatch.py b/mercurial/dispatch.py
--- a/mercurial/dispatch.py
+++ b/mercurial/dispatch.py
@@ -595,8 +595,6 @@
         try:
             repo = hg.repository(ui, path=path)
             ui = repo.ui
-            if not repo.local():
-                raise util.Abort(_("repository '%s' is not local") % path)
             ui.setconfig("bundle", "mainreporoot", repo.root)
         except error.RequirementError:
             raise
diff --git a/mercurial/hg.py b/mercurial/hg.py
--- a/mercurial/hg.py
+++ b/mercurial/hg.py
@@ -21,6 +21,7 @@
     return (os.path.isfile(path) and bundlerepo or localrepo)
 
 def addbranchrevs(lrepo, repo, branches, revs):
+    repo = repo.proxy()
     hashbranch, branches = branches
     if not hashbranch and not branches:
         return revs or None, revs and revs[0] or None
@@ -34,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:
@@ -88,7 +89,7 @@
             return False
     return repo.local()
 
-def repository(ui, path='', create=False):
+def repoproxy(ui, path='', create=False):
     """return a repository object for the specified path"""
     repo = _lookup(path).instance(ui, path, create)
     ui = getattr(repo, "ui", ui)
@@ -96,7 +97,14 @@
         hook = getattr(module, 'reposetup', None)
         if hook:
             hook(ui, repo)
-    return repo
+    return repo.proxy()
+
+def repository(ui, path='', create=False):
+    """return a repository object for the specified path"""
+    proxy = repoproxy(ui, path, create)
+    if not proxy.local():
+        raise util.Abort(_("repository '%s' is not local") % (path or proxy.url()))
+    return proxy.repo
 
 def defaultdest(source):
     '''return default destination of clone if none is given'''
@@ -119,7 +127,7 @@
         srcrepo = repository(ui, source)
         rev, checkout = addbranchrevs(srcrepo, srcrepo, branches, None)
     else:
-        srcrepo = source
+        srcrepo = source.repo
         origsource = source = srcrepo.url()
         checkout = None
 
@@ -209,12 +217,12 @@
     if isinstance(source, str):
         origsource = ui.expandpath(source)
         source, branch = parseurl(origsource, branch)
-        srcrepo = repository(ui, source)
+        srcproxy = repoproxy(ui, source)
     else:
-        srcrepo = source
+        srcproxy = source.proxy()
         branch = (None, branch or [])
-        origsource = source = srcrepo.url()
-    rev, checkout = addbranchrevs(srcrepo, srcrepo, branch, rev)
+        origsource = source = srcproxy.url()
+    rev, checkout = addbranchrevs(srcproxy, srcproxy, branch, rev)
 
     if dest is None:
         dest = defaultdest(source)
@@ -251,10 +259,11 @@
             dircleanup = DirCleanup(dest)
 
         copy = False
-        if srcrepo.cancopy() and islocal(dest):
+        if srcproxy.cancopy() and islocal(dest):
             copy = not pull and not rev
 
         if copy:
+            srcrepo = srcproxy.repo
             try:
                 # we use a lock here because if we race with commit, we
                 # can end up with extra data in the cloned revlogs that's
@@ -303,12 +312,12 @@
 
             # we need to re-init the repo after manually copying the data
             # into it
-            destrepo = repository(ui, dest)
+            destproxy = repoproxy(ui, dest)
             srcrepo.hook('outgoing', source='clone',
                           node=node.hex(node.nullid))
         else:
             try:
-                destrepo = repository(ui, dest, create=True)
+                destproxy = repoproxy(ui, dest, create=True)
             except OSError, inst:
                 if inst.errno == errno.EEXIST:
                     dircleanup.close()
@@ -318,23 +327,24 @@
 
             revs = None
             if rev:
-                if 'lookup' not in srcrepo.capabilities:
+                if not srcproxy.capable('lookup'):
                     raise util.Abort(_("src repository does not support "
                                        "revision lookup and so doesn't "
                                        "support clone by revision"))
-                revs = [srcrepo.lookup(r) for r in rev]
+                revs = [srcproxy.lookup(r) for r in rev]
                 checkout = revs[0]
-            if destrepo.local():
-                destrepo.clone(srcrepo, heads=revs, stream=stream)
-            elif srcrepo.local():
-                srcrepo.push(destrepo, revs=revs)
+            if destproxy.local():
+                destproxy.repo.clone(srcproxy, heads=revs, stream=stream)
+            elif srcproxy.local():
+                srcproxy.repo.push(destproxy, revs=revs)
             else:
                 raise util.Abort(_("clone from remote to remote not supported"))
 
         if dircleanup:
             dircleanup.close()
 
-        if destrepo.local():
+        if destproxy.local():
+            destrepo = destproxy.repo
             fp = destrepo.opener("hgrc", "w", text=True)
             fp.write("[paths]\n")
             fp.write("default = %s\n" % abspath)
@@ -345,8 +355,8 @@
             if update:
                 if update is not True:
                     checkout = update
-                    if srcrepo.local():
-                        checkout = srcrepo.lookup(update)
+                    if srcproxy.local(): # FIXME why this test?
+                        checkout = srcproxy.repo.lookup(update)
                 for test in (checkout, 'default', 'tip'):
                     if test is None:
                         continue
@@ -360,8 +370,9 @@
                 _update(destrepo, uprev)
 
         # clone all bookmarks
-        if destrepo.local() and srcrepo.capable("pushkey"):
-            rb = srcrepo.listkeys('bookmarks')
+        if destproxy.local() and srcproxy.capable("pushkey"):
+            destrepo = destproxy.repo
+            rb = srcproxy.listkeys('bookmarks')
             for k, n in rb.iteritems():
                 try:
                     m = destrepo.lookup(n)
@@ -370,11 +381,12 @@
                     pass
             if rb:
                 bookmarks.write(destrepo)
-        elif srcrepo.local() and destrepo.capable("pushkey"):
+        elif srcproxy.local() and destproxy.capable("pushkey"):
+            srcrepo = srcproxy.repo
             for k, n in srcrepo._bookmarks.iteritems():
-                destrepo.pushkey('bookmarks', k, '', hex(n))
+                destproxy.pushkey('bookmarks', k, '', hex(n))
 
-        return srcrepo, destrepo
+        return srcproxy, destproxy
     finally:
         release(srclock, destlock)
         if dircleanup is not None:
@@ -423,7 +435,7 @@
     and is supposed to contain only code that can't be unified.
     """
     source, branches = parseurl(ui.expandpath(source), opts.get('branch'))
-    other = repository(remoteui(repo, opts), source)
+    other = repoproxy(remoteui(repo, opts), source)
     ui.status(_('comparing with %s\n') % util.hidepassword(source))
     revs, checkout = addbranchrevs(repo, other, branches, opts.get('rev'))
 
@@ -481,7 +493,7 @@
     if revs:
         revs = [repo.lookup(rev) for rev in revs]
 
-    other = repository(remoteui(repo, opts), dest)
+    other = repoproxy(remoteui(repo, opts), dest)
     common, outheads = discovery.findcommonoutgoing(repo, other, revs,
                                                     force=opts.get('force'))
     o = repo.changelog.findmissing(common, outheads)
diff --git a/mercurial/httprepo.py b/mercurial/httprepo.py
--- a/mercurial/httprepo.py
+++ b/mercurial/httprepo.py
@@ -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'))
 
diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -18,9 +18,134 @@
 import weakref, errno, os, time, inspect
 propertycache = util.propertycache
 
+MODERNCAPS = set(('lookup', 'branchmap', 'pushkey', 'known', 'getbundle'))
+LEGACYCAPS = MODERNCAPS.union(set(['changegroupsubset']))
+
+class localproxy(repo.repositoryproxy):
+    '''proxy for a local repo; reflects only the most recent API'''
+
+    def capabilities(self):
+        return MODERNCAPS
+
+    def __init__(self, repo):
+        self.repo = repo
+        self.ui = repo.ui
+
+    def localrepo(self):
+        return self.repo
+
+    def close(self):
+        self.repo.close()
+
+    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):
+        nm = self.repo.changelog.nodemap
+        return [(n in nm) for n in nodes]
+
+    def getbundle(self, source, heads=None, common=None):
+        """Like changegroupsubset, but returns the set difference between the
+        ancestors of heads and the ancestors common.
+
+        If heads is None, use the local heads. If common is None, use [nullid].
+
+        The nodes in common might not all be known locally due to the way the
+        current discovery protocol works.
+        """
+        return self.repo._getbundle(source, heads=heads, common=common)
+
+    # FIXME We might want to lose these 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 localwireproto(localproxy):
+    '''proxy extension for use by wireproto; implements legacy methods too'''
+
+    def __init__(self, repo):
+        localproxy.__init__(self, repo)
+        self.requirements = repo.requirements
+        self.supportedformats = repo.supportedformats
+
+    def capabilities(self):
+        return self.repo._capabilities()
+
+    def branches(self, nodes):
+        cl = self.repo.changelog
+        if not nodes:
+            nodes = [cl.tip()]
+        b = []
+        for n in nodes:
+            t = n
+            while True:
+                p = cl.parents(n)
+                if p[1] != nullid or p[0] == nullid:
+                    b.append((t, n, p[0], p[1]))
+                    break
+                n = p[0]
+        return b
+
+    def between(self, pairs):
+        cl = self.repo.changelog
+        r = []
+        for top, bottom in pairs:
+            n, l, i = top, [], 0
+            f = 1
+            while n != bottom and n != nullid:
+                p = cl.parents(n)[0]
+                if i == f:
+                    l.append(n)
+                    f = f * 2
+                n = p
+                i += 1
+            r.append(l)
+        return r
+
+    def changegroup(self, nodes, kind):
+        return self.repo.changegroup(nodes, kind)
+
+    def changegroupsubset(self, bases, heads, kind):
+        return self.repo.changegroupsubset(bases, heads, kind)
+
+    def pushkey(self, namespace, key, old, new):
+        # don't run hooks for server-side calls
+        return pushkey.push(self.repo, namespace, key, old, new)
+
+    def listkeys(self, namespace):
+        # don't run hooks for server-side calls
+        return pushkey.list(self.repo, namespace)
+
 class localrepository(repo.repository):
-    capabilities = set(('lookup', 'changegroupsubset', 'branchmap', 'pushkey',
-                        'known', 'getbundle'))
+
     supportedformats = set(('revlogv1', 'generaldelta'))
     supported = supportedformats | set(('store', 'fncache', 'shared',
                                         'dotencode'))
@@ -122,6 +247,9 @@
             reqfile.write("%s\n" % r)
         reqfile.close()
 
+    def _capabilities(self):
+        return LEGACYCAPS
+
     def _checknested(self, path):
         """Determine if path is a legal nested repository."""
         if not path.startswith(self.root):
@@ -159,6 +287,11 @@
                 parts.pop()
         return False
 
+    def proxy(self):
+        if not hasattr(self, '_proxy'):
+            self._proxy = localproxy(self)
+        return self._proxy
+
     @util.propertycache
     def _bookmarks(self):
         return bookmarks.read(self)
@@ -558,13 +691,12 @@
         repo = (remote and remote.local()) and remote or self
         return repo[key].branch()
 
-    def known(self, nodes):
-        nm = self.changelog.nodemap
-        return [(n in nm) for n in nodes]
-
     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)
 
@@ -1303,39 +1435,6 @@
                       ('close' not in self.changelog.read(h)[5])]
         return bheads
 
-    def branches(self, nodes):
-        if not nodes:
-            nodes = [self.changelog.tip()]
-        b = []
-        for n in nodes:
-            t = n
-            while True:
-                p = self.changelog.parents(n)
-                if p[1] != nullid or p[0] == nullid:
-                    b.append((t, n, p[0], p[1]))
-                    break
-                n = p[0]
-        return b
-
-    def between(self, pairs):
-        r = []
-
-        for top, bottom in pairs:
-            n, l, i = top, [], 0
-            f = 1
-
-            while n != bottom and n != nullid:
-                p = self.changelog.parents(n)[0]
-                if i == f:
-                    l.append(n)
-                    f = f * 2
-                n = p
-                i += 1
-
-            r.append(l)
-
-        return r
-
     def pull(self, remote, heads=None, force=False):
         lock = self.lock()
         try:
@@ -1468,15 +1567,7 @@
         common = set(cl.ancestors(*[cl.rev(n) for n in bases]))
         return self._changegroupsubset(common, csets, heads, source)
 
-    def getbundle(self, source, heads=None, common=None):
-        """Like changegroupsubset, but returns the set difference between the
-        ancestors of heads and the ancestors common.
-
-        If heads is None, use the local heads. If common is None, use [nullid].
-
-        The nodes in common might not all be known locally due to the way the
-        current discovery protocol works.
-        """
+    def _getbundle(self, source, heads=None, common=None):
         cl = self.changelog
         if common:
             nm = cl.nodemap
@@ -1958,10 +2049,6 @@
         self.hook('listkeys', namespace=namespace, values=values)
         return values
 
-    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)
-
 # used to avoid circular references so destructors work
 def aftertrans(files):
     renamefiles = [tuple(t) for t in files]
diff --git a/mercurial/repo.py b/mercurial/repo.py
--- a/mercurial/repo.py
+++ b/mercurial/repo.py
@@ -10,15 +10,30 @@
 import error
 
 class repository(object):
+
+    # def proxy(self):
+
+    def close(self):
+        pass
+
+class repositoryproxy(object):
+
+    def proxy(self):
+        return self
+
+    def close(self):
+        pass
+
     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
@@ -36,5 +51,6 @@
     def cancopy(self):
         return self.local()
 
-    def close(self):
-        pass
+    # def heads(self):
+    # def known(self, nodes):
+    # ...
diff --git a/mercurial/revset.py b/mercurial/revset.py
--- a/mercurial/revset.py
+++ b/mercurial/revset.py
@@ -599,7 +599,7 @@
     revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
     if revs:
         revs = [repo.lookup(rev) for rev in revs]
-    other = hg.repository(hg.remoteui(repo, {}), dest)
+    other = hg.repoproxy(hg.remoteui(repo, {}), dest)
     repo.ui.pushbuffer()
     common, outheads = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
     repo.ui.popbuffer()
diff --git a/mercurial/sshrepo.py b/mercurial/sshrepo.py
--- a/mercurial/sshrepo.py
+++ b/mercurial/sshrepo.py
@@ -81,12 +81,15 @@
         else:
             self._abort(error.RepoError(_("no suitable response from remote hg")))
 
-        self.capabilities = set()
+        self._capabilities = set()
         for l in reversed(lines):
             if l.startswith("capabilities:"):
-                self.capabilities.update(l[:-1].split(":")[1].split())
+                self._capabilities.update(l[:-1].split(":")[1].split())
                 break
 
+    def capabilities(self):
+        return self._capabilities
+
     def readerr(self):
         while True:
             size = util.fstat(self.pipee).st_size
@@ -186,7 +189,7 @@
     def unlock(self):
         self._call("unlock")
 
-    def addchangegroup(self, cg, source, url):
+    def addchangegroup(self, cg, source, url, lock=None):
         '''Send a changegroup to the remote server.  Return an integer
         similar to unbundle(). DEPRECATED, since it requires locking the
         remote.'''
diff --git a/mercurial/statichttprepo.py b/mercurial/statichttprepo.py
--- a/mercurial/statichttprepo.py
+++ b/mercurial/statichttprepo.py
@@ -124,7 +124,9 @@
         self._branchcachetip = None
         self.encodepats = None
         self.decodepats = None
-        self.capabilities = self.capabilities.difference(["pushkey"])
+
+    def _capabilities(self):
+        return localrepo.localrepository._capabilities(self).difference(["pushkey"])
 
     def url(self):
         return self._url
diff --git a/mercurial/subrepo.py b/mercurial/subrepo.py
--- a/mercurial/subrepo.py
+++ b/mercurial/subrepo.py
@@ -437,14 +437,15 @@
         if revision not in self._repo:
             self._repo._subsource = source
             srcurl = _abssource(self._repo)
-            other = hg.repository(self._repo.ui, srcurl)
+            other = hg.repoproxy(self._repo.ui, srcurl)
             if len(self._repo) == 0:
                 self._repo.ui.status(_('cloning subrepo %s from %s\n')
                                      % (subrelpath(self), srcurl))
                 parentrepo = self._repo._subparent
                 shutil.rmtree(self._repo.root)
-                other, self._repo = hg.clone(self._repo._subparent.ui, other,
-                                             self._repo.root, update=False)
+                other, cloned = hg.clone(self._repo._subparent.ui, other,
+                                         self._repo.root, update=False)
+                self._repo = cloned.repo
                 self._initrepo(parentrepo, source, create=True)
             else:
                 self._repo.ui.status(_('pulling subrepo %s from %s\n')
@@ -495,7 +496,7 @@
         dsturl = _abssource(self._repo, True)
         self._repo.ui.status(_('pushing subrepo %s to %s\n') %
             (subrelpath(self), dsturl))
-        other = hg.repository(self._repo.ui, dsturl)
+        other = hg.repoproxy(self._repo.ui, dsturl)
         return self._repo.push(other, force)
 
     def outgoing(self, ui, dest, opts):
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 repo, localrepo, error, encoding, util, store
 import pushkey as pushkeymod
 
 # list of nodes encoding / decoding
@@ -24,7 +24,7 @@
 
 # client side
 
-class wirerepository(repo.repository):
+class wirerepository(repo.repositoryproxy):
     def lookup(self, key):
         self.requirecap('lookup', _('look up remote revision'))
         d = self._call("lookup", key=encoding.fromlocal(key))
@@ -185,7 +185,8 @@
 def dispatch(repo, proto, command):
     func, spec = commands[command]
     args = proto.getargs(spec)
-    return func(repo, proto, *args)
+    proxy = localrepo.localwireproto(repo)
+    return func(proxy, proto, *args)
 
 def options(cmd, keys, others):
     opts = {}
@@ -274,7 +275,7 @@
     return "capabilities: %s\n" % (capabilities(repo, proto))
 
 def listkeys(repo, proto, namespace):
-    d = pushkeymod.list(repo, encoding.tolocal(namespace)).items()
+    d = repo.listkeys(encoding.tolocal(namespace)).items()
     t = '\n'.join(['%s\t%s' % (encoding.fromlocal(k), encoding.fromlocal(v))
                    for k, v in d])
     return t
@@ -304,9 +305,8 @@
     else:
         new = encoding.tolocal(new) # normal path
 
-    r = pushkeymod.push(repo,
-                        encoding.tolocal(namespace), encoding.tolocal(key),
-                        encoding.tolocal(old), new)
+    r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key),
+                     encoding.tolocal(old), new)
     return '%s\n' % int(r)
 
 def _allowstream(ui):
@@ -326,6 +326,7 @@
     if not _allowstream(repo.ui):
         return '1\n'
 
+    repo = repo.repo # undo the localwireproto wrapper
     entries = []
     total_bytes = 0
     try:
diff --git a/tests/notcapable b/tests/notcapable
--- a/tests/notcapable
+++ b/tests/notcapable
@@ -6,13 +6,20 @@
 fi
 
 cat > notcapable-$CAP.py << EOF
-from mercurial import extensions, repo
+from mercurial import extensions, repo, localrepo
 def extsetup():
-    extensions.wrapfunction(repo.repository, 'capable', wrapper)
+    extensions.wrapfunction(repo.repositoryproxy, 'capable', wrapper)
+    extensions.wrapfunction(localrepo.localrepository, 'proxy', proxy)
 def wrapper(orig, self, name, *args, **kwargs):
     if name in '$CAP'.split(' '):
         return False
     return orig(self, name, *args, **kwargs)
+def proxy(orig, self):
+    # Since we're disabling some newer features, we need to make sure local
+    # proxies add in the legacy features again.
+    if not hasattr(self, '_proxy'):
+        self._proxy = localrepo.localwireproto(self)
+    return self._proxy
 EOF
 
 echo '[extensions]' >> $HGRCPATH


More information about the Mercurial-devel mailing list