'''enable experimental obsolescence feature of Mercurial
OBSOLESCENCE IS AN EXPERIMENTAL FEATURE MAKE SURE YOU UNDERSTOOD THE INVOLVED
CONCEPT BEFORE USING IT.
/!\ THIS EXTENSION IS INTENDED FOR SERVER SIDE ONLY USAGE /!\
For client side usages it is recommended to use the evolve extension for
improved user interface.'''
testedwith = '2.9.1'
buglink = 'https://bitbucket.org/marmoute/mutable-history/issues'
import mercurial.obsolete
mercurial.obsolete._enabled = True
import struct
from mercurial import util
from mercurial import wireproto
from mercurial import extensions
from mercurial import obsolete
from cStringIO import StringIO
from mercurial import node
from mercurial.hgweb import hgweb_mod
_pack = struct.pack
def srv_pushobsmarkers(repo, proto):
"""wireprotocol command"""
fp = StringIO()
proto.redirect()
proto.getfile(fp)
data = fp.getvalue()
fp.close()
lock = repo.lock()
try:
tr = repo.transaction('pushkey: obsolete markers')
try:
repo.obsstore.mergemarkers(tr, data)
tr.close()
finally:
tr.release()
finally:
lock.release()
return wireproto.pushres(0)
def _encodemarkersstream(fp, markers):
fp.write(_pack('>B', 0))
for mark in markers:
fp.write(obsolete._encodeonemarker(mark))
def _getobsmarkersstream(repo, heads=None, common=None):
revset = ''
args = []
repo = repo.unfiltered()
if heads is None:
revset = 'all()'
elif heads:
revset += "(::%ln)"
args.append(heads)
else:
assert False, 'pulling no heads?'
if common:
revset += ' - (::%ln)'
args.append(common)
nodes = [c.node() for c in repo.set(revset, *args)]
markers = repo.obsstore.relevantmarkers(nodes)
obsdata = StringIO()
_encodemarkersstream(obsdata, markers)
obsdata.seek(0)
return obsdata
class pruneobsstore(obsolete.obsstore):
def __init__(self, *args, **kwargs):
self.prunedchildren = {}
return super(pruneobsstore, self).__init__(*args, **kwargs)
def _load(self, markers):
markers = self._prunedetectingmarkers(markers)
return super(pruneobsstore, self)._load(markers)
def _prunedetectingmarkers(self, markers):
for m in markers:
if not m[1]: # no successors
meta = obsolete.decodemeta(m[3])
if 'p1' in meta:
p1 = node.bin(meta['p1'])
self.prunedchildren.setdefault(p1, set()).add(m)
if 'p2' in meta:
p2 = node.bin(meta['p2'])
self.prunedchildren.setdefault(p2, set()).add(m)
yield m
def relevantmarkers(self, nodes):
"""return a set of all obsolescence marker relevant to a set of node.
"relevant" to a set of node mean:
- marker that use this changeset as successors
- prune marker of direct children on this changeset.
- recursive application of the two rules on precursors of these markers
It a set so you cannot rely on order"""
seennodes = set(nodes)
seenmarkers = set()
pendingnodes = set(nodes)
precursorsmarkers = self.precursors
prunedchildren = self.prunedchildren
while pendingnodes:
direct = set()
for current in pendingnodes:
direct.update(precursorsmarkers.get(current, ()))
direct.update(prunedchildren.get(current, ()))
direct -= seenmarkers
pendingnodes = set([m[0] for m in direct])
seenmarkers |= direct
pendingnodes -= seennodes
seennodes |= pendingnodes
return seenmarkers
def srv_pullobsmarkers(repo, proto, others):
opts = wireproto.options('', ['heads', 'common'], others)
for k, v in opts.iteritems():
if k in ('heads', 'common'):
opts[k] = wireproto.decodelist(v)
obsdata = _getobsmarkersstream(repo, **opts)
length = '%20i' % len(obsdata.getvalue())
def data():
yield length
for c in proto.groupchunks(obsdata):
yield c
return wireproto.streamres(data())
def _obsrelsethashtree(repo):
cache = []
unfi = repo.unfiltered()
for i in unfi:
ctx = unfi[i]
entry = 0
sha = util.sha1()
# add data from p1
for p in ctx.parents():
p = p.rev()
if p < 0:
p = node.nullid
else:
p = cache[p][1]
if p != node.nullid:
entry += 1
sha.update(p)
tmarkers = repo.obsstore.relevantmarkers([ctx.node()])
if tmarkers:
bmarkers = [obsolete._encodeonemarker(m) for m in tmarkers]
bmarkers.sort()
for m in bmarkers:
entry += 1
sha.update(m)
if entry:
cache.append((ctx.node(), sha.digest()))
else:
cache.append((ctx.node(), node.nullid))
return cache
def _obshash(repo, nodes):
hashs = _obsrelsethashtree(repo)
nm = repo.changelog.nodemap
return [hashs[nm.get(n)][1] for n in nodes]
def srv_obshash(repo, proto, nodes):
return wireproto.encodelist(_obshash(repo, wireproto.decodelist(nodes)))
def capabilities(orig, repo, proto):
"""wrapper to advertise new capability"""
caps = orig(repo, proto)
if obsolete._enabled:
caps += ' _evoext_pushobsmarkers_0'
caps += ' _evoext_pullobsmarkers_0'
caps += ' _evoext_obshash_0'
return caps
def extsetup(ui):
obsolete.obsstore = pruneobsstore
obsolete.obsstore.relevantmarkers = relevantmarkers
hgweb_mod.perms['evoext_pushobsmarkers_0'] = 'push'
hgweb_mod.perms['evoext_pullobsmarkers_0'] = 'pull'
hgweb_mod.perms['evoext_obshash'] = 'pull'
wireproto.commands['evoext_pushobsmarkers_0'] = (srv_pushobsmarkers, '')
wireproto.commands['evoext_pullobsmarkers_0'] = (srv_pullobsmarkers, '*')
extensions.wrapfunction(wireproto, 'capabilities', capabilities)
wireproto.commands['evoext_obshash'] = (srv_obshash, 'nodes')