# states.py - introduce the state concept for mercurial changeset
#
# Copyright 2011 Pierre-Yves David <pierre-yves.david@ens-lyon.org>
# Logilab SA <contact@logilab.fr>
# Augie Fackler <durin42@gmail.com>
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
'''introduce the state concept for mercurial changeset
Change can be in the following state:
0 immutable
1 mutable
2 private
name are not fixed yet.
'''
from functools import partial
from mercurial.i18n import _
from mercurial import cmdutil
from mercurial import scmutil
from mercurial import context
from mercurial import revset
from mercurial import templatekw
from mercurial import util
from mercurial import node
from mercurial.node import nullid, hex, short
from mercurial import discovery
from mercurial import extensions
from mercurial import wireproto
_NOSHARE=2
_MUTABLE=1
class state(object):
def __init__(self, name, order=0, next=None):
self.name = name
self.order = order
assert next is None or self < next
self.next = next
def __repr__(self):
return 'state(%s)' % self.name
def __str__(self):
return self.name
@util.propertycache
def trackheads(self):
"""Do we need to track heads of changeset in this state ?
We don't need to track heads for the last state as this is repos heads"""
return self.next is not None
def __cmp__(self, other):
return cmp(self.order, other.order)
@util.propertycache
def _revsetheads(self):
"""function to be used by revset to finds heads of this states"""
assert self.trackheads
def revsetheads(repo, subset, x):
args = revset.getargs(x, 0, 0, 'publicheads takes no arguments')
heads = map(repo.changelog.rev, repo._statesheads[self])
heads.sort()
return heads
return revsetheads
@util.propertycache
def headssymbol(self):
"""name of the revset symbols"""
if self.trackheads:
return "%sheads" % self.name
else:
return 'heads'
STDRAFT = state('draft', _NOSHARE | _MUTABLE)
STREADY = state('ready', _MUTABLE, next=STDRAFT)
STPUBLISHED = state('published', next=STREADY)
STATES = (STPUBLISHED, STREADY, STDRAFT)
# util function
#############################
def noderange(repo, revsets):
return map(repo.changelog.node,
scmutil.revrange(repo, revsets))
# Patch changectx
#############################
def state(ctx):
return ctx._repo.nodestate(ctx.node())
context.changectx.state = state
# improve template
#############################
def showstate(ctx, **args):
return ctx.state()
# New revset predicate
#############################
def extsetup(ui):
for state in STATES:
if state.trackheads:
revset.symbols[state.headssymbol] = state._revsetheads
# New commands
#############################
def cmdsetstate(ui, repo, statename, *changesets):
"""turn private changesets into public ones"""
#assert repo.ui.configbool('private', 'enable', False)
for state in STATES: # few states
if state.name == statename:
break
else:
raise util.Abort(_('unknown state: %s') % statename)
revs = scmutil.revrange(repo, changesets)
repo.setstate(state, [repo.changelog.node(rev) for rev in revs])
return 0
cmdtable = {
'setstate': (cmdsetstate, [], _('<state> <revset>')),
}
templatekw.keywords['state'] = showstate
def uisetup(ui):
def filterprivateout(orig, repo, *args,**kwargs):
common, heads = orig(repo, *args, **kwargs)
return common, repo._reducehead(heads)
def filterprivatein(orig, repo, remote, *args, **kwargs):
common, anyinc, heads = orig(repo, remote, *args, **kwargs)
heads = remote._reducehead(heads)
return common, anyinc, heads
extensions.wrapfunction(discovery, 'findcommonoutgoing', filterprivateout)
extensions.wrapfunction(discovery, 'findcommonincoming', filterprivatein)
# Write protocols
####################
def heads(repo, proto):
h = repo._publicheads
return wireproto.encodelist(h) + "\n"
def _reducehead(wirerepo, heads):
"""heads filtering is done repo side"""
return heads
wireproto.wirerepository._reducehead = _reducehead
wireproto.commands['heads'] = (heads, '')
def reposetup(ui, repo):
if not repo.local():
return
o_cancopy =repo.cancopy
class statefulrepo(repo.__class__):
def nodestate(self, node):
rev = self.changelog.rev(node)
for head in self._publicheads:
revhead = self.changelog.rev(head)
if self.changelog.descendant(revhead, rev):
return STATES[2]
for head in self._frozenheads:
revhead = self.changelog.rev(head)
if self.changelog.descendant(revhead, rev):
return STATES[1]
return STATES[0]
@property
def _publicheads(self):
if self.ui.configbool('states', 'private', False):
return self._statesheads[STREADY]
return self.heads()
@property
def _frozenheads(self):
if self.ui.configbool('states', 'liquid', False):
return self._statesheads[STPUBLISHED]
return self.heads()
@util.propertycache
def _statesheads(self):
return self._readstatesheads()
def _readheadsfile(self, filename):
heads = [nullid]
try:
f = self.opener(filename)
try:
heads = sorted([node.bin(n) for n in f.read().split() if n])
finally:
f.close()
except IOError:
pass
return heads
def _readstatesheads(self):
statesheads = {}
statesheads[STPUBLISHED] = self._readheadsfile('frozenheads')
statesheads[STREADY] = self._readheadsfile('publicheads')
return statesheads
def _writeheadsfile(self, filename, heads):
f = self.opener(filename, 'w', atomictemp=True)
try:
for h in heads:
f.write(hex(h) + '\n')
f.rename()
finally:
f.close()
def _writestateshead(self):
# transaction!
self._writeheadsfile('frozenheads', self._statesheads[STPUBLISHED])
self._writeheadsfile('publicheads', self._statesheads[STREADY])
def setstate(self, state, nodes):
"""freeze targets changeset and it's ancestors.
Simplify the list of head."""
heads = self._statesheads[state]
olds = heads[:]
heads.extend(nodes)
heads[:] = set(heads)
heads.sort()
if olds != heads:
heads[:] = noderange(repo, ["heads(::%s())" % state.headssymbol])
heads.sort()
if olds != heads:
self._writestateshead()
if state.next is not None and state.next.trackheads:
self.setstate(state.next, nodes) # cascading
def _reducehead(self, candidates):
selected = set()
for candidate in candidates:
rev = self.changelog.rev(candidate)
ok = True
for h in self._publicheads:
revh = self.changelog.rev(h)
if self.changelog.descendant(revh, rev):
ok = False
selected.add(h)
if ok:
selected.add(candidate)
return sorted(selected)
def cancopy(self):
return o_cancopy() and (self._publicheads == self.heads())
repo.__class__ = statefulrepo