# HG changeset patch # User Pierre-Yves David # Date 1324293919 -3600 # Node ID 6d461c2143a0e1e86a902b0ec9beeb94e4625a53 # Parent a2e8057117d3f54b00dfdbc218725c2547085834 remote the states extention. (phase are now in mercurial core) diff -r a2e8057117d3 -r 6d461c2143a0 hgext/states.py --- a/hgext/states.py Mon Dec 19 12:19:00 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1074 +0,0 @@ -# states.py - introduce the state concept for mercurial changeset -# -# Copyright 2011 Pierre-Yves David -# Logilab SA -# Augie Fackler -# -# 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 - -(see http://mercurial.selenic.com/wiki/StatesPlan) - -General concept -=============== - -This extension adds the state concept. A changeset are now in a specific state -that control they mutability and they exchange. - -States properties -................. - -The states extension currently alter two property for changeset - -:mutability: history rewritten tool should refuse to work on immutable changeset -:sharing: shared changeset are exchanged during pull and push. other are not - -Here is a small summary of the current property of state existing state:: - - || || mutable || shared || - || published || || x || - || ready || x || x || - || draft || x || || - -States consistency and ordering -............................... - -States of changesets have to be consistent with each other. A changeset can only have ancestors of it's state (or a compatible states) - -Example: - - A ``published`` changeset can't have a ``draft`` parent. - -a state is compatible with itself and all "smaller" states. Order is as follow:: - - published < ready < draft - - -.. note: - - This section if probably far too conceptual for people. The result is just - that: A ``published`` changeset can only have ``published`` ancestors. A - ``ready`` changeset can only have ``published`` or ``ready`` ancestors. - - Moreover There is a need for a nice word to refer to "a state smaller than another" - - -States details -============== - - -published - Changesets in the ``published`` state are the core of the history. They are - changesets that you published to the world. People can expect them to always - exist. They are changesets as you know them. **By default all changesets - are published** - - - They are exchanged with other repositories (included in pull//push). - - - They are not mutable, extensions rewriting history should refuse to - rewrite them. - -ready - Changesets in the ``ready`` state have not yet been accepted in the - immutable history. You can share them with others for review, testing or - improvement. Any ``ready`` changeset can either be included in the - published history (and become immutable) or be rewritten and never make it - to the published history. - - - They are exchanged with other repositories (included in pull//push). - - - They are mutable, extensions rewriting history accept to work on them. - -draft - - Changesets in the ``draft`` state are heavy work in progress you are not - yet willing to share with others. - - - They are not exchanged with other repositories. pull//push do not see them. - - They are mutable, extensions rewriting history accept to work on them. - --- - -.. note: - - The Dead states mentionned in on the wiki page are missing. There is two main reason for it: - - 1. The ``dead`` state has a different behaviour that requires more work to be - implemented. - - 2. I believe that the use cases of ``dead changeset`` are better covered by - the ``obsolete`` extension. - --- - -.. note: - - I'm tempted to add a state with the same property that ``ready`` for review - workflow.:: - - || || mutable || shared || - || published || || x || - || ready || x || x || - || inprogress|| x || x || - || draft || x || || - - The ``ready`` state would be for changeset that wait review of someone that - can "publish" them. - - - -Current Feature and usage -========================= - - -Enabling states -............... - -The extension adds a :hg:`hg states` command to display and choose which states -are used by a repository, see :hg:`hg states` for details. - -By default all changesets in the repository are ``published``. Other states -must be explicitly activated. Changeset in a remote repository that doesn't -support states are all seen as ``published``. - -.. note: - - When a state is not activated, changesets in this state are handled as - changesets of the previous state it (``draft`` are handled as ``ready``, - ``ready`` are handled as ``published``). - -TODO: - -- have a configuration in hgrc:: - - [states] - ready=(off|on)(-inherit)? - =(off|on)(-inherit)? - - :off: state disabled for new repo - :on: state enabled for new repo - :inherit: if present, inherit states of source on :hg:`clone`. - -- display the number of changesets that change state when activating a state. - - - -State transition -................ - -Changeset you create locally will be in the ``draft`` state. (or any previous -state if draft isn't enabled) - -There is some situation where the state of a changeset will change -automatically. Automatic movement always go in the same direction.: ``draft -> -``ready`` -> ``published`` - -1. When you pull or push boundary move. Common changeset that are ``published`` in -one of the two repository are set to ``published``. Same goes for ``ready`` etc -(states are evaluated from in increasing order XXX I bet no one understand this -parenthesis. Pull operation alter the local repository. push alter both local -and remote repository. - -.. note: - - As Repository without any specific state have all their changeset - ``published``, Pushing to such repo will ``publish`` all common changeset. - -2. Tagged changeset get automatically Published. The tagging changeset is -tagged too... This doesn't apply to local tag. - - -You can also manually change changeset state with a dedicated command for each -state. See :hg:`published`, :hg:`ready` and :hg:`draft` for details. - -XXX maybe we can details the general behaviour here - -:hg revs: move boundary of state so it includes revs - ( revs included in ::heads()) -:hg --exact revs: move boundary so that revs are exactly in state - ( all([rev.state == for rev in - revs])) -:hg --exact --force revs: move boundary event if it create inconsistency - (with tag for example) - -TODO: - -- implement consistency check - -- implement --force - - -Existing command change -....................... - -As said in the previous section: - -:commit: Create draft changeset (or the first enabled previous changeset). -:tag: Move tagged and tagging changeset in the ``published`` state. -:incoming: Exclude ``draft`` changeset of remote repository. -:outgoing: Exclude ``draft`` changeset of local repository. -:pull: As :hg:`in` + change state of local changeset according to remote side. -:push: As :hg:`out` + sync state of common changeset on both side -:rollback: rollback restore states heads as before the last transaction (see bookmark) - -State Transition control -......................... - -There is currently no way to control who can alter boundary (The most notable -usecase is about the published one). - -This is probably needed quickly - -XXX TODO: Proper behaviour when heads file are chmoded whould be a first step. - -XXX We are going to need hooks (pre and post) hook on state transition too. - -Template -........ - -A new template keyword ``{state}`` has been added. - -Revset -...... - -We add new ``readyheads()`` and ``publishedheads()`` revset directives. This -returns the heads of each state **as if all of them were activated**. - -XXX TODO - I would like to - -- move the current ``heads()`` directives to - _``heads()`` - -- add ``heads()`` directives to that return the currently in used heads - -- add ``()`` directives that match all node in a state. - -Context -....... - -The ``context`` class gain a new method ``states()`` that return a ``state`` object. The -most notable property of this states object are ```name`` and ``mutable``. - -Other extensions -................ - -:rebase: can't rebase immutable changeset. -:mq: can't qimport immutable changeset. - -TODO: publishing a changeset should qfinish mq patches. - - - -Implementation -============== - -State definition -................ - -Conceptually: - -The set of node in the states are defined by the set of the state heads. This allow -easy storage, exchange and consistency. - -.. note: A cache of the complete set of node that belong to a states will - probably be need for performance. - -Code wise: - -There is a ``state`` class that hold the state property and several useful -logic (name, revset entry etc). - -All defined states are accessible thought the STATES tuple at the ROOT of the -module. Or the STATESMAP dictionary that allow to fetch a state from it's -name. - -You can get and edit the list head node that define a state with two methods on -repo. - -:stateheads(): Returns the list of heads node that define a states -:setstate(, [nodes]): Move states boundary forward to include the given - nodes in the given states. - -Those methods handle ``node`` and not rev as it seems more resilient to me that -rev in a mutable world. Maybe it' would make more sens to have ``node`` store -on disk but revision in the code. - -Storage -....... - -States related data are stored in the ``.hg/states/`` directory. - -The ``.hg/states/Enabled`` file list the states enabled in this -repository. This data is *not* stored in the .hg/hgrc because the .hg/hgrc -might be ignored for trust reason. As missing und with states can be pretty -annoying. (publishing unfinalized changeset, pulling draft one etc) we don't -want trust issue to interfer with enabled states information. - -``.hg/states/-heads`` file list the nodes that define a states. - -_NOSHARE filtering -.................. - -Any changeset in a state with a _NOSHARE property will be exclude from pull, -push, clone, incoming, outgoing and bundle. It is done through three mechanism: - -1. Wrapping the findcommonincoming and findcommonoutgoing code with (not very - efficient) logic that recompute the exchanged heads. - -2. Altering ``heads`` wireprotocol command to return sharead heads. - -3. Disabling hardlink cloning when there is _NOSHARE changeset available. - -Internal plumbery ------------------ - -sum up of what we do: - -* state are object - -* repo.__class__ is extended - -* discovery is wrapped up - -* wire protocol is patched - -* transaction and rollback mechanism are wrapped up. - -* XXX we write new version of the boundard whenever something happen. We need a - smarter and faster way to do this. - - -''' -import os -from functools import partial -from operator import or_ - -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 -from mercurial import pushkey -from mercurial import error -from mercurial import repair -from mercurial.lock import release - - - -# states property constante -_NOSHARE=2 -_MUTABLE=1 - -class state(object): - """State of changeset - - An utility object that handle several behaviour and containts useful code - - A state is defined by: - - It's name - - It's property (defined right above) - - - It's next state. - - XXX maybe we could stick description of the state semantic here. - """ - - # plumbery utily - def __init__(self, name, properties=0, next=None): - self.name = name - self.properties = properties - assert next is None or self < next - self.next = next - @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 repo heads""" - return self.next is not None - - # public utility - def __cmp__(self, other): - """Use property to compare states. - - This is a naiv approach that assume the the next state are strictly - more property than the one before - # assert min(self, other).properties = self.properties & other.properties - """ - return cmp(self.properties, other.properties) - - @property - def mutable(self): - return bool(self.properties & _MUTABLE) - - # display code - def __repr__(self): - return 'state(%s)' % self.name - - def __str__(self): - return self.name - - - # revset utility - @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 = [] - for h in repo._statesheads[self]: - try: - heads.append(repo.changelog.rev(h)) - except error.LookupError: - pass - 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' - -# Actual state definition - -ST2 = state('draft', _NOSHARE | _MUTABLE) -ST1 = state('ready', _MUTABLE, next=ST2) -ST0 = state('published', next=ST1) - -# all available state -STATES = (ST0, ST1, ST2) -# all available state by name -STATESMAP =dict([(st.name, st) for st in STATES]) - -@util.cachefunc -def laststatewithout(prop): - """Find the states with the most property but - - (This function is necessary because the whole state stuff are abstracted)""" - for state in STATES: - if not state.properties & prop: - candidate = state - else: - return candidate - -# util function -############################# -def noderange(repo, revsets): - """The same as revrange but return node""" - return map(repo.changelog.node, - scmutil.revrange(repo, revsets)) - -# Patch changectx -############################# - -def state(ctx): - """return the state objet associated to the context""" - if ctx.node()is None: - return STATES[-1] - return ctx._repo.nodestate(ctx.node()) -context.changectx.state = state - -# improve template -############################# - -def showstate(ctx, **args): - """Show the name of the state associated with the context""" - return ctx.state() - - -# New commands -############################# - - -def cmdstates(ui, repo, *states, **opt): - """view and modify activated states. - - With no argument, list activated state. - - With argument, activate the state in argument. - - With argument plus the --off switch, deactivate the state in argument. - - note: published state are alway activated.""" - - if not states: - for st in sorted(repo._enabledstates): - ui.write('%s\n' % st) - else: - off = opt.get('off', False) - for state_name in states: - for st in STATES: - if st.name == state_name: - break - else: - ui.write_err(_('no state named %s\n') % state_name) - return 1 - if off: - if st in repo._enabledstates: - repo.disablestate(st) - else: - ui.write_err(_('state %s already deactivated\n') % - state_name) - - else: - repo.enablestate(st, not opt.get('clever')) - repo._writeenabledstates() - return 0 - -cmdtable = {'states': (cmdstates, [ - ('', 'off', False, _('desactivate the state') ), - ('', 'clever', False, _('do not fix lower when activating the state') )], - '')} - -# automatic generation of command that set state -def makecmd(state): - def cmdmoveheads(ui, repo, *changesets, **opts): - """set revisions in %s state - - This command also alter state of ancestors if necessary. - """ % state - if not state in repo._enabledstates: - raise error.Abort( - _('state %s is not activated' % state), - hint=_('try ``hg states %s`` before' % state)) - if opts.get('exact'): - repo.setstate_unsafe(state, changesets) - return 0 - revs = scmutil.revrange(repo, changesets) - repo.setstate(state, [repo.changelog.node(rev) for rev in revs]) - return 0 - return cmdmoveheads - -for state in STATES: - cmdmoveheads = makecmd(state) - cmdtable[state.name] = (cmdmoveheads, [ - ('e', 'exact', False, _('move boundary so that revs are exactly in ' - 'state ( all([rev.state == for ' - 'rev in revs]))')) - ], '') - -# Pushkey mechanism for mutable -######################################### - -def pushstatesheads(repo, key, old, new): - """receive a new state for a revision via pushkey - - It only move revision from a state to a <= one - - Return True if the revision exist in the repository - Return False otherwise. (and doesn't alter any state)""" - st = STATESMAP[new] - w = repo.wlock() - try: - newhead = node.bin(key) - try: - repo[newhead] - except error.RepoLookupError: - return False - repo.setstate(st, [newhead]) - return True - finally: - w.release() - -def liststatesheads(repo): - """List the boundary of all states. - - {"node-hex" -> "comma separated list of state",} - """ - keys = {} - for state in [st for st in STATES if st.trackheads]: - for head in repo.stateheads(state): - head = node.hex(head) - if head in keys: - keys[head] += ',' + state.name - else: - keys[head] = state.name - return keys - -pushkey.register('states-heads', pushstatesheads, liststatesheads) - - -# Wrap discovery -#################### -def filterprivateout(orig, repo, *args,**kwargs): - """wrapper for findcommonoutgoing that remove _NOSHARE""" - common, heads = orig(repo, *args, **kwargs) - if getattr(repo, '_reducehead', None) is not None: - return common, repo._reducehead(heads) -def filterprivatein(orig, repo, remote, *args, **kwargs): - """wrapper for findcommonincoming that remove _NOSHARE""" - common, anyinc, heads = orig(repo, remote, *args, **kwargs) - if getattr(remote, '_reducehead', None) is not None: - heads = remote._reducehead(heads) - return common, anyinc, heads - -# states boundary IO -##################### - -def _readheadsfile(repo, filename): - """read head from the given file - - XXX move me elsewhere""" - heads = [nullid] - try: - f = repo.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(repo, undo=False): - """read all state heads - - XXX move me elsewhere""" - statesheads = {} - for state in STATES: - if state.trackheads: - filemask = 'states/%s-heads' - filename = filemask % state.name - statesheads[state] = _readheadsfile(repo, filename) - return statesheads - -def _writeheadsfile(repo, filename, heads): - """write given in the file with at - - XXX move me elsewhere""" - f = repo.opener(filename, 'w', atomictemp=True) - try: - for h in heads: - f.write(hex(h) + '\n') - try: - f.rename() - except AttributeError: # old version - f.close() - finally: - f.close() - -def _writestateshead(repo): - """write all heads - - XXX move me elsewhere""" - # XXX transaction! - for state in STATES: - if state.trackheads: - filename = 'states/%s-heads' % state.name - _writeheadsfile(repo, filename, repo._statesheads[state]) - -# WireProtocols -#################### -def wireheads(repo, proto): - """Altered head command that doesn't include _NOSHARE - - This is a write protocol command""" - st = laststatewithout(_NOSHARE) - h = repo.stateheads(st) - return wireproto.encodelist(h) + "\n" - -# Other extension support -######################### - -def wraprebasebuildstate(orig, repo, *args, **kwargs): - """Wrapped rebuild state that check for immutable changeset - - buildstate are the best place i found to hook :-/""" - result = orig(repo, *args, **kwargs) - if result is not None: - # rebase.nullmerge is issued in the detach case - rebase = extensions.find('rebase') - rebased = [rev for rev, rbst in result[2].items() if rbst != rebase.nullmerge] - base = repo.changelog.node(min(rebased)) - state = repo.nodestate(base) - if not state.mutable: - raise util.Abort(_('can not rebase published changeset %s') - % node.short(base), - hint=_('see `hg help --extension states` for details')) - return result - -def wrapmqqimport(orig, queue, repo, *args, **kwargs): - """Wrapper for rebuild state that deny importing immutable changeset - """ - if 'rev' in kwargs: - # we can take the min as non linear import will break - # anyway - revs = scmutil.revrange(repo, kwargs['rev']) - if revs: - base = min(revs) - basenode = repo.changelog.node(base) - state = repo.nodestate(basenode) - if not state.mutable: - raise util.Abort(_('can not qimport published changeset %s') - % node.short(basenode), - hint=_('see `hg help --extension states` for details')) - return orig(queue, repo, *args, **kwargs) - -def strip(orig, ui, repo, node, backup="all"): - cl = repo.changelog - striprev = cl.rev(node) - revstostrip = set(cl.descendants(striprev)) - revstostrip.add(striprev) - tostrip = set(map(cl.node, revstostrip)) - # compute the potentially new created states bondaries which are any - # parent of the stripped node that are not stripped (may not be heads) - newbondaries = set(par for nod in tostrip for par in cl.parents(nod) - if par not in tostrip) - # save the current states of newbondaries in a chache as repo.nodestate - # must work along the loop. We will use the next loop to add them. - statesheads={} - for nd in newbondaries: - state = repo.nodestate(nd) - if state.trackheads: - statesheads.setdefault(state, set([])).add(nd) - - for state, heads in repo._statesheads.iteritems(): - if not state.trackheads: - continue - heads = set(heads) - tostrip | statesheads.get(state, set([])) - # reduce heads (make them really heads) - revs = set(map(cl.rev, heads)) - minrev = min(revs) - for rev in cl.ancestors(*revs): - if rev >= minrev: - revs.discard(rev) - repo._statesheads[state] = map(cl.node, revs) - _writestateshead(repo) - - return orig(ui, repo, node, backup) - - -def uisetup(ui): - """ - * patch stuff for the _NOSHARE property - * add template keyword - """ - # patch discovery - extensions.wrapfunction(discovery, 'findcommonoutgoing', filterprivateout) - extensions.wrapfunction(discovery, 'findcommonincoming', filterprivatein) - extensions.wrapfunction(repair, 'strip', strip) - - # patch wireprotocol - wireproto.commands['heads'] = (wireheads, '') - - # add template keyword - templatekw.keywords['state'] = showstate - -def extsetup(ui): - """Extension setup - - * add revset entry""" - for state in STATES: - if state.trackheads: - revset.symbols[state.headssymbol] = state._revsetheads - # wrap rebase - try: - rebase = extensions.find('rebase') - if rebase: - extensions.wrapfunction(rebase, 'buildstate', wraprebasebuildstate) - except KeyError: - pass # rebase not found - # wrap mq - try: - mq = extensions.find('mq') - if mq: - extensions.wrapfunction(mq.queue, 'qimport', wrapmqqimport) - except KeyError: - pass # mq not found - - - -def reposetup(ui, repo): - """Repository setup - - * extend repo class with states logic""" - - if not repo.local(): - return - - ocancopy =repo.cancopy - opull = repo.pull - opush = repo.push - o_tag = repo._tag - orollback = repo.rollback - o_writejournal = repo._writejournal - class statefulrepo(repo.__class__): - """An extension of repo class that handle state logic - - - nodestate - - stateheads - """ - - def nodestate(self, node): - """return the state object associated to the given node""" - rev = self.changelog.rev(node) - for state in STATES: - # avoid for untracked heads - if state.next is not None: - ancestors = map(self.changelog.rev, self.stateheads(state)) - ancestors.extend(self.changelog.ancestors(*ancestors)) - if rev in ancestors: - break - return state - - def enablestate(self, state, fix_lower=True): - if fix_lower: - # at least published which is always activated - lower = max(st for st in self._enabledstates if st < state) - self.setstate(lower, self.stateheads(state)) - self._enabledstates.add(state) - - def disablestate(self, state): - """Disable empty state. - Raise error.Abort if the state is not empty. - """ - # the lowest is mandatory - if state == ST0: - raise error.Abort(_('could not disable %s' % state.name)) - enabled = self._enabledstates - # look up for lower state that is enabled (at least published) - lower = max(st for st in self._enabledstates if st < state) - if repo.stateheads(state) != repo.stateheads(lower): - raise error.Abort( - _('could not disable non empty state %s' % state.name), - hint=_("You may want to use `hg %s '%sheads()'`" - % (lower.name, state.name)) - ) - else: - enabled.remove(state) - - def stateheads(self, state): - """Return the set of head that define the state""" - # look for a relevant state - while state.trackheads and state.next not in self._enabledstates: - state = state.next - # last state have no cached head. - if state.trackheads: - return self._statesheads[state] - return self.heads() - - @util.propertycache - def _statesheads(self): - """{ state-object -> set(defining head)} mapping""" - return _readstatesheads(self) - - def setstate_unsafe(self, state, changesets): - """Change state of targets changesets and it's ancestors. - - Simplify the list of heads. - - Unlike ``setstate``, the "lower" states are also changed - """ - #modify "lower" states - req_nodes_rst = '|'.join('((%s)::)' % rst for rst in changesets) - for st in STATES: - if st >= state: # only modify lower state heads for now - continue - try: - heads = self._statesheads[st] - except KeyError: # forget non-activated states - continue - olds = heads[:] - rst = "heads((::%s()) - (%s))" % (st.headssymbol, req_nodes_rst) - heads[:] = noderange(repo, [rst]) - if olds != heads: - _writestateshead(self) - #modify the state - if state in self._statesheads: - revs = scmutil.revrange(repo, changesets) - repo.setstate(state, [repo.changelog.node(rev) for rev in revs]) - - def setstate(self, state, nodes): - """change state of targets changeset and it's ancestors. - - Simplify the list of head.""" - assert not isinstance(nodes, basestring), repr(nodes) - if not state.trackheads: - return - 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: - _writestateshead(self) - if state.next is not None and state.next.trackheads: - self.setstate(state.next, nodes) # cascading - - def _reducehead(self, candidates): - """recompute a set of heads so it doesn't include _NOSHARE changeset - - This is basically a complicated method that compute - heads(::candidates - _NOSHARE) - """ - selected = set() - st = laststatewithout(_NOSHARE) - candidates = set(map(self.changelog.rev, candidates)) - heads = set(map(self.changelog.rev, self.stateheads(st))) - shareable = set(self.changelog.ancestors(*heads)) - shareable.update(heads) - selected = candidates & shareable - unselected = candidates - shareable - for rev in unselected: - for revh in heads: - if self.changelog.descendant(revh, rev): - selected.add(revh) - return sorted(map(self.changelog.node, selected)) - - ### enable // disable logic - - @util.propertycache - def _enabledstates(self): - """The set of state enabled in this repository""" - return self._readenabledstates() - - def _readenabledstates(self): - """read enabled state from disk""" - states = set() - states.add(ST0) - mapping = dict([(st.name, st) for st in STATES]) - try: - f = self.opener('states/Enabled') - for line in f: - st = mapping.get(line.strip()) - if st is not None: - states.add(st) - finally: - return states - - def _writeenabledstates(self): - """read enabled state to disk""" - f = self.opener('states/Enabled', 'w', atomictemp=True) - try: - for st in self._enabledstates: - f.write(st.name + '\n') - try: - f.rename() - except AttributeError: # old version - f.close() - finally: - f.close() - - ### local clone support - - def cancopy(self): - """deny copy if there is _NOSHARE changeset""" - st = laststatewithout(_NOSHARE) - return ocancopy() and (self.stateheads(st) == self.heads()) - - ### pull // push support - - def pull(self, remote, *args, **kwargs): - """altered pull that also update states heads on local repo""" - result = opull(remote, *args, **kwargs) - remoteheads = self._pullstatesheads(remote) - for st, heads in remoteheads.iteritems(): - self.setstate(st, heads) - return result - - def push(self, remote, *args, **opts): - """altered push that also update states heads on local and remote""" - result = opush(remote, *args, **opts) - if not self.ui.configbool('states', 'bypass', False): - remoteheads = self._pullstatesheads(remote) - for st, heads in remoteheads.iteritems(): - self.setstate(st, heads) - if heads != self.stateheads(st): - self._pushstatesheads(remote, st, heads) - return result - - def _pushstatesheads(self, remote, state, remoteheads): - """push head of a given state for remote - - This handle pushing boundary that does exist on remote host - This is done a very naive way""" - local = set(self.stateheads(state)) - missing = local - set(remoteheads) - while missing: - h = missing.pop() - ok = remote.pushkey('states-heads', node.hex(h), '', state.name) - if not ok: - missing.update(p.node() for p in repo[h].parents()) - - - def _pullstatesheads(self, remote): - """pull all remote states boundary locally - - This can only make the boundary move on a newer changeset""" - remoteheads = {} - self.ui.debug('checking for states-heads on remote server') - if 'states-heads' not in remote.listkeys('namespaces'): - self.ui.debug('states-heads not enabled on the remote server, ' - 'marking everything as published\n') - remoteheads[ST0] = remote.heads() - else: - self.ui.debug('server has states-heads enabled, merging lists') - for hex, statenames in remote.listkeys('states-heads').iteritems(): - for stn in statenames.split(','): - remoteheads.setdefault(STATESMAP[stn], []).append(node.bin(hex)) - return remoteheads - - ### Tag support - - def _tag(self, names, node, *args, **kwargs): - """Altered version of _tag that make tag (and tagging) published""" - tagnode = o_tag(names, node, *args, **kwargs) - if tagnode is not None: # do nothing for local one - self.setstate(ST0, [node, tagnode]) - return tagnode - - ### rollback support - - def _writejournal(self, desc): - """extended _writejournal that also save states""" - entries = list(o_writejournal(desc)) - for state in STATES: - if state.trackheads: - filename = 'states/%s-heads' % state.name - filepath = self.join(filename) - if os.path.exists(filepath): - journalname = 'states/journal.%s-heads' % state.name - journalpath = self.join(journalname) - util.copyfile(filepath, journalpath) - entries.append(journalpath) - return tuple(entries) - - def rollback(self, dryrun=False): - """extended rollback that also restore states""" - wlock = lock = None - try: - wlock = self.wlock() - lock = self.lock() - ret = orollback(dryrun) - if not (ret or dryrun): #rollback did not failed - for state in STATES: - if state.trackheads: - src = self.join('states/undo.%s-heads') % state.name - dest = self.join('states/%s-heads') % state.name - if os.path.exists(src): - util.rename(src, dest) - elif os.path.exists(dest): #unlink in any case - os.unlink(dest) - self.__dict__.pop('_statesheads', None) - return ret - finally: - release(lock, wlock) - - repo.__class__ = statefulrepo - diff -r a2e8057117d3 -r 6d461c2143a0 tests/test-draft.t --- a/tests/test-draft.t Mon Dec 19 12:19:00 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,217 +0,0 @@ - $ cat >> $HGRCPATH < [web] - > push_ssl = false - > allow_push = * - > [extensions] - > EOF - $ echo "states=$(echo $(dirname $TESTDIR))/hgext/states.py" >> $HGRCPATH - - $ hg init local - $ hg init remote1 - $ hg init remote2 - $ cd local - $ echo "celestine" > babar - $ hg add babar - $ hg ci -m "add babar" - $ echo "la veille dame" > babar - $ hg ci -m "add dame" - $ hg log --template='{rev}:{node|short}: {state}\n' - 1:710fe444b3b0: published - 0:5caa672bac26: published - $ hg out ../remote1 --template='{rev}:{node|short}\n' - comparing with ../remote1 - searching for changes - 0:5caa672bac26 - 1:710fe444b3b0 - $ hg push ../remote1 - pushing to ../remote1 - searching for changes - adding changesets - adding manifests - adding file changes - added 2 changesets with 2 changes to 1 files - $ echo "tree" >> savanna - $ hg add savanna - $ hg ci -m "terrain" - $ echo "flore" >> babar - $ hg ci -m "children" - $ hg log --template='{rev}:{node|short}: {state}\n' - 3:73585b17392a: published - 2:3c8695235a32: published - 1:710fe444b3b0: published - 0:5caa672bac26: published - -turn draft on (repo side) - $ hg states --clever draft - $ hg log --template='{rev}:{node|short}: {state}\n' - 3:73585b17392a: draft - 2:3c8695235a32: draft - 1:710fe444b3b0: published - 0:5caa672bac26: published - -test outgoing and push - $ hg out ../remote1 --template='{rev}:{node|short}\n' - comparing with ../remote1 - searching for changes - no changes found - [1] - $ hg push --traceback ../remote1 - pushing to ../remote1 - searching for changes - no changes found - - $ hg out ../remote2 --template='{rev}:{node|short}\n' - comparing with ../remote2 - searching for changes - 0:5caa672bac26 - 1:710fe444b3b0 - $ hg push ../remote2 - pushing to ../remote2 - searching for changes - adding changesets - adding manifests - adding file changes - added 2 changesets with 2 changes to 1 files - -turn draft off again (repo side) - $ hg states --off draft - abort: could not disable non empty state draft - (You may want to use `hg published 'draftheads()'`) - [255] - $ hg published tip - $ hg log --template='{rev}:{node|short}: {state}\n' - 3:73585b17392a: published - 2:3c8695235a32: published - 1:710fe444b3b0: published - 0:5caa672bac26: published - $ hg out ../remote1 --template='{rev}:{node|short}\n' - comparing with ../remote1 - searching for changes - 2:3c8695235a32 - 3:73585b17392a - -turn draft on again (repo side) - $ hg states draft - $ hg draft --exact 2 - -test incoming and pull - - $ hg init ../other1 - $ cd ../other1 - $ hg incoming ../local --template='{rev}:{node|short}\n' - comparing with ../local - 0:5caa672bac26 - 1:710fe444b3b0 - $ hg pull ../local - pulling from ../local - requesting all changes - adding changesets - adding manifests - adding file changes - added 2 changesets with 2 changes to 1 files - (run 'hg update' to get a working copy) - $ hg log --template='{rev}:{node|short}\n' - 1:710fe444b3b0 - 0:5caa672bac26 - $ cd .. - $ hg clone local other2 - requesting all changes - adding changesets - adding manifests - adding file changes - added 2 changesets with 2 changes to 1 files - updating to branch default - 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ hg -R other2 log --template='{rev}:{node|short}\n' - 1:710fe444b3b0 - 0:5caa672bac26 - -test on http - - $ hg -R local serve -p $HGPORT -d --pid-file=local.pid - $ cat local.pid >> "$DAEMON_PIDS" - $ hg clone http://localhost:$HGPORT/ fromhttp - requesting all changes - adding changesets - adding manifests - adding file changes - added 2 changesets with 2 changes to 1 files - updating to branch default - 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ hg -R fromhttp log --template='{rev}:{node|short}\n' - 1:710fe444b3b0 - 0:5caa672bac26 - - $ hg init fromhttp2 - $ cd fromhttp2 - $ hg inc http://localhost:$HGPORT/ --template='{rev}:{node|short}\n' - comparing with http://localhost:$HGPORT/ - 0:5caa672bac26 - 1:710fe444b3b0 - $ hg pull http://localhost:$HGPORT/ - pulling from http://localhost:$HGPORT/ - requesting all changes - adding changesets - adding manifests - adding file changes - added 2 changesets with 2 changes to 1 files - (run 'hg update' to get a working copy) - $ hg log --template='{rev}:{node|short}\n' - 1:710fe444b3b0 - 0:5caa672bac26 - $ hg inc http://localhost:$HGPORT/ --template='{rev}:{node|short}\n' - comparing with http://localhost:$HGPORT/ - searching for changes - no changes found - [1] - -turn draft off again (repo side) - $ cd .. - $ "$TESTDIR/killdaemons.py" - $ hg -R local states --off draft - abort: could not disable non empty state draft - (You may want to use `hg published 'draftheads()'`) - [255] - $ hg -R local published tip - $ hg -R local states --off draft - $ hg -R local serve -p $HGPORT -d --pid-file=local.pid - $ cat local.pid >> "$DAEMON_PIDS" - $ cd fromhttp2 - - $ hg inc http://localhost:$HGPORT/ --template='{rev}:{node|short}\n' - comparing with http://localhost:$HGPORT/ - searching for changes - 2:3c8695235a32 - 3:73585b17392a - $ hg pull http://localhost:$HGPORT/ - pulling from http://localhost:$HGPORT/ - searching for changes - adding changesets - adding manifests - adding file changes - added 2 changesets with 2 changes to 2 files - (run 'hg update' to get a working copy) - $ cd .. - $ "$TESTDIR/killdaemons.py" - -turn draft on again (repo side) - $ hg -R local states draft - $ hg init httpto - $ hg -R httpto serve -p $HGPORT -d --pid-file=remote.pid - $ cat remote.pid >> "$DAEMON_PIDS" - $ cd local - $ hg out http://localhost:$HGPORT/ --template='{rev}:{node|short}\n' - comparing with http://localhost:$HGPORT/ - searching for changes - 0:5caa672bac26 - 1:710fe444b3b0 - 2:3c8695235a32 - 3:73585b17392a - $ hg push http://localhost:$HGPORT/ - pushing to http://localhost:$HGPORT/ - searching for changes - remote: adding changesets - remote: adding manifests - remote: adding file changes - remote: added 4 changesets with 4 changes to 2 files - $ "$TESTDIR/killdaemons.py" diff -r a2e8057117d3 -r 6d461c2143a0 tests/test-ready.t --- a/tests/test-ready.t Mon Dec 19 12:19:00 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,186 +0,0 @@ - $ cat >> $HGRCPATH < [liquid] - > publish = False - > [extensions] - > graphlog= - > EOF - $ echo "states=$(echo $(dirname $TESTDIR))/hgext/states.py" >> $HGRCPATH - - $ mkcommit() { - > echo "$1" > "$1" - > hg add "$1" - > hg ci -m "add $1" - > } - - $ hg init alpha - $ cd alpha - $ hg states ready - $ mkcommit z - $ mkcommit a - $ mkcommit b - $ mkcommit c - $ hg published 1 - $ hg log --graph - @ changeset: 3:090483935bca - | tag: tip - | user: test - | date: Thu Jan 01 00:00:00 1970 +0000 - | summary: add c - | - o changeset: 2:720fd97246d7 - | user: test - | date: Thu Jan 01 00:00:00 1970 +0000 - | summary: add b - | - o changeset: 1:7a344d213ee2 - | user: test - | date: Thu Jan 01 00:00:00 1970 +0000 - | summary: add a - | - o changeset: 0:d32fd17cb041 - user: test - date: Thu Jan 01 00:00:00 1970 +0000 - summary: add z - - $ cat .hg/states/published-heads - 7a344d213ee2eb3359d94630d4e076460d59dbf0 - -publishedheads() should return only revision 1: - $ hg log -r 'publishedheads()' --graph - o changeset: 1:7a344d213ee2 - | user: test - | date: Thu Jan 01 00:00:00 1970 +0000 - | summary: add a - | - -ancestors of publishedheads shows all frozen revisions: - $ hg log -r '::publishedheads()' --graph - o changeset: 1:7a344d213ee2 - | user: test - | date: Thu Jan 01 00:00:00 1970 +0000 - | summary: add a - | - o changeset: 0:d32fd17cb041 - user: test - date: Thu Jan 01 00:00:00 1970 +0000 - summary: add z - - $ cd .. - $ hg init beta - $ cd beta - $ hg states ready - $ hg pull ../alpha --update - pulling from ../alpha - requesting all changes - adding changesets - adding manifests - adding file changes - added 4 changesets with 4 changes to 4 files - 4 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ hg log --graph -r 'publishedheads()' - o changeset: 1:7a344d213ee2 - | user: test - | date: Thu Jan 01 00:00:00 1970 +0000 - | summary: add a - | - -Freeze in beta and push to alpha: - $ hg published 3 - $ mkcommit d - $ hg push --traceback ../alpha - pushing to ../alpha - searching for changes - adding changesets - adding manifests - adding file changes - added 1 changesets with 1 changes to 1 files - $ cd ../alpha - $ hg debugrevspec 'publishedheads()' - 3 - $ hg log --graph -r 'publishedheads()' - @ changeset: 3:090483935bca - | user: test - | date: Thu Jan 01 00:00:00 1970 +0000 - | summary: add c - | - - $ hg log -r tip - changeset: 4:fb98f3f5bba0 - tag: tip - user: test - date: Thu Jan 01 00:00:00 1970 +0000 - summary: add d - - $ mkcommit e - created new head - $ hg up -C 4 - 1 files updated, 0 files merged, 1 files removed, 0 files unresolved - $ hg log -r 'publishedheads()' - changeset: 3:090483935bca - user: test - date: Thu Jan 01 00:00:00 1970 +0000 - summary: add c - - $ hg tag -fr tip babar - $ hg log -r 'publishedheads()' - changeset: 5:cdaaa31e4239 - tag: babar - parent: 3:090483935bca - user: test - date: Thu Jan 01 00:00:00 1970 +0000 - summary: add e - - changeset: 6:bd66bf1525ee - tag: tip - parent: 4:fb98f3f5bba0 - user: test - date: Thu Jan 01 00:00:00 1970 +0000 - summary: Added tag babar for changeset cdaaa31e4239 - -Check rollback - - $ hg merge - 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - (branch merge, don't forget to commit) - $ hg commit -m 'merge' - $ hg published tip - $ hg log -r 'publishedheads()' - changeset: 7:26631d82e09e - tag: tip - parent: 6:bd66bf1525ee - parent: 5:cdaaa31e4239 - user: test - date: Thu Jan 01 00:00:00 1970 +0000 - summary: merge - -test rollback does not mess up the liquid changeset - $ mkcommit bibi - $ hg rollback - repository tip rolled back to revision 7 (undo commit) - working directory now based on revision 7 - $ hg log -r 'publishedheads()' - changeset: 7:26631d82e09e - tag: tip - parent: 6:bd66bf1525ee - parent: 5:cdaaa31e4239 - user: test - date: Thu Jan 01 00:00:00 1970 +0000 - summary: merge - - -test rollback even when rollbacking frozen - - $ mkcommit bubu - $ hg published tip - $ hg rollback - repository tip rolled back to revision 7 (undo commit) - working directory now based on revision 7 - $ hg log -r 'publishedheads()' - changeset: 7:26631d82e09e - tag: tip - parent: 6:bd66bf1525ee - parent: 5:cdaaa31e4239 - user: test - date: Thu Jan 01 00:00:00 1970 +0000 - summary: merge - diff -r a2e8057117d3 -r 6d461c2143a0 tests/test-state-strip.t --- a/tests/test-state-strip.t Mon Dec 19 12:19:00 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,288 +0,0 @@ - - - $ cat >> $HGRCPATH < [web] - > push_ssl = false - > allow_push = * - > [extensions] - > hgext.mq= - > hgext.graphlog= - > EOF - $ echo "states=$(echo $(dirname $TESTDIR))/hgext/states.py" >> $HGRCPATH - - $ mkcommit() { - > echo "$1" > "$1" - > hg add "$1" - > hg ci -m "$1" - > } - $ alias hglog='hg glog --template "{desc} {state} {node}\n"' - - $ hg init alpha - $ cd alpha - $ hg states draft ready - $ mkcommit 0 - $ mkcommit 1 - $ hg up 0 - 0 files updated, 0 files merged, 1 files removed, 0 files unresolved - $ mkcommit 2 - created new head - $ mkcommit 3 - $ mkcommit 4 - $ hg up 3 - 0 files updated, 0 files merged, 1 files removed, 0 files unresolved - $ mkcommit 5 - created new head - $ hg up 1 - 1 files updated, 0 files merged, 3 files removed, 0 files unresolved - $ mkcommit 6 - $ hg published 6 - $ hg ready 4 - $ hglog - @ 6 published 2a653cad66937648173a936140f09a0e780afd76 - | - | o 5 draft ffe7eb8acef3efeceaa566b85a1ac419b0ecb856 - | | - | | o 4 ready 138777f75ddeb6ee0b527cfdb0eebbd1e0037bf6 - | |/ - | o 3 ready 0915e256b0ca7f81dace67bc6fd512bfd1bcab85 - | | - | o 2 ready a00ba83de58390cbbdae1fc580df0bb0db2e8e88 - | | - o | 1 published e8342c9a2ed18fb27f9fdfc48a3105d164a06e77 - |/ - o 0 published 06254b90631198c9b9e426be9886af92fedc9a2d - - -We strip a published heads, so published heads 6 -> 1 - $ hg strip -n 6 - 0 files updated, 0 files merged, 1 files removed, 0 files unresolved - $ hglog - o 5 draft ffe7eb8acef3efeceaa566b85a1ac419b0ecb856 - | - | o 4 ready 138777f75ddeb6ee0b527cfdb0eebbd1e0037bf6 - |/ - o 3 ready 0915e256b0ca7f81dace67bc6fd512bfd1bcab85 - | - o 2 ready a00ba83de58390cbbdae1fc580df0bb0db2e8e88 - | - | @ 1 published e8342c9a2ed18fb27f9fdfc48a3105d164a06e77 - |/ - o 0 published 06254b90631198c9b9e426be9886af92fedc9a2d - - -Back to the previous configuration. -Then strip accros branches and remove draft changesets completly, and cut in -the middle of ready changesets - $ hg up 1 - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ mkcommit 6 - $ hg strip -n 3:6 - 0 files updated, 0 files merged, 1 files removed, 0 files unresolved - $ hglog - o 2 ready a00ba83de58390cbbdae1fc580df0bb0db2e8e88 - | - | @ 1 published e8342c9a2ed18fb27f9fdfc48a3105d164a06e77 - |/ - o 0 published 06254b90631198c9b9e426be9886af92fedc9a2d - -Now merge 1 & 2 then strip merging changeset - $ hg up 1 - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ hg merge 2 - 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - (branch merge, don't forget to commit) - $ hg commit -m Merge - $ hg strip -n 3 - 0 files updated, 0 files merged, 1 files removed, 0 files unresolved - $ hglog - o 2 ready a00ba83de58390cbbdae1fc580df0bb0db2e8e88 - | - | @ 1 published e8342c9a2ed18fb27f9fdfc48a3105d164a06e77 - |/ - o 0 published 06254b90631198c9b9e426be9886af92fedc9a2d - -Do the same but with merge changeset as ready - $ hg up 1 - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ hg merge 2 - 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - (branch merge, don't forget to commit) - $ hg commit -m Merge - $ hg ready 3 - $ hg strip -n 3 - 0 files updated, 0 files merged, 1 files removed, 0 files unresolved - $ hglog - o 2 ready a00ba83de58390cbbdae1fc580df0bb0db2e8e88 - | - | @ 1 published e8342c9a2ed18fb27f9fdfc48a3105d164a06e77 - |/ - o 0 published 06254b90631198c9b9e426be9886af92fedc9a2d - - $ hg log --template "{rev} {node}\n" -r 'readyheads()' - 2 a00ba83de58390cbbdae1fc580df0bb0db2e8e88 - -More complecated case: a merging changeset inheritate a ready state of one of -its child and have another child in draft. And We strip the ready child - $ hg up 1 - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ hg merge 2 - 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - (branch merge, don't forget to commit) - $ hg ci -m 3 - $ mkcommit 4 - $ hg up 3 - 0 files updated, 0 files merged, 1 files removed, 0 files unresolved - $ mkcommit 5 - created new head - $ mkcommit 6 - $ hg ready 6 - $ hglog - @ 6 ready bfd1096b3dd69e57c184e9f43646a9b7e0dd5927 - | - o 5 ready 8195da2a3c382a4acd7ce796b4bc74092f1875eb - | - | o 4 draft 6aaadc67da669af964adabe681c0a78f46b7ce58 - |/ - o 3 ready e7cd12398be70c568cefab9b4ad86a8a2a728a09 - |\ - | o 2 ready a00ba83de58390cbbdae1fc580df0bb0db2e8e88 - | | - o | 1 published e8342c9a2ed18fb27f9fdfc48a3105d164a06e77 - |/ - o 0 published 06254b90631198c9b9e426be9886af92fedc9a2d - - $ hg strip -n 3 - 0 files updated, 0 files merged, 3 files removed, 0 files unresolved - $ hglog - o 2 ready a00ba83de58390cbbdae1fc580df0bb0db2e8e88 - | - | @ 1 published e8342c9a2ed18fb27f9fdfc48a3105d164a06e77 - |/ - o 0 published 06254b90631198c9b9e426be9886af92fedc9a2d - -Quitelly the same as before but we strip before the merging node, from another -branch than the ready changeset, so this changeset is not a parent of the -the explicitly stripped node. - $ hg up 1 - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ mkcommit 3 - $ hg up 2 - 1 files updated, 0 files merged, 2 files removed, 0 files unresolved - $ hg merge 3 - 2 files updated, 0 files merged, 0 files removed, 0 files unresolved - (branch merge, don't forget to commit) - $ hg ci -m '4' - $ mkcommit 5 - $ hg published 3 - $ hg ready 4 - $ hglog - @ 5 draft 0777a3135ec5396c57db4402c71ab8cba2a0ef7e - | - o 4 ready 667667458ecc8cf7763dee1ae172a5a9ebf115f3 - |\ - | o 3 published 03fc50a1c0074093104ff6c5357c486781742b64 - | | - o | 2 ready a00ba83de58390cbbdae1fc580df0bb0db2e8e88 - | | - | o 1 published e8342c9a2ed18fb27f9fdfc48a3105d164a06e77 - |/ - o 0 published 06254b90631198c9b9e426be9886af92fedc9a2d - - $ hg strip -n 3 - 0 files updated, 0 files merged, 3 files removed, 0 files unresolved - $ hglog - o 2 ready a00ba83de58390cbbdae1fc580df0bb0db2e8e88 - | - | @ 1 published e8342c9a2ed18fb27f9fdfc48a3105d164a06e77 - |/ - o 0 published 06254b90631198c9b9e426be9886af92fedc9a2d - - -pathologic case - $ hg up 1 - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ mkcommit 3 - $ hg up 2 - 1 files updated, 0 files merged, 2 files removed, 0 files unresolved - $ hg merge 3 - 2 files updated, 0 files merged, 0 files removed, 0 files unresolved - (branch merge, don't forget to commit) - $ hg ci -m '4' - $ mkcommit 5 - $ hg up 3 - 0 files updated, 0 files merged, 2 files removed, 0 files unresolved - $ mkcommit 6 - created new head - $ hg published 3 - $ hg ready 4 - $ hglog - @ 6 draft aeb74c71311d9305498bbf371746d095b80ff51f - | - | o 5 draft 0777a3135ec5396c57db4402c71ab8cba2a0ef7e - | | - | o 4 ready 667667458ecc8cf7763dee1ae172a5a9ebf115f3 - |/| - o | 3 published 03fc50a1c0074093104ff6c5357c486781742b64 - | | - | o 2 ready a00ba83de58390cbbdae1fc580df0bb0db2e8e88 - | | - o | 1 published e8342c9a2ed18fb27f9fdfc48a3105d164a06e77 - |/ - o 0 published 06254b90631198c9b9e426be9886af92fedc9a2d - - $ hg strip -n 3 - 0 files updated, 0 files merged, 2 files removed, 0 files unresolved - $ hglog - o 2 ready a00ba83de58390cbbdae1fc580df0bb0db2e8e88 - | - | @ 1 published e8342c9a2ed18fb27f9fdfc48a3105d164a06e77 - |/ - o 0 published 06254b90631198c9b9e426be9886af92fedc9a2d - -pathologic case - $ hg up 1 - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ hg merge 2 - 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - (branch merge, don't forget to commit) - $ hg ci -m 3 - $ hg up 2 - 0 files updated, 0 files merged, 1 files removed, 0 files unresolved - $ mkcommit 4 - created new head - $ hg merge 3 - 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - (branch merge, don't forget to commit) - $ hg ci -m 5 - $ hg up 4 - 0 files updated, 0 files merged, 1 files removed, 0 files unresolved - $ mkcommit 6 - created new head - $ hg ready 4 - $ hglog - @ 6 draft 036d507f2b771a3b7cc88580c93d5037bf4bf1bf - | - | o 5 draft 19788060dab104e9385a14c4be2fc5678b9433f0 - |/| - o | 4 ready 0fc8455e844047eab375a1f51816f697551e34cf - | | - | o 3 draft e7cd12398be70c568cefab9b4ad86a8a2a728a09 - |/| - o | 2 ready a00ba83de58390cbbdae1fc580df0bb0db2e8e88 - | | - | o 1 published e8342c9a2ed18fb27f9fdfc48a3105d164a06e77 - |/ - o 0 published 06254b90631198c9b9e426be9886af92fedc9a2d - - $ hg strip -n 1 - $ hglog - @ 6 draft 036d507f2b771a3b7cc88580c93d5037bf4bf1bf - | - o 4 ready 0fc8455e844047eab375a1f51816f697551e34cf - | - o 2 ready a00ba83de58390cbbdae1fc580df0bb0db2e8e88 - | - o 0 published 06254b90631198c9b9e426be9886af92fedc9a2d - - $ hg log --template '{desc} {state} {node}\n' -r 'readyheads()' - 4 ready 0fc8455e844047eab375a1f51816f697551e34cf diff -r a2e8057117d3 -r 6d461c2143a0 tests/test-states-enable.t --- a/tests/test-states-enable.t Mon Dec 19 12:19:00 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,199 +0,0 @@ - - $ cat >> $HGRCPATH < [web] - > push_ssl = false - > allow_push = * - > [extensions] - > EOF - $ echo "states=$(echo $(dirname $TESTDIR))/hgext/states.py" >> $HGRCPATH - - $ mkcommit() { - > echo "$1" > "$1" - > hg add "$1" - > hg ci -m "$1" - > } - $ alias hglog='hg log --template "{rev} {state}\n"' - - $ hg init alpha - $ cd alpha - $ mkcommit 0 - $ mkcommit 1 - - -enable draft: existing changesets stay as published and newer are draft - $ hg states draft - $ hg states - published - draft - $ hglog - 1 published - 0 published - $ mkcommit 2 - $ hglog - 2 draft - 1 published - 0 published - -enable ready: existing changset states are the same, newer are draft - $ hg states ready - $ hg states - published - ready - draft - $ hglog - 2 draft - 1 published - 0 published - $ mkcommit 3 - $ hglog - 3 draft - 2 draft - 1 published - 0 published - - -publish all then enable states in other order - $ hg published tip - $ hg states --off ready draft - $ hglog - 3 published - 2 published - 1 published - 0 published - -enable ready: changesets stay as published and newer are ready - $ hg states ready - $ hglog - 3 published - 2 published - 1 published - 0 published - $ mkcommit 4 - $ hglog - 4 ready - 3 published - 2 published - 1 published - 0 published - -enable draft: changesets stay unchanged and newer are draft - $ hg states draft - $ hglog - 4 ready - 3 published - 2 published - 1 published - 0 published - $ mkcommit 5 - $ hglog - 5 draft - 4 ready - 3 published - 2 published - 1 published - 0 published - -disable ready - $ hg states --off ready - abort: could not disable non empty state ready - (You may want to use `hg published 'readyheads()'`) - [255] - $ hg publish 4 - $ hg states --off ready - $ hg states - published - draft - $ hglog - 5 draft - 4 published - 3 published - 2 published - 1 published - 0 published - $ hg ready 4 - abort: state ready is not activated - (try ``hg states ready`` before) - [255] - -disable draft - $ hg states --off draft - abort: could not disable non empty state draft - (You may want to use `hg published 'draftheads()'`) - [255] - $ hg publish tip - $ hg states --off draft - $ hg states - published - $ hglog - 5 published - 4 published - 3 published - 2 published - 1 published - 0 published - $ hg draft 5 - abort: state draft is not activated - (try ``hg states draft`` before) - [255] - -disable published - $ hg states --off published - abort: could not disable published - [255] - - -enable both draft and ready - $ hg states draft ready - $ hg states - published - ready - draft - $ hglog - 5 published - 4 published - 3 published - 2 published - 1 published - 0 published - $ mkcommit 6 - $ hglog - 6 draft - 5 published - 4 published - 3 published - 2 published - 1 published - 0 published - -disable both draft and ready - $ hg published tip - $ hg states --off draft ready - $ hg states - published - -clever enabling - $ hg states --clever ready - $ hglog - 6 published - 5 published - 4 published - 3 published - 2 published - 1 published - 0 published - - $ cd .. - $ hg init beta - $ cd beta - $ mkcommit 0 - $ mkcommit 1 - $ hg states --clever ready - $ hglog - 1 ready - 0 ready - $ hg states --clever draft - $ hglog - 1 draft - 0 draft - - diff -r a2e8057117d3 -r 6d461c2143a0 tests/test-states-exact.t --- a/tests/test-states-exact.t Mon Dec 19 12:19:00 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,282 +0,0 @@ - $ cat >> $HGRCPATH < [web] - > push_ssl = false - > allow_push = * - > [extensions] - > EOF - $ echo "states=$(echo $(dirname $TESTDIR))/hgext/states.py" >> $HGRCPATH - - $ mkcommit() { - > echo "$1" > "$1" - > hg add "$1" - > hg ci -m "$1" - > } - $ alias hglog='hg log --template "{desc|short} {rev} {state}\n"' - - $ hg init alpha - $ cd alpha - $ hg states ready draft - $ mkcommit a - $ mkcommit b - $ mkcommit c - $ mkcommit d - $ mkcommit e - $ mkcommit f - -|0 - 1 - 2 - 3 - 4 - 5 - - $ hg ready 3 - $ hg published 1 - $ hglog - f 5 draft - e 4 draft - d 3 ready - c 2 ready - b 1 published - a 0 published - -very simple case - $ hg ready --exact 1 - $ hglog - f 5 draft - e 4 draft - d 3 ready - c 2 ready - b 1 ready - a 0 published - -required state not in statesheads - $ hg draft --exact 3 - $ hglog - f 5 draft - e 4 draft - d 3 draft - c 2 ready - b 1 ready - a 0 published - -remove all other states - $ hg draft --exact 0 - $ hglog - f 5 draft - e 4 draft - d 3 draft - c 2 draft - b 1 draft - a 0 draft - -draft was not there before - $ hg ready 5 - $ hg draft --exact 4 - $ hglog - f 5 draft - e 4 draft - d 3 ready - c 2 ready - b 1 ready - a 0 ready - -already in the required state - $ hg draft --exact 5 - $ hglog - f 5 draft - e 4 draft - d 3 ready - c 2 ready - b 1 ready - a 0 ready - -backward and foreward - $ hg published 1 - $ hg ready --exact 1:4 - $ hglog - f 5 draft - e 4 ready - d 3 ready - c 2 ready - b 1 ready - a 0 published - -with complex revset - $ hg draft --exact 'readyheads()' - $ hglog - f 5 draft - e 4 draft - d 3 ready - c 2 ready - b 1 ready - a 0 published - $ hg ready --exact 'publishedheads()' - $ hglog - f 5 draft - e 4 draft - d 3 ready - c 2 ready - b 1 ready - a 0 ready - -Work with branches now - $ hg up 1 - 0 files updated, 0 files merged, 4 files removed, 0 files unresolved - $ mkcommit g - created new head - $ mkcommit h - $ hg up 3 - 2 files updated, 0 files merged, 2 files removed, 0 files unresolved - $ mkcommit i - created new head - $ mkcommit j - -| / - - - - 6 - 7 -|0 - 1 - 2 - 3 - 4 - 5 -| \ - - - - - - - - - - - - 8 - 9 - -Set the new branches as ready - $ hg ready 9 7 - $ hglog - j 9 ready - i 8 ready - h 7 ready - g 6 ready - f 5 draft - e 4 draft - d 3 ready - c 2 ready - b 1 ready - a 0 ready - -with composite revset - $ hg draft --exact 5:5 7:8 - $ hglog - j 9 draft - i 8 draft - h 7 draft - g 6 ready - f 5 draft - e 4 draft - d 3 ready - c 2 ready - b 1 ready - a 0 ready - -cross braches - $ hg published 5 7 9 - $ hg draft --exact 4:6 - $ hglog - j 9 published - i 8 published - h 7 draft - g 6 draft - f 5 draft - e 4 draft - d 3 published - c 2 published - b 1 published - a 0 published - -Ok more complicated stuffs - $ hg up 7 - 2 files updated, 0 files merged, 4 files removed, 0 files unresolved - $ mkcommit k - $ hg published 5 9 10 - -| / - - - - 6 - 7 - - - - - 10 -|0 - 1 - 2 - 3 - 4 - 5 -| \ - - - - - - - - - - - - 8 - 9 - -cross branches and propagation on the same branche - $ hg draft --exact 5:8 - $ hglog - k 10 draft - j 9 draft - i 8 draft - h 7 draft - g 6 draft - f 5 draft - e 4 published - d 3 published - c 2 published - b 1 published - a 0 published - - -cross branches and propagation on multiple branches - $ hg published 10 - $ hg ready --exact 3 6:8 - $ hglog - k 10 ready - j 9 draft - i 8 ready - h 7 ready - g 6 ready - f 5 draft - e 4 ready - d 3 ready - c 2 published - b 1 published - a 0 published - -propagate ready on multiple branches taht contains draft states - $ hg ready --exact 1 - $ hglog - k 10 ready - j 9 draft - i 8 ready - h 7 ready - g 6 ready - f 5 draft - e 4 ready - d 3 ready - c 2 ready - b 1 ready - a 0 published - -brute propagation - $ hg draft --exact 0 - $ hglog - k 10 draft - j 9 draft - i 8 draft - h 7 draft - g 6 draft - f 5 draft - e 4 draft - d 3 draft - c 2 draft - b 1 draft - a 0 draft - -forget non activated state - $ hg init beta - $ cd beta - $ hg states draft - $ mkcommit a - $ mkcommit b - $ mkcommit c - $ mkcommit d - $ mkcommit e - $ mkcommit f - $ hg published 5 - $ hg draft --exact 3 - $ hglog - f 5 draft - e 4 draft - d 3 draft - c 2 published - b 1 published - a 0 published - $ hg states - published - draft - $ hg ready 3 - abort: state ready is not activated - (try ``hg states ready`` before) - [255] - $ hglog - f 5 draft - e 4 draft - d 3 draft - c 2 published - b 1 published - a 0 published - diff -r a2e8057117d3 -r 6d461c2143a0 tests/test-states.t --- a/tests/test-states.t Mon Dec 19 12:19:00 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,43 +0,0 @@ - - $ cat >> $HGRCPATH < [web] - > push_ssl = false - > allow_push = * - > [extensions] - > EOF - $ echo "states=$(echo $(dirname $TESTDIR))/hgext/states.py" >> $HGRCPATH - - $ hg init local - $ hg init other - $ cd local - $ hg states --traceback -v - published - $ hg states draft - $ hg states - published - draft - $ hg states ready - $ hg states - published - ready - draft - $ hg states --off draft - $ hg states - published - ready - $ hg states babar - no state named babar - [1] - $ echo 'babar' >> .hg/states/Enabled - $ hg states - published - ready - $ hg -R ../other states - published - -do nothing if state already deactivated - $ hg states --off draft - state draft already deactivated - $ hg states - published - ready