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

Peter Arrenbrecht peter.arrenbrecht at gmail.com
Sat Jun 4 07:05:14 CDT 2011


On Fri, Jun 3, 2011 at 9:41 PM, Matt Mackall <mpm at selenic.com> wrote:
> 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?

I guess I can do the following steps:

 * sshrepo fix (should have been separate anyway)
 * make all users go through repo.capable() instead of .capabilities
 * turn repo.capabilities into repo._capabilities()
 * factor legacy support out of localrepo into localwireproto
 * introduce hg.repoproxy and make callers use it appropriately
 * rename wirerepo, httprepo, sshrepo into *repoproxy (this is not
part of the current patch)
 * introduce localproxy and make hg.repoproxy return it

It's quite a bit of work, so I'd rather only do this if you think it will help.
-parren

>> 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