[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