view hgext3rd/topic/topicmap.py @ 1901:85390446f8c1

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.
author Pierre-Yves David <pierre-yves.david@fb.com>
date Thu, 17 Mar 2016 09:12:18 -0700
parents src/topic/topicmap.py@e846b8f402d0
children cf33ba7fbf4b
line wrap: on
line source

from mercurial import branchmap
from mercurial import encoding
from mercurial import error
from mercurial import scmutil
from mercurial import util
from mercurial.node import hex, bin, nullid

def _filename(repo):
    """name of a branchcache file for a given repo or repoview"""
    filename = "cache/topicmap"
    if repo.filtername:
        filename = '%s-%s' % (filename, repo.filtername)
    return filename

oldbranchcache = branchmap.branchcache

def _phaseshash(repo, maxrev):
    revs = set()
    cl = repo.changelog
    fr = cl.filteredrevs
    nm = cl.nodemap
    for roots in repo._phasecache.phaseroots[1:]:
        for n in roots:
            r = nm.get(n)
            if r not in fr and r < maxrev:
                revs.add(r)
    key = nullid
    revs = sorted(revs)
    if revs:
        s = util.sha1()
        for rev in revs:
            s.update('%s;' % rev)
        key = s.digest()
    return key

class topiccache(oldbranchcache):

    def __init__(self, *args, **kwargs):
        otherbranchcache = branchmap.branchcache
        try:
            # super() call may fail otherwise
            branchmap.branchcache = oldbranchcache
            super(topiccache, self).__init__(*args, **kwargs)
            if self.filteredhash is None:
                self.filteredhash = nullid
            self.phaseshash = nullid
        finally:
            branchmap.branchcache = otherbranchcache

    def copy(self):
        """return an deep copy of the branchcache object"""
        new =  topiccache(self, self.tipnode, self.tiprev, self.filteredhash,
                          self._closednodes)
        if self.filteredhash is None:
            self.filteredhash = nullid
        new.phaseshash = self.phaseshash
        return new

    def branchtip(self, branch, topic=''):
        '''Return the tipmost open head on branch head, otherwise return the
        tipmost closed head on branch.
        Raise KeyError for unknown branch.'''
        if topic:
            branch = '%s:%s' % (branch, topic)
        return super(topiccache, self).branchtip(branch)

    def branchheads(self, branch, closed=False, topic=''):
        if topic:
            branch = '%s:%s' % (branch, topic)
        return super(topiccache, self).branchheads(branch, closed=closed)

    def validfor(self, repo):
        """Is the cache content valid regarding a repo

        - False when cached tipnode is unknown or if we detect a strip.
        - True when cache is up to date or a subset of current repo."""
        # This is copy paste of mercurial.branchmap.branchcache.validfor in
        # 69077c65919d With a small changes to the cache key handling to
        # include phase information that impact the topic cache.
        #
        # All code changes should be flagged on site.
        try:
            if (self.tipnode == repo.changelog.node(self.tiprev)):
                fh = scmutil.filteredhash(repo, self.tiprev)
                if fh is None:
                    fh = nullid
                if ((self.filteredhash == fh)
                     and (self.phaseshash == _phaseshash(repo, self.tiprev))):
                    return True
            return False
        except IndexError:
            return False

    def write(self, repo):
        # This is copy paste of mercurial.branchmap.branchcache.write in
        # 69077c65919d With a small changes to the cache key handling to
        # include phase information that impact the topic cache.
        #
        # All code changes should be flagged on site.
        try:
            f = repo.vfs(_filename(repo), "w", atomictemp=True)
            cachekey = [hex(self.tipnode), str(self.tiprev)]
            # [CHANGE] we need a hash in all cases
            assert self.filteredhash is not None
            cachekey.append(hex(self.filteredhash))
            cachekey.append(hex(self.phaseshash))
            f.write(" ".join(cachekey) + '\n')
            nodecount = 0
            for label, nodes in sorted(self.iteritems()):
                for node in nodes:
                    nodecount += 1
                    if node in self._closednodes:
                        state = 'c'
                    else:
                        state = 'o'
                    f.write("%s %s %s\n" % (hex(node), state,
                                            encoding.fromlocal(label)))
            f.close()
            repo.ui.log('branchcache',
                        'wrote %s branch cache with %d labels and %d nodes\n',
                        repo.filtername, len(self), nodecount)
        except (IOError, OSError, error.Abort) as inst:
            repo.ui.debug("couldn't write branch cache: %s\n" % inst)
            # Abort may be raise by read only opener
            pass

    def update(self, repo, revgen):
        """Given a branchhead cache, self, that may have extra nodes or be
        missing heads, and a generator of nodes that are strictly a superset of
        heads missing, this function updates self to be correct.
        """
        oldgetbranchinfo = repo.revbranchcache().branchinfo
        try:
            def branchinfo(r):
                info = oldgetbranchinfo(r)
                topic = ''
                ctx = repo[r]
                if ctx.mutable():
                    topic = ctx.topic()
                branch = info[0]
                if topic:
                    branch = '%s:%s' % (branch, topic)
                return (branch, info[1])
            repo.revbranchcache().branchinfo = branchinfo
            super(topiccache, self).update(repo, revgen)
            if self.filteredhash is None:
                self.filteredhash = nullid
            self.phaseshash = _phaseshash(repo, self.tiprev)
        finally:
            repo.revbranchcache().branchinfo = oldgetbranchinfo

def readtopicmap(repo):
    # This is copy paste of mercurial.branchmap.read in 69077c65919d
    # With a small changes to the cache key handling to include phase
    # information that impact the topic cache.
    #
    # All code changes should be flagged on site.
    try:
        f = repo.vfs(_filename(repo))
        lines = f.read().split('\n')
        f.close()
    except (IOError, OSError):
        return None

    try:
        cachekey = lines.pop(0).split(" ", 2)
        last, lrev = cachekey[:2]
        last, lrev = bin(last), int(lrev)
        filteredhash = bin(cachekey[2]) # [CHANGE] unconditional filteredhash
        partial = branchcache(tipnode=last, tiprev=lrev,
                              filteredhash=filteredhash)
        partial.phaseshash = bin(cachekey[3]) # [CHANGE] read phaseshash
        if not partial.validfor(repo):
            # invalidate the cache
            raise ValueError('tip differs')
        cl = repo.changelog
        for l in lines:
            if not l:
                continue
            node, state, label = l.split(" ", 2)
            if state not in 'oc':
                raise ValueError('invalid branch state')
            label = encoding.tolocal(label.strip())
            node = bin(node)
            if not cl.hasnode(node):
                raise ValueError('node %s does not exist' % hex(node))
            partial.setdefault(label, []).append(node)
            if state == 'c':
                partial._closednodes.add(node)
    except KeyboardInterrupt:
        raise
    except Exception as inst:
        if repo.ui.debugflag:
            msg = 'invalid branchheads cache'
            if repo.filtername is not None:
                msg += ' (%s)' % repo.filtername
            msg += ': %s\n'
            repo.ui.debug(msg % inst)
        partial = None
    return partial