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

Matt Mackall mpm at selenic.com
Fri Jun 3 14:41:23 CDT 2011


On Fri, 2011-06-03 at 20:37 +0200, Peter Arrenbrecht wrote:
> # 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.

This is quite a big patch, is there a way we can split it up into
smaller steps?

> 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


-- 
Mathematics is the supreme nostalgia of our time.




More information about the Mercurial-devel mailing list