# 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 from mercurial import discovery from mercurial import extensions from mercurial import wireproto _NOPULLPUSH=2 STATES = (0, _NOPULLPUSH) def statename(state): return str(STATES) # 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 revsetpublicheads(repo, subset, x): args = revset.getargs(x, 0, 0, 'publicheads takes no arguments') heads = map(repo.changelog.rev, repo._statesheads[0]) heads.sort() return heads def extsetup(ui): revset.symbols['publicheads'] = revsetpublicheads REVSETHEADS = {0: 'publicheads()'} # New commands ############################# def cmdsetstate(ui, repo, state, *changesets): """turn private changesets into public ones""" #assert repo.ui.configbool('private', 'enable', False) state = int(state) #for now 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[1] return STATES[0] @property def _publicheads(self): if self.ui.configbool('states', 'private', False): return self._statesheads[0] return self.heads() @util.propertycache def _statesheads(self): return self._readstatesheads() def _readstatesheads(self): statesheads = {} try: f = self.opener('publicheads') try: heads = sorted([node.bin(n) for n in f.read().split() if n]) finally: f.close() except IOError: heads = [nullid] statesheads[0] = heads return statesheads def _writestateshead(self): # transaction! f = self.opener('publicheads', 'w', atomictemp=True) try: for h in self._statesheads[0]: f.write(node.hex(h) + '\n') f.rename() finally: f.close() 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)" % REVSETHEADS[state]]) heads.sort() if olds != heads: self._writestateshead() 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