# 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.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):
ctx._repo._obsobjrels # XXX hack to fill hiddenrevs
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)
# compute hidden (XXX should move elsewhere)
for obj in objrels:
self.changelog.hiddenrevs.add(repo[obj].rev())
#
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