Mercurial > evolve
view states.py @ 20:9983f240ac63
Smarter states heads.
We use the next relevant heads instead of repo one when asking for the head of a
disabled state.
author | Pierre-Yves David <pierre-yves.david@logilab.fr> |
---|---|
date | Mon, 06 Jun 2011 16:52:03 +0200 |
parents | 8784a989a572 |
children | aa0870d093b8 |
line wrap: on
line source
# 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, properties=0, next=None): self.name = name self.properties = properties 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.properties, other.properties) @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' def enabled(self, ui): return ui.configbool('states', self.name, False) ST2 = state('draft', _NOSHARE | _MUTABLE) ST1 = state('ready', _MUTABLE, next=ST2) ST0 = state('published', next=ST1) STATES = (ST0, ST1, ST2) def laststatewithout(prop): for state in STATES: if not state.properties & prop: candidate = state else: return candidate # 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 commands ############################# cmdtable = {} def makecmd(state): def cmdmoveheads(ui, repo, *changesets): revs = scmutil.revrange(repo, changesets) repo.setstate(state, [repo.changelog.node(rev) for rev in revs]) return 0 return cmdmoveheads for state in STATES: if state.trackheads: cmdmoveheads = makecmd(state) cmdtable[state.name] = (cmdmoveheads, [], '<revset>') 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): st = laststatewithout(_NOSHARE) h = repo.stateheads(st) 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, '') templatekw.keywords['state'] = showstate def extsetup(ui): for state in STATES: if state.trackheads: revset.symbols[state.headssymbol] = state._revsetheads 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 state in STATES[::-1]: # XXX avoid for untracked heads if state.next is not None: for head in self.stateheads(state): revhead = self.changelog.rev(head) if self.changelog.descendant(revhead, rev): return state.next return state def stateheads(self, state): # look for a relevant state while state.trackheads and not state.next.enabled(self.ui): state = state.next # last state have no cached head. if state.trackheads: return self._statesheads[state] 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 = {} for state in STATES: if state.trackheads: filename = 'states/%s-heads' % state.name statesheads[state] = self._readheadsfile(filename) 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! for state in STATES: if state.trackheads: filename = 'states/%s-heads' % state.name self._writeheadsfile(filename, self._statesheads[state]) 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() st = laststatewithout(_NOSHARE) for candidate in candidates: rev = self.changelog.rev(candidate) ok = True for h in self.stateheads(st): 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): st = laststatewithout(_NOSHARE) return o_cancopy() and (self.stateheads(st) == self.heads()) repo.__class__ = statefulrepo