hgext3rd/topic/discovery.py
author Pierre-Yves David <pierre-yves.david@fb.com>
Thu, 17 Mar 2016 09:12:18 -0700
changeset 1901 85390446f8c1
parent 1899 src/topic/discovery.py@b65f39791f92
child 1903 58cdf061d49a
permissions -rw-r--r--
packaging: fix setup.py and install as hgext3rd.topic This changeset is doing two things (gasp): - It fixes various errors in the setup.py - It move the topic source and install into hgext3rd.topic. This last part (code source move) use hgext3rd as namespace package to prevent installation nightmare. This won't be officially supported until Mercurial 3.8, but in the meantime, 3.7 user can enable it using the full package name: [extensions] hgext3rd.topic= Thanks goes to Julien Cristau <julien.cristau@logilab.fr> for the initial version of this.

import weakref
from mercurial import branchmap
from mercurial import error
from mercurial import exchange
from mercurial.i18n import _
from . import topicmap

def _headssummary(orig, repo, remote, outgoing):
    publishing = ('phases' not in remote.listkeys('namespaces')
                  or bool(remote.listkeys('phases').get('publishing', False)))
    if publishing:
        return orig(repo, remote, outgoing)
    oldgetitem = repo.__getitem__
    oldrepo = repo.__class__
    oldbranchcache = branchmap.branchcache
    oldfilename = branchmap._filename
    try:
        class repocls(repo.__class__):
            def __getitem__(self, key):
                ctx = super(repocls, self).__getitem__(key)
                oldbranch = ctx.branch
                def branch():
                    branch = oldbranch()
                    topic = ctx.topic()
                    if topic:
                        branch = "%s:%s" % (branch, topic)
                    return branch
                ctx.branch = branch
                return ctx
        repo.__class__ = repocls
        branchmap.branchcache = topicmap.topiccache
        branchmap._filename = topicmap._filename
        summary = orig(repo, remote, outgoing)
        for key, value in summary.iteritems():
            if ':' in key: # This is a topic
                if value[0] is None and value[1]:
                    summary[key] = ([value[1].pop(0)], ) + value[1:]
        return summary
    finally:
        repo.__class__ = oldrepo
        branchmap.branchcache = oldbranchcache
        branchmap._filename = oldfilename

def wireprotobranchmap(orig, repo, proto):
    oldrepo = repo.__class__
    try:
        class repocls(repo.__class__):
            def branchmap(self):
                usetopic = not self.publishing()
                return super(repocls, self).branchmap(topic=usetopic)
        repo.__class__ = repocls
        return orig(repo, proto)
    finally:
        repo.__class__ = oldrepo


# Discovery have deficiency around phases, branch can get new heads with pure
# phases change. This happened with a changeset was allowed to be pushed
# because it had a topic, but it later become public and create a new branch
# head.
#
# Handle this by doing an extra check for new head creation server side
def _nbheads(repo):
    data = {}
    for b in repo.branchmap().iterbranches():
        if ':' in b[0]:
            continue
        data[b[0]] = len(b[1])
    return data

def handlecheckheads(orig, op, inpart):
    orig(op, inpart)
    if op.repo.publishing():
        return
    tr = op.gettransaction()
    if tr.hookargs['source'] not in ('push', 'serve'): # not a push
        return
    tr._prepushheads = _nbheads(op.repo)
    reporef = weakref.ref(op.repo)
    oldvalidator = tr.validator
    def validator(tr):
        repo = reporef()
        if repo is not None:
            repo.invalidatecaches()
            finalheads = _nbheads(repo)
            for branch, oldnb in tr._prepushheads.iteritems():
                newnb = finalheads.pop(branch, 0)
                if oldnb < newnb:
                    msg = _('push create a new head on branch "%s"' % branch)
                    raise error.Abort(msg)
            for branch, newnb in finalheads.iteritems():
                if 1 < newnb:
                    msg = _('push create more than 1 head on new branch "%s"' % branch)
                    raise error.Abort(msg)
        return oldvalidator(tr)
    tr.validator = validator
handlecheckheads.params = frozenset()

def _pushb2phases(orig, pushop, bundler):
    hascheck =  any(p.type == 'check:heads' for p in  bundler._parts)
    if pushop.outdatedphases and not hascheck:
        exchange._pushb2ctxcheckheads(pushop, bundler)
    return orig(pushop, bundler)