obsolete.py
author Pierre-Yves David <pierre-yves.david@logilab.fr>
Wed, 07 Sep 2011 12:42:26 +0200
changeset 40 b9a5a596d9ef
parent 39 b12655157ba0
child 41 99c131e97bb3
permissions -rw-r--r--
proper computation of hidden changeset.

# obsolete.py - introduce the obsolete concept in mercurial.
#
# Copyright 2011 Pierre-Yves David <pierre-yves.david@ens-lyon.org>
#                Logilab SA        <contact@logilab.fr>
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.

from mercurial import util
from mercurial import context
from mercurial import revset
from mercurial import scmutil
from mercurial.node import hex, bin

# Patch changectx
#############################

def obsolete(ctx):
    """is the changeset obsolete by other"""
    if ctx.node()is None:
        return False
    return bool(ctx._repo.obsoletedby(ctx.node()))

context.changectx.obsolete = obsolete

ohidden = context.changectx.hidden
def hidden(ctx):
    # hack to fill hiddenrevs
    # compute hidden (XXX should move elsewhere)
    if not getattr(ctx._repo.changelog, 'hiddeninit', False):
        basicquery = 'obsolete() - (ancestors(not obsolete()))'
        for rev in scmutil.revrange(ctx._repo, [basicquery]):
            ctx._repo.changelog.hiddenrevs.add(rev)
        ctx._repo.changelog.hiddeninit = True

    return ohidden(ctx)
context.changectx.hidden = hidden

# revset
#############################

def revsetobsolete(repo, subset, x):
    args = revset.getargs(x, 0, 0, 'publicheads takes no arguments')
    return [r for r in subset if repo[r].obsolete()] # XXX slow

def extsetup(ui):
    revset.symbols["obsolete"] = revsetobsolete

# New commands
#############################


def cmddebugobsolete(ui, repo, subject, object):
    """Add an obsolete relation between a too node
    
    The subject is expected to be a newer version of the object"""
    sub = repo[subject]
    obj = repo[object]
    repo.addobsolete(sub.node(), obj.node())
    return 0

cmdtable = {'debugobsolete': (cmddebugobsolete, [], '<subject> <object>')}

def reposetup(ui, repo):

    class obsoletingrepo(repo.__class__):

        @util.propertycache
        def hiddenrevs(self):
            # It's a property because It simpler that to handle the __init__
            revs = set()
            return revs

        @util.propertycache
        def _obsobjrels(self):
            """{<old-node> -> set(<new-node>)}

            also compute hidden revision"""
            #reverse sub -> objs mapping
            objrels = {}
            for sub, objs in self._obssubrels.iteritems():
                for obj in objs:
                    objrels.setdefault(obj, set()).add(sub)

            #
            return objrels

        @util.propertycache
        def _obssubrels(self):
            """{<new-node> -> set(<old-node>)}"""
            return self._readobsrels()


        ### Disk IO
        def _readobsrels(self):
            """Write obsolete relation on disk"""
            # XXX handle lock
            rels = {}
            try:
                f = self.opener('obsolete-relations')
                try:
                    for line in f:
                        subhex, objhex = line.split()
                        rels.setdefault(bin(subhex), set()).add(bin(objhex))
                finally:
                    f.close()
            except IOError:
                pass
            return rels

        def _writeobsrels(self):
            """Write obsolete relation on disk"""
            # XXX handle lock
            f = self.opener('obsolete-relations', 'w', atomictemp=True)
            try:
                for sub, objs in self._obssubrels.iteritems():
                    for obj in objs:
                        f.write('%s %s\n' % (hex(sub), hex(obj)))
                f.rename()
            finally:
                f.close()

        ### Public method
        def obsoletedby(self, node):
            """return the set of node that make <node> obsolete (obj)"""
            return self._obsobjrels.get(node, set())

        def obsolete(self, node):
            """return the set of node that <node> make obsolete (sub)"""
            return self._obssubrels.get(node, set())

        def addobsolete(self, sub, obj):
            """Add a relation marking that node <sub> is a new version of <obj>"""
            self._obssubrels.setdefault(sub, set()).add(obj)
            self._obsobjrels.setdefault(obj, set()).add(sub)
            self.changelog.hiddenrevs.add(repo[obj].rev())
            self._writeobsrels()

    repo.__class__ = obsoletingrepo