Prepare 0.1.0 by merging default into stable
stable is now compatible with 2.3 only.
--- a/.hgignore Tue Aug 21 12:43:21 2012 +0200
+++ b/.hgignore Fri Aug 24 11:53:55 2012 +0200
@@ -5,6 +5,9 @@
^html/
\.pyc$
~$
+\.swp$
\.orig$
\.rej$
\.err$
+^tests/easy_run.sh$
+^build/
--- a/README Tue Aug 21 12:43:21 2012 +0200
+++ b/README Fri Aug 24 11:53:55 2012 +0200
@@ -43,6 +43,26 @@
Changelog
==================
+1.0
+
+- Align with Mercurial version 2.3 (drop 2.2 support).
+- stabilize handle killed parent
+- stabilize handle late comer
+- stabilize handle conflicting
+- stabilize get a --continue switch
+- merge and update ignore extinct changeset in most case.
+- new "troubled()" revset
+- summary now reports troubles changesets
+- new touch command
+- new fold command
+- new basic olog alias
+
+- rebase refuse to work on public changeset again
+- rebase explicitly state that there is nothing to rebase because everything is
+ extinct() when that happen.
+- amend now cleanly abort when --change switch is misused
+
+
0.7 -- 2012-08-06
- hook: work around insanely huge value in obsolete pushkey call
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/nopushpublish.py Fri Aug 24 11:53:55 2012 +0200
@@ -0,0 +1,36 @@
+# Extension which prevent changeset to be turn public by push operation
+#
+# Copyright 2011 Logilab SA <contact@logilab.fr>
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+
+from mercurial import extensions, util
+from mercurial import discovery
+
+def checkpublish(orig, repo, remote, outgoing, *args):
+
+ # is remote publishing?
+ publish = True
+ if 'phases' in remote.listkeys('namespaces'):
+ remotephases = remote.listkeys('phases')
+ publish = remotephases.get('publishing', False)
+
+ npublish = 0
+ if publish:
+ for rev in outgoing.missing:
+ if repo[rev].phase():
+ npublish += 1
+ if npublish:
+ repo.ui.warn("Push would publish %s changesets" % npublish)
+
+ ret = orig(repo, remote, outgoing, *args)
+ if npublish:
+ raise util.Abort("Publishing push forbiden",
+ hint="Use `hg phase -p <rev>` to manually publish them")
+
+ return ret
+
+def uisetup(ui):
+ extensions.wrapfunction(discovery, 'checkheads', checkpublish)
--- a/docs/evolve-collaboration.rst Tue Aug 21 12:43:21 2012 +0200
+++ b/docs/evolve-collaboration.rst Fri Aug 24 11:53:55 2012 +0200
@@ -111,7 +111,7 @@
Being now done with grammar and typo fixes, Alice decides it is time to
stabilize again the tree. She does::
- $ hg stabilize
+ $ hg evolve
two times, one for each unstable descendant. The last time, hgview
shows her a straight line again. Wow! that feels a bit like a
--- a/docs/evolve-faq.rst Tue Aug 21 12:43:21 2012 +0200
+++ b/docs/evolve-faq.rst Fri Aug 24 11:53:55 2012 +0200
@@ -96,9 +96,9 @@
Getting changes out of a commit
------------------------------------------------------------
-the ``hg uncommit`` commands allow you to rewrite the current commit to not
-include change for some file. The content of target files are not altered on
-disk and back as "modified"::
+The ``hg uncommit`` command lets you rewrite the parent commit without
+selected changed files. Target files content is not altered and
+appears again as "modified"::
$ hg st
M babar
@@ -112,8 +112,7 @@
Split a changeset
-----------------------
-I you just want to split whole file, you can just use the ``uncommit`` command.
-
+To split on file boundaries, just use ``uncommit`` command.
If you need fine-grained split, there is no official command for that yet.
However, it is easily achieved by manual operation::
@@ -153,7 +152,7 @@
.. warning:: Beware that rebasing obsolete changesets will result in
conflicting versions of the changesets.
-Stabilize history: ``stabilize``
+Stabilize history: ``evolve``
------------------------------------------------------------
When you rewrite (amend) a changeset with children without rewriting
@@ -165,16 +164,6 @@
newest version. This is not done automatically to avoid the
proliferation of useless hidden changesets.
-.. warning:: ``hg stabilize`` have no --continue to use after conflict
- resolution
-
-.. warning:: stabilization does not handle deletion yet.
-
-.. warning:: obsolete currently relies on changesets in secret phase
- to avoid exchanging obsolete and unstable changesets.
-
- XXX details issue here
-
Fix my history afterward: ``prune -n``
------------------------------------------------------------
@@ -209,7 +198,7 @@
You can also use a debug command
- $ hg debugsuccessors
+ $ hg debugobsolete
5eb72dbe0cb4 e8db4aa611f6
c4cbebac3751 4f1c269eab68
@@ -223,8 +212,8 @@
Extinct changesets are hidden using the *hidden* feature of mercurial.
-Only ``hg log`` and ``hgview`` support it. ``hg glog`` Only support that since
-2.2. Other visual viewer don't.
+Only ``hg log``, ``hg glog`` and ``hgview`` support it, other
+graphical viewer do not.
--- a/docs/from-mq.rst Tue Aug 21 12:43:21 2012 +0200
+++ b/docs/from-mq.rst Fri Aug 24 11:53:55 2012 +0200
@@ -12,7 +12,7 @@
qnew ``commit``
qrefresh ``amend``
qpop ``update`` or ``qdown``
-qpush ``update`` or ``gup`` sometimes ``stabilize``
+qpush ``update`` or ``gup`` sometimes ``evolve``
qrm ``prune``
qfold ``amend -c`` (for now, ``collapse`` soon)
qdiff ``odiff``
@@ -84,7 +84,7 @@
hg qref -X
````````````
-To remove change from you current commit use::
+To remove changes from you current commit use::
$ hg uncommit not-ready.txt
@@ -113,7 +113,7 @@
The evolution extension adds a command to rewrite the "out of sync"
changesets:::
- $ hg stabilize
+ $ hg evolve
You can also decide to do it manually using::
--- a/docs/index.rst Tue Aug 21 12:43:21 2012 +0200
+++ b/docs/index.rst Fri Aug 24 11:53:55 2012 +0200
@@ -16,13 +16,14 @@
The effort is split in two parts:
- * The **obsolete marker** concept aims to provide an alternative to ``strip``
- to get rid of changesets.
+ * The **obsolescence marker** concept aims to provide an alternative to ``strip``
+ to get rid of changesets. This concept have been partially implemented in
+ Mercurial 2.3.
* The **evolve** mercurial extension rewrites history using obsolete
*marker* under the hood.
-The first and most important step is by far the **obsolete marker**. However
+The first and most important step is by far the **obsolescence marker**. However
most users will never be directly exposed to the concept. For this reason
this manual starts with changeset evolution.
@@ -69,19 +70,20 @@
Production ready version should hide such details to normal user.
+The evolve extension require mercurial 2.3
+
To enable the evolve extension use::
$ hg clone https://bitbucket.org/marmoute/mutable-history -u stable
- $ mutable-history/enable.sh >> ~/.hgrc
+ $ echo '[extensions]\nevolve=$PWD/mutable-history/hgext/evolve.py' >> ~/.hgrc
-You will probably want to use the associated version of hgview (qt viewer
-recommended). ::
+You will probably want to use hgview_ to visualize obsolescence. Version 1.6.2
+or later is required.
- $ hg clone http://hg-lab.logilab.org/wip/hgview/ -u obsolete
- $ cd hgview
- $ python setup.py install --user
+.. _hgview: http://www.logilab.org/project/hgview/
-Works with mercurial 2.2
+(The old version 0.7 of evolve works with mercurial 2.2 but have far less feature)
+
---
@@ -137,27 +139,13 @@
Here is a list of know issue that will be fixed later:
-* ``hg stabilize`` does not handle merge conflict.
-
- You must fallback to graft or rebase when that happen.
-
-* rewriting conflict are not detected yet``hg stabilize`` does not
- handle them.
-
-* ``hg update`` can move an obsolete parent
-
* you need to provide to `graft --continue -O` if you started you
graft using `-O`.
-* ``hg merge`` considers an extinct head to be a valid target, hence requiring
you to manually specify target all the time.
* trying to exchange obsolete marker with a static http repo will crash.
-* trying to exchange a lot of obsolete markers through http crash.
-
-* Extinct changesets are turned secret by various commands.
-
* Extinct changesets are hidden using the *hidden* feature of mercurial only
supported by a few commands.
--- a/docs/instability.rst Tue Aug 21 12:43:21 2012 +0200
+++ b/docs/instability.rst Fri Aug 24 11:53:55 2012 +0200
@@ -183,11 +183,11 @@
$ hg pull
added 3 changeset
+2 unstable changeset
- (do you want "hg stabilize" ?)
+ (do you want "hg evolve" ?)
working directory parent is obsolete!
$ hg push
outgoing unstable changesets
- (use "hg stabilize" or force the push)
+ (use "hg evolve" or force the push)
And should not not encourage people to create instability
--- a/docs/obs-terms.rst Tue Aug 21 12:43:21 2012 +0200
+++ b/docs/obs-terms.rst Fri Aug 24 11:53:55 2012 +0200
@@ -89,16 +89,16 @@
| | | |
| +--------------------------+-----------------------------+
| | | |
-| | **troublesome** | **unstable** |
+| | **troubled** | **unstable** |
| | | |
-| | *troublesome* has | *unstable* is a changeset |
+| | *troubled* has | *unstable* is a changeset |
| | unresolved issue caused | with obsolete ancestors. |
| | by *obsolete* relations. | |
| | | |
| | Possible issues are | It must be rebased on a |
-| | listed in the next | non *troublesome* base to |
+| | listed in the next | non *troubled* base to |
| | column. It is possible | solve the problem. |
-| | for *troublesome* | |
+| | for *troubled* | |
| | changeset to combine | (possible alternative name: |
| | multiple issue at once. | precarious) |
| | (a.k.a. conflicting and | |
@@ -106,7 +106,7 @@
| | | |
| | (possible alternative | **latecomer** |
| | names: unsettled, | |
-| | troubled) | *latecomer* is a changeset |
+| | troublesome | *latecomer* is a changeset |
| | | that tries to be successor |
| | | of public changesets. |
| | | |
@@ -141,10 +141,13 @@
| | | properly not detected as a |
| | | conflict) |
| | | |
+| | | (possible alternative names:|
+| | | clashing, rival) |
+| | | |
| +--------------------------+-----------------------------+
| | |
| | Mutable changesets which are neither *obsolete* or |
-| | *troublesome* are *"ok"*. |
+| | *troubled* are *"ok"*. |
| | |
| | Do we really need a name for it ? *"ok"* is a pretty |
| | crappy name :-/ other possibilities are: |
@@ -176,7 +179,7 @@
- "ok" changeset
- latecomer
- - troublesome
+ - conflicting
Any better idea are welcome.
@@ -222,7 +225,7 @@
- kill: shall has funny effects when you forget "hg" in front of ``hg kill``.
- obsolete: too vague, too long and too generic.
-Stabilize
+evolve
```````````````
Automatically resolve *troublesome* changesets
@@ -231,9 +234,7 @@
This is an important name as hg pull/push will suggest it the same way it
suggest merging when you add heads.
-I do not like stabilize much.
-
alternative names:
- solve (too generic ?)
-- evolve (too vague)
+- stabilize
--- a/docs/tutorials/tutorial.t Tue Aug 21 12:43:21 2012 +0200
+++ b/docs/tutorials/tutorial.t Fri Aug 24 11:53:55 2012 +0200
@@ -223,7 +223,7 @@
adding manifests
adding file changes
added 1 changesets with 1 changes to 1 files (+1 heads)
- (run 'hg update' to get a working copy)
+ (run 'hg heads .' to see heads, 'hg merge' to merge)
I now have a new heads. Note that this remote head is immutable
@@ -585,7 +585,7 @@
|
| @ ffa278c50818 (draft): bathroom stuff
| |
- o | 8a79ae8b029e (draft): bathroom stuff
+ x | 8a79ae8b029e (draft): bathroom stuff
|/
o a2fccc2e7b08 (public): SPAM SPAM
|
@@ -610,7 +610,7 @@
pushing to $TESTTMP/other
searching for changes
abort: push includes an unstable changeset: 9ac5d0e790a2!
- (use 'hg stabilize' to get a stable history (or --force to proceed))
+ (use 'hg stabilize' to get a stable history or --force to ignore warnings)
[255]
@@ -723,7 +723,7 @@
$ hg log -G
o ae45c0c3092a (draft): SPAM SPAM SPAM
|
- o 437efbcaf700 (draft): animals
+ x 437efbcaf700 (draft): animals
|
@ ffa278c50818 (draft): bathroom stuff
|
--- a/enable.sh Tue Aug 21 12:43:21 2012 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,52 +0,0 @@
-#!/bin/sh
-
-here=`python -c "import os; print os.path.realpath('$0')"`
-repo_root=`dirname "$here"`
-
-if !( hg --version -q | grep -qe 'version 2\.[2-9]' ); then
- echo 'You need mercurial 2.2 or later' >&2
- exit 2
-fi
-
-
-
-cat << EOF >&2
-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
-XXX Add lines below to the [extensions] section of you hgrc XXX
-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
-
-
-EOF
-
-cat << EOF | sed -e "s#XXXREPOPATHXXX#${repo_root}#"
-[extensions]
-### experimental extensions for history rewriting
-
-# obsolete relation support (will move in core)
-obsolete=XXXREPOPATHXXX/hgext/obsolete.py
-
-# history rewriting UI
-# needed by evolve
-hgext.rebase=
-evolve=XXXREPOPATHXXX/hgext/evolve.py
-
-
-[alias]
-### useful alias to check future amend result
-# equivalent to the qdiff command for mq
-
-# diff
-pdiff=diff --rev .^
-
-# status
-pstatus=status --rev .^
-
-# diff with the previous amend
-odiff=diff --rev 'limit(precursors(.),1)' --rev .
-EOF
-
-cat << EOF >&2
-
-
-### check qsync-enable.sh if your need mq export too.
-EOF
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/__init__.py Fri Aug 24 11:53:55 2012 +0200
@@ -0,0 +1,1 @@
+#f00
--- a/hgext/evolve.py Tue Aug 21 12:43:21 2012 +0200
+++ b/hgext/evolve.py Fri Aug 24 11:53:55 2012 +0200
@@ -7,23 +7,1098 @@
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
-'''a set of commands to handle changeset mutation'''
+'''Extends Mercurial feature related to Changeset Evolution
+
+This extension Provide several command tommutate history and deal with issue it may raise.
+
+It also:
+
+ - enable the "Changeset Obsolescence" feature of mercurial,
+ - alter core command and extension that rewrite history to use this feature,
+ - improve some aspect of the early implementation in 2.3
+'''
+import random
+
+from mercurial import util
+
+try:
+ from mercurial import obsolete
+ if not obsolete._enabled:
+ obsolete._enabled = True
+except ImportError:
+ raise util.Abort('Obsolete extension requires Mercurial 2.3 (or later)')
+
+from mercurial import bookmarks
from mercurial import cmdutil
-from mercurial import scmutil
-from mercurial import node
-from mercurial import error
-from mercurial import extensions
-from mercurial import commands
-from mercurial import bookmarks
-from mercurial import phases
from mercurial import commands
from mercurial import context
from mercurial import copies
-from mercurial import util
+from mercurial import discovery
+from mercurial import error
+from mercurial import extensions
+from mercurial import hg
+from mercurial import localrepo
+from mercurial import merge
+from mercurial import node
+from mercurial import phases
+from mercurial import revset
+from mercurial import scmutil
+from mercurial import templatekw
from mercurial.i18n import _
-from mercurial.commands import walkopts, commitopts, commitopts2, logopts
-from mercurial import hg
+from mercurial.commands import walkopts, commitopts, commitopts2
+from mercurial.node import nullid
+
+
+
+# This extension contains the following code
+#
+# - Extension Helper code
+# - Obsolescence cache
+# - ...
+# - Older format compat
+
+
+
+#####################################################################
+### Extension helper ###
+#####################################################################
+
+class exthelper(object):
+ """Helper for modular extension setup
+
+ A single helper should be instanciated for each extension. Helper
+ methods are then used as decorator for various purpose.
+
+ All decorators return the original function and may be chained.
+ """
+
+ def __init__(self):
+ self._uicallables = []
+ self._extcallables = []
+ self._repocallables = []
+ self._revsetsymbols = []
+ self._templatekws = []
+ self._commandwrappers = []
+ self._extcommandwrappers = []
+ self._functionwrappers = []
+ self._duckpunchers = []
+
+ def final_uisetup(self, ui):
+ """Method to be used as the extension uisetup
+
+ The following operations belong here:
+
+ - Changes to ui.__class__ . The ui object that will be used to run the
+ command has not yet been created. Changes made here will affect ui
+ objects created after this, and in particular the ui that will be
+ passed to runcommand
+ - Command wraps (extensions.wrapcommand)
+ - Changes that need to be visible to other extensions: because
+ initialization occurs in phases (all extensions run uisetup, then all
+ run extsetup), a change made here will be visible to other extensions
+ during extsetup
+ - Monkeypatch or wrap function (extensions.wrapfunction) of dispatch
+ module members
+ - Setup of pre-* and post-* hooks
+ - pushkey setup
+ """
+ for cont, funcname, func in self._duckpunchers:
+ setattr(cont, funcname, func)
+ for command, wrapper in self._commandwrappers:
+ extensions.wrapcommand(commands.table, command, wrapper)
+ for cont, funcname, wrapper in self._functionwrappers:
+ extensions.wrapfunction(cont, funcname, wrapper)
+ for c in self._uicallables:
+ c(ui)
+
+ def final_extsetup(self, ui):
+ """Method to be used as a the extension extsetup
+
+ The following operations belong here:
+
+ - Changes depending on the status of other extensions. (if
+ extensions.find('mq'))
+ - Add a global option to all commands
+ - Register revset functions
+ """
+ knownexts = {}
+ for name, symbol in self._revsetsymbols:
+ revset.symbols[name] = symbol
+ for name, kw in self._templatekws:
+ templatekw.keywords[name] = kw
+ for ext, command, wrapper in self._extcommandwrappers:
+ if ext not in knownexts:
+ e = extensions.find(ext)
+ if e is None:
+ raise util.Abort('extension %s not found' % ext)
+ knownexts[ext] = e.cmdtable
+ extensions.wrapcommand(knownexts[ext], commands, wrapper)
+ for c in self._extcallables:
+ c(ui)
+
+ def final_reposetup(self, ui, repo):
+ """Method to be used as a the extension reposetup
+
+ The following operations belong here:
+
+ - All hooks but pre-* and post-*
+ - Modify configuration variables
+ - Changes to repo.__class__, repo.dirstate.__class__
+ """
+ for c in self._repocallables:
+ c(ui, repo)
+
+ def uisetup(self, call):
+ """Decorated function will be executed during uisetup
+
+ example::
+
+ @eh.uisetup
+ def setupbabar(ui):
+ print 'this is uisetup!'
+ """
+ self._uicallables.append(call)
+ return call
+
+ def extsetup(self, call):
+ """Decorated function will be executed during extsetup
+
+ example::
+
+ @eh.extsetup
+ def setupcelestine(ui):
+ print 'this is extsetup!'
+ """
+ self._uicallables.append(call)
+ return call
+
+ def reposetup(self, call):
+ """Decorated function will be executed during reposetup
+
+ example::
+
+ @eh.reposetup
+ def setupzephir(ui, repo):
+ print 'this is reposetup!'
+ """
+ self._repocallables.append(call)
+ return call
+
+ def revset(self, symbolname):
+ """Decorated function is a revset symbol
+
+ The name of the symbol must be given as the decorator argument.
+ The symbol is added during `extsetup`.
+
+ example::
+
+ @eh.revset('hidden')
+ def revsetbabar(repo, subset, x):
+ args = revset.getargs(x, 0, 0, 'babar accept no argument')
+ return [r for r in subset if 'babar' in repo[r].description()]
+ """
+ def dec(symbol):
+ self._revsetsymbols.append((symbolname, symbol))
+ return symbol
+ return dec
+
+
+ def templatekw(self, keywordname):
+ """Decorated function is a revset keyword
+
+ The name of the keyword must be given as the decorator argument.
+ The symbol is added during `extsetup`.
+
+ example::
+
+ @eh.templatekw('babar')
+ def kwbabar(ctx):
+ return 'babar'
+ """
+ def dec(keyword):
+ self._templatekws.append((keywordname, keyword))
+ return keyword
+ return dec
+
+ def wrapcommand(self, command, extension=None):
+ """Decorated function is a command wrapper
+
+ The name of the command must be given as the decorator argument.
+ The wrapping is installed during `uisetup`.
+
+ If the second option `extension` argument is provided, the wrapping
+ will be applied in the extension commandtable. This argument must be a
+ string that will be searched using `extension.find` if not found and
+ Abort error is raised. If the wrapping applies to an extension, it is
+ installed during `extsetup`
+
+ example::
+
+ @eh.wrapcommand('summary')
+ def wrapsummary(orig, ui, repo, *args, **kwargs):
+ ui.note('Barry!')
+ return orig(ui, repo, *args, **kwargs)
+
+ """
+ def dec(wrapper):
+ if extension is None:
+ self._commandwrappers.append((command, wrapper))
+ else:
+ self._extcommandwrappers.append((extension, command, wrapper))
+ return wrapper
+ return dec
+
+ def wrapfunction(self, container, funcname):
+ """Decorated function is a function wrapper
+
+ This function takes two arguments, the container and the name of the
+ function to wrap. The wrapping is performed during `uisetup`.
+ (there is no extension support)
+
+ example::
+
+ @eh.function(discovery, 'checkheads')
+ def wrapfunction(orig, *args, **kwargs):
+ ui.note('His head smashed in and his heart cut out')
+ return orig(*args, **kwargs)
+ """
+ def dec(wrapper):
+ self._functionwrappers.append((container, funcname, wrapper))
+ return wrapper
+ return dec
+
+ def addattr(self, container, funcname):
+ """Decorated function is to be added to the container
+
+ This function takes two arguments, the container and the name of the
+ function to wrap. The wrapping is performed during `uisetup`.
+
+ example::
+
+ @eh.function(context.changectx, 'babar')
+ def babar(ctx):
+ return 'babar' in ctx.description
+ """
+ def dec(func):
+ self._duckpunchers.append((container, funcname, func))
+ return func
+ return dec
+
+eh = exthelper()
+uisetup = eh.final_uisetup
+extsetup = eh.final_extsetup
+reposetup = eh.final_reposetup
+
+#####################################################################
+### Obsolescence Caching Logic ###
+#####################################################################
+
+# Obsolescence related logic can be very slow if we don't have efficient cache.
+#
+# This section implements a cache mechanism that did not make it into core for
+# time reason. It store meaningful set of revision related to obsolescence
+# (obsolete, unstabletble ...
+#
+# Here is:
+#
+# - Computation of meaningful set,
+# - Cache access logic,
+# - Cache invalidation logic,
+# - revset and ctx using this cache.
+#
+
+
+### Computation of meaningful set
+#
+# Most set can be computed with "simple" revset.
+
+#: { set name -> function to compute this set } mapping
+#: function take a single "repo" argument.
+#:
+#: Use the `cachefor` decorator to register new cache function
+cachefuncs = {}
+def cachefor(name):
+ """Decorator to register a function as computing the cache for a set"""
+ def decorator(func):
+ assert name not in cachefuncs
+ cachefuncs[name] = func
+ return func
+ return decorator
+
+@cachefor('obsolete')
+def _computeobsoleteset(repo):
+ """the set of obsolete revisions"""
+ obs = set()
+ nm = repo.changelog.nodemap
+ for prec in repo.obsstore.precursors:
+ rev = nm.get(prec)
+ if rev is not None:
+ obs.add(rev)
+ return set(repo.revs('%ld - public()', obs))
+
+@cachefor('unstable')
+def _computeunstableset(repo):
+ """the set of non obsolete revisions with obsolete parents"""
+ return set(repo.revs('(obsolete()::) - obsolete()'))
+
+@cachefor('suspended')
+def _computesuspendedset(repo):
+ """the set of obsolete parents with non obsolete descendants"""
+ return set(repo.revs('obsolete() and obsolete()::unstable()'))
+
+@cachefor('extinct')
+def _computeextinctset(repo):
+ """the set of obsolete parents without non obsolete descendants"""
+ return set(repo.revs('obsolete() - obsolete()::unstable()'))
+
+@eh.wrapfunction(obsolete.obsstore, '__init__')
+def _initobsstorecache(orig, obsstore, *args, **kwargs):
+ """add a cache attribute to obsstore"""
+ obsstore.caches = {}
+ return orig(obsstore, *args, **kwargs)
+
+### Cache access
+
+def getobscache(repo, name):
+ """Return the set of revision that belong to the <name> set
+
+ Such access may compute the set and cache it for future use"""
+ if not repo.obsstore:
+ return ()
+ if name not in repo.obsstore.caches:
+ repo.obsstore.caches[name] = cachefuncs[name](repo)
+ return repo.obsstore.caches[name]
+
+### Cache clean up
+#
+# To be simple we need to invalidate obsolescence cache when:
+#
+# - new changeset is added:
+# - public phase is changed
+# - obsolescence marker are added
+# - strip is used a repo
+
+
+def clearobscaches(repo):
+ """Remove all obsolescence related cache from a repo
+
+ This remove all cache in obsstore is the obsstore already exist on the
+ repo.
+
+ (We could be smarter here)"""
+ if 'obsstore' in repo._filecache:
+ repo.obsstore.caches.clear()
+
+@eh.wrapfunction(localrepo.localrepository, 'addchangegroup') # new changeset
+@eh.wrapfunction(phases, 'retractboundary') # phase movement
+@eh.wrapfunction(phases, 'advanceboundary') # phase movement
+@eh.wrapfunction(localrepo.localrepository, 'destroyed') # strip
+def wrapclearcache(orig, repo, *args, **kwargs):
+ try:
+ return orig(repo, *args, **kwargs)
+ finally:
+ # we are a bit wide here
+ # we could restrict to:
+ # advanceboundary + phase==public
+ # retractboundary + phase==draft
+ clearobscaches(repo)
+
+@eh.wrapfunction(obsolete.obsstore, 'add') # new marker
+def clearonadd(orig, obsstore, *args, **kwargs):
+ try:
+ return orig(obsstore, *args, **kwargs)
+ finally:
+ obsstore.caches.clear()
+
+### Use the case
+# Function in core that could benefic from the cache are overwritten by cache using version
+
+# changectx method
+
+@eh.addattr(context.changectx, 'unstable')
+def unstable(ctx):
+ """is the changeset unstable (have obsolete ancestor)"""
+ if ctx.node() is None:
+ return False
+ return ctx.rev() in getobscache(ctx._repo, 'unstable')
+
+
+@eh.addattr(context.changectx, 'extinct')
+def extinct(ctx):
+ """is the changeset extinct by other"""
+ if ctx.node() is None:
+ return False
+ return ctx.rev() in getobscache(ctx._repo, 'extinct')
+
+# revset
+
+@eh.revset('obsolete')
+def revsetobsolete(repo, subset, x):
+ """``obsolete()``
+ Changeset is obsolete.
+ """
+ args = revset.getargs(x, 0, 0, 'obsolete takes no argument')
+ obsoletes = getobscache(repo, 'obsolete')
+ return [r for r in subset if r in obsoletes]
+
+@eh.revset('unstable')
+def revsetunstable(repo, subset, x):
+ """``unstable()``
+ Unstable changesets are non-obsolete with obsolete ancestors.
+ """
+ args = revset.getargs(x, 0, 0, 'unstable takes no arguments')
+ unstables = getobscache(repo, 'unstable')
+ return [r for r in subset if r in unstables]
+
+@eh.revset('extinct')
+def revsetextinct(repo, subset, x):
+ """``extinct()``
+ Obsolete changesets with obsolete descendants only.
+ """
+ args = revset.getargs(x, 0, 0, 'extinct takes no arguments')
+ extincts = getobscache(repo, 'extinct')
+ return [r for r in subset if r in extincts]
+
+#####################################################################
+### Complete troubles computation logic ###
+#####################################################################
+
+# there is two kind of trouble not handled by core right now:
+# - latecomer: (successors for public changeset)
+# - conflicting: (two changeset try to succeed to the same precursors)
+#
+# This section add support for those two addition trouble
+#
+# - Cache computation
+# - revset and ctx method
+# - push warning
+
+### Cache computation
+latediff = 1 # flag to prevent taking late comer fix into account
+
+@cachefor('latecomer')
+def _computelatecomerset(repo):
+ """the set of rev trying to obsolete public revision"""
+ candidates = _allsuccessors(repo, repo.revs('public()'),
+ haltonflags=latediff)
+ query = '%ld - obsolete() - public()'
+ return set(repo.revs(query, candidates))
+
+@cachefor('conflicting')
+def _computeconflictingset(repo):
+ """the set of rev trying to obsolete public revision"""
+ conflicting = set()
+ obsstore = repo.obsstore
+ newermap = {}
+ for ctx in repo.set('(not public()) - obsolete()'):
+ prec = obsstore.successors.get(ctx.node(), ())
+ toprocess = set(prec)
+ while toprocess:
+ prec = toprocess.pop()[0]
+ if prec not in newermap:
+ newermap[prec] = newerversion(repo, prec)
+ newer = [n for n in newermap[prec] if n] # filter kill
+ if len(newer) > 1:
+ conflicting.add(ctx.rev())
+ break
+ toprocess.update(obsstore.successors.get(prec, ()))
+ return conflicting
+
+### changectx method
+
+@eh.addattr(context.changectx, 'latecomer')
+def latecomer(ctx):
+ """is the changeset latecomer (Try to succeed to public change)"""
+ if ctx.node() is None:
+ return False
+ return ctx.rev() in getobscache(ctx._repo, 'latecomer')
+
+@eh.addattr(context.changectx, 'conflicting')
+def conflicting(ctx):
+ """is the changeset conflicting (Try to succeed to public change)"""
+ if ctx.node() is None:
+ return False
+ return ctx.rev() in getobscache(ctx._repo, 'conflicting')
+
+### revset symbol
+
+@eh.revset('latecomer')
+def revsetlatecomer(repo, subset, x):
+ """``latecomer()``
+ Changesets marked as successors of public changesets.
+ """
+ args = revset.getargs(x, 0, 0, 'latecomer takes no arguments')
+ lates = getobscache(repo, 'latecomer')
+ return [r for r in subset if r in lates]
+
+@eh.revset('conflicting')
+def revsetconflicting(repo, subset, x):
+ """``conflicting()``
+ Changesets marked as successors of a same changeset.
+ """
+ args = revset.getargs(x, 0, 0, 'conflicting takes no arguments')
+ conf = getobscache(repo, 'conflicting')
+ return [r for r in subset if r in conf]
+
+
+### Discovery wrapping
+
+@eh.wrapfunction(discovery, 'checkheads')
+def wrapcheckheads(orig, repo, remote, outgoing, *args, **kwargs):
+ """wrap mercurial.discovery.checkheads
+
+ * prevent latecomer and unstable to be pushed
+ """
+ # do not push instability
+ for h in outgoing.missingheads:
+ # Checking heads is enough, obsolete descendants are either
+ # obsolete or unstable.
+ ctx = repo[h]
+ if ctx.latecomer():
+ raise util.Abort(_("push includes a latecomer changeset: %s!")
+ % ctx)
+ if ctx.conflicting():
+ raise util.Abort(_("push includes a conflicting changeset: %s!")
+ % ctx)
+ return orig(repo, remote, outgoing, *args, **kwargs)
+
+#####################################################################
+### Filter extinct changeset from common operation ###
+#####################################################################
+
+@eh.wrapfunction(merge, 'update')
+def wrapmergeupdate(orig, repo, node, *args, **kwargs):
+ """ensure we don't automatically update on hidden changeset"""
+ if node is None:
+ # tip of current branch
+ branch = repo[None].branch()
+ node = repo.revs('last((.:: and branch(%s)) - hidden())', branch)[0]
+ return orig(repo, node, *args, **kwargs)
+
+@eh.wrapfunction(localrepo.localrepository, 'branchtip')
+def obsbranchtip(orig, repo, branch):
+ """ensure "stable" reference does not end on a hidden changeset"""
+ result = ()
+ heads = repo.branchmap().get(branch, ())
+ if heads:
+ result = list(repo.set('last(heads(branch(%n) - hidden()))', heads[0]))
+ if not result:
+ raise error.RepoLookupError(_("unknown branch '%s'") % branch)
+ return result[0].node()
+
+
+#####################################################################
+### Additional Utilities ###
+#####################################################################
+
+# This section contains a lot of small utility function and method
+
+# - Function to create markers
+# - useful alias pstatus and pdiff (should probably go in evolve)
+# - "troubles" method on changectx
+# - function to travel throught the obsolescence graph
+# - function to find useful changeset to stabilize
+
+### Marker Create
+
+def createmarkers(repo, relations, metadata=None, flag=0):
+ """Add obsolete markers between changeset in a repo
+
+ <relations> must be an iterable of (<old>, (<new>, ...)) tuple.
+ `old` and `news` are changectx.
+
+ Current user and date are used except if specified otherwise in the
+ metadata attribute.
+
+ /!\ assume the repo have been locked by the user /!\
+ """
+ # prepare metadata
+ if metadata is None:
+ metadata = {}
+ if 'date' not in metadata:
+ metadata['date'] = '%i %i' % util.makedate()
+ if 'user' not in metadata:
+ metadata['user'] = repo.ui.username()
+ # check future marker
+ tr = repo.transaction('add-obsolescence-marker')
+ try:
+ for prec, sucs in relations:
+ if not prec.mutable():
+ raise util.Abort("Cannot obsolete immutable changeset: %s" % prec)
+ nprec = prec.node()
+ nsucs = tuple(s.node() for s in sucs)
+ if nprec in nsucs:
+ raise util.Abort("Changeset %s cannot obsolete himself" % prec)
+ repo.obsstore.create(tr, nprec, nsucs, flag, metadata)
+ clearobscaches(repo)
+ tr.close()
+ finally:
+ tr.release()
+
+
+### Useful alias
+
+@eh.uisetup
+def _installalias(ui):
+ if ui.config('alias', 'pstatus', None) is None:
+ ui.setconfig('alias', 'pstatus', 'status --rev .^')
+ if ui.config('alias', 'pdiff', None) is None:
+ ui.setconfig('alias', 'pdiff', 'diff --rev .^')
+ if ui.config('alias', 'olog', None) is None:
+ ui.setconfig('alias', 'olog', "log -r 'precursors(.)' --hidden")
+ if ui.config('alias', 'odiff', None) is None:
+ ui.setconfig('alias', 'odiff', "diff --rev 'limit(precursors(.),1)' --rev .")
+
+# - "troubles" method on changectx
+
+@eh.addattr(context.changectx, 'troubles')
+def troubles(ctx):
+ """Return a tuple listing all the troubles that affect a changeset
+
+ Troubles may be "unstable", "latecomer" or "conflicting".
+ """
+ troubles = []
+ if ctx.unstable():
+ troubles.append('unstable')
+ if ctx.latecomer():
+ troubles.append('latecomer')
+ if ctx.conflicting():
+ troubles.append('conflicting')
+ return tuple(troubles)
+
+### Troubled revset symbol
+
+@eh.revset('troubled')
+def revsetlatecomer(repo, subset, x):
+ """``troubled()``
+ Changesets with troubles.
+ """
+ _ = revset.getargs(x, 0, 0, 'troubled takes no arguments')
+ return list(repo.revs('%ld and (unstable() + latecomer() + conflicting())',
+ subset))
+
+
+### Obsolescence graph
+
+# XXX SOME MAJOR CLEAN UP TO DO HERE XXX
+
+def _precursors(repo, s):
+ """Precursor of a changeset"""
+ cs = set()
+ nm = repo.changelog.nodemap
+ markerbysubj = repo.obsstore.successors
+ for r in s:
+ for p in markerbysubj.get(repo[r].node(), ()):
+ pr = nm.get(p[0])
+ if pr is not None:
+ cs.add(pr)
+ return cs
+
+def _allprecursors(repo, s): # XXX we need a better naming
+ """transitive precursors of a subset"""
+ toproceed = [repo[r].node() for r in s]
+ seen = set()
+ allsubjects = repo.obsstore.successors
+ while toproceed:
+ nc = toproceed.pop()
+ for mark in allsubjects.get(nc, ()):
+ np = mark[0]
+ if np not in seen:
+ seen.add(np)
+ toproceed.append(np)
+ nm = repo.changelog.nodemap
+ cs = set()
+ for p in seen:
+ pr = nm.get(p)
+ if pr is not None:
+ cs.add(pr)
+ return cs
+
+def _successors(repo, s):
+ """Successors of a changeset"""
+ cs = set()
+ nm = repo.changelog.nodemap
+ markerbyobj = repo.obsstore.precursors
+ for r in s:
+ for p in markerbyobj.get(repo[r].node(), ()):
+ for sub in p[1]:
+ sr = nm.get(sub)
+ if sr is not None:
+ cs.add(sr)
+ return cs
+
+def _allsuccessors(repo, s, haltonflags=0): # XXX we need a better naming
+ """transitive successors of a subset
+
+ haltonflags allows to provide flags which prevent the evaluation of a
+ marker. """
+ toproceed = [repo[r].node() for r in s]
+ seen = set()
+ allobjects = repo.obsstore.precursors
+ while toproceed:
+ nc = toproceed.pop()
+ for mark in allobjects.get(nc, ()):
+ if mark[2] & haltonflags:
+ continue
+ for sub in mark[1]:
+ if sub == nullid:
+ continue # should not be here!
+ if sub not in seen:
+ seen.add(sub)
+ toproceed.append(sub)
+ nm = repo.changelog.nodemap
+ cs = set()
+ for s in seen:
+ sr = nm.get(s)
+ if sr is not None:
+ cs.add(sr)
+ return cs
+
+
+
+def newerversion(repo, obs):
+ """Return the newer version of an obsolete changeset"""
+ toproceed = set([(obs,)])
+ # XXX known optimization available
+ newer = set()
+ objectrels = repo.obsstore.precursors
+ while toproceed:
+ current = toproceed.pop()
+ assert len(current) <= 1, 'splitting not handled yet. %r' % current
+ current = [n for n in current if n != nullid]
+ if current:
+ n, = current
+ if n in objectrels:
+ markers = objectrels[n]
+ for mark in markers:
+ toproceed.add(tuple(mark[1]))
+ else:
+ newer.add(tuple(current))
+ else:
+ newer.add(())
+ return sorted(newer)
+
+
+#####################################################################
+### Extending revset and template ###
+#####################################################################
+
+# this section add several useful revset symbol not yet in core.
+# they are subject to changes
+
+### hidden revset is not in core yet
+
+@eh.revset('hidden')
+def revsethidden(repo, subset, x):
+ """``hidden()``
+ Changeset is hidden.
+ """
+ args = revset.getargs(x, 0, 0, 'hidden takes no argument')
+ return [r for r in subset if r in repo.hiddenrevs]
+
+### XXX I'm not sure this revset is useful
+@eh.revset('suspended')
+def revsetsuspended(repo, subset, x):
+ """``suspended()``
+ Obsolete changesets with non-obsolete descendants.
+ """
+ args = revset.getargs(x, 0, 0, 'suspended takes no arguments')
+ suspended = getobscache(repo, 'suspended')
+ return [r for r in subset if r in suspended]
+
+
+@eh.revset('precursors')
+def revsetprecursors(repo, subset, x):
+ """``precursors(set)``
+ Immediate precursors of changesets in set.
+ """
+ s = revset.getset(repo, range(len(repo)), x)
+ cs = _precursors(repo, s)
+ return [r for r in subset if r in cs]
+
+
+@eh.revset('allprecursors')
+def revsetallprecursors(repo, subset, x):
+ """``allprecursors(set)``
+ Transitive precursors of changesets in set.
+ """
+ s = revset.getset(repo, range(len(repo)), x)
+ cs = _allprecursors(repo, s)
+ return [r for r in subset if r in cs]
+
+
+@eh.revset('successors')
+def revsetsuccessors(repo, subset, x):
+ """``successors(set)``
+ Immediate successors of changesets in set.
+ """
+ s = revset.getset(repo, range(len(repo)), x)
+ cs = _successors(repo, s)
+ return [r for r in subset if r in cs]
+
+@eh.revset('allsuccessors')
+def revsetallsuccessors(repo, subset, x):
+ """``allsuccessors(set)``
+ Transitive successors of changesets in set.
+ """
+ s = revset.getset(repo, range(len(repo)), x)
+ cs = _allsuccessors(repo, s)
+ return [r for r in subset if r in cs]
+
+### template keywords
+# XXX it does not handle troubles well :-/
+
+@eh.templatekw('obsolete')
+def obsoletekw(repo, ctx, templ, **args):
+ """:obsolete: String. The obsolescence level of the node, could be
+ ``stable``, ``unstable``, ``suspended`` or ``extinct``.
+ """
+ rev = ctx.rev()
+ if ctx.obsolete():
+ if ctx.extinct():
+ return 'extinct'
+ else:
+ return 'suspended'
+ elif ctx.unstable():
+ return 'unstable'
+ return 'stable'
+
+#####################################################################
+### Various trouble warning ###
+#####################################################################
+
+# This section take care of issue warning to the user when troubles appear
+
+@eh.wrapcommand("update")
+@eh.wrapcommand("pull")
+def wrapmayobsoletewc(origfn, ui, repo, *args, **opts):
+ """Warn that the working directory parent is an obsolete changeset"""
+ res = origfn(ui, repo, *args, **opts)
+ if repo['.'].obsolete():
+ ui.warn(_('Working directory parent is obsolete\n'))
+ return res
+
+# XXX this could wrap transaction code
+# XXX (but this is a bit a layer violation)
+@eh.wrapcommand("commit")
+@eh.wrapcommand("push")
+@eh.wrapcommand("pull")
+@eh.wrapcommand("graft")
+@eh.wrapcommand("phase")
+@eh.wrapcommand("unbundle")
+def warnobserrors(orig, ui, repo, *args, **kwargs):
+ """display warning is the command resulted in more instable changeset"""
+ priorunstables = len(repo.revs('unstable()'))
+ priorlatecomers = len(repo.revs('latecomer()'))
+ priorconflictings = len(repo.revs('conflicting()'))
+ ret = orig(ui, repo, *args, **kwargs)
+ newunstables = len(repo.revs('unstable()')) - priorunstables
+ newlatecomers = len(repo.revs('latecomer()')) - priorlatecomers
+ newconflictings = len(repo.revs('conflicting()')) - priorconflictings
+ if newunstables > 0:
+ ui.warn(_('%i new unstable changesets\n') % newunstables)
+ if newlatecomers > 0:
+ ui.warn(_('%i new latecomer changesets\n') % newlatecomers)
+ if newconflictings > 0:
+ ui.warn(_('%i new conflicting changesets\n') % newconflictings)
+ return ret
+
+@eh.reposetup
+def _repostabilizesetup(ui, repo):
+ """Add a hint for "hg evolve" when troubles make push fails
+ """
+ if not repo.local():
+ return
+
+ opush = repo.push
+
+ class evolvingrepo(repo.__class__):
+ def push(self, remote, *args, **opts):
+ """wrapper around pull that pull obsolete relation"""
+ try:
+ result = opush(remote, *args, **opts)
+ except util.Abort, ex:
+ hint = _("use 'hg evolve' to get a stable history "
+ "or --force to ignore warnings")
+ if (len(ex.args) >= 1
+ and ex.args[0].startswith('push includes ')
+ and ex.hint is None):
+ ex.hint = hint
+ raise
+ return result
+ repo.__class__ = evolvingrepo
+
+@eh.wrapcommand("summary")
+def obssummary(orig, ui, repo, *args, **kwargs):
+ ret = orig(ui, repo, *args, **kwargs)
+ nbunstable = len(getobscache(repo, 'unstable'))
+ nblatecomer = len(getobscache(repo, 'latecomer'))
+ nbconflicting = len(getobscache(repo, 'unstable'))
+ if nbunstable:
+ ui.write('unstable: %i changesets\n' % nbunstable)
+ else:
+ ui.note('unstable: 0 changesets\n')
+ if nblatecomer:
+ ui.write('latecomer: %i changesets\n' % nblatecomer)
+ else:
+ ui.note('latecomer: 0 changesets\n')
+ if nbconflicting:
+ ui.write('conflicting: %i changesets\n' % nbconflicting)
+ else:
+ ui.note('conflicting: 0 changesets\n')
+ return ret
+
+
+#####################################################################
+### Core Other extension compat ###
+#####################################################################
+
+# This section make official history rewritter create obsolete marker
+
+
+### commit --amend
+# make commit --amend create obsolete marker
+#
+# The precursor is still strip from the repository.
+
+@eh.wrapfunction(cmdutil, 'amend')
+def wrapcmdutilamend(orig, ui, repo, commitfunc, old, *args, **kwargs):
+ oldnode = old.node()
+ new = orig(ui, repo, commitfunc, old, *args, **kwargs)
+ if new != oldnode:
+ lock = repo.lock()
+ try:
+ tr = repo.transaction('post-amend-obst')
+ try:
+ meta = {
+ 'date': '%i %i' % util.makedate(),
+ 'user': ui.username(),
+ }
+ repo.obsstore.create(tr, oldnode, [new], 0, meta)
+ tr.close()
+ clearobscaches(repo)
+ finally:
+ tr.release()
+ finally:
+ lock.release()
+ return new
+
+### rebase
+#
+# - ignore obsolete changeset
+# - create obsolete marker *instead of* striping
+
+def buildstate(orig, repo, dest, rebaseset, *ags, **kws):
+ """wrapper for rebase 's buildstate that exclude obsolete changeset"""
+
+ rebaseset = repo.revs('%ld - extinct()', rebaseset)
+ if not rebaseset:
+ repo.ui.warn(_('whole rebase set is extinct and ignored.\n'))
+ return {}
+ root = min(rebaseset)
+ if not repo._rebasekeep and not repo[root].mutable():
+ raise util.Abort(_("can't rebase immutable changeset %s") % repo[root],
+ hint=_('see hg help phases for details'))
+ return orig(repo, dest, rebaseset, *ags, **kws)
+
+def defineparents(orig, repo, rev, target, state, *args, **kwargs):
+ rebasestate = getattr(repo, '_rebasestate', None)
+ if rebasestate is not None:
+ repo._rebasestate = dict(state)
+ repo._rebasetarget = target
+ return orig(repo, rev, target, state, *args, **kwargs)
+
+def concludenode(orig, repo, rev, p1, *args, **kwargs):
+ """wrapper for rebase 's concludenode that set obsolete relation"""
+ newrev = orig(repo, rev, p1, *args, **kwargs)
+ rebasestate = getattr(repo, '_rebasestate', None)
+ if rebasestate is not None:
+ if newrev is not None:
+ nrev = repo[newrev].rev()
+ else:
+ nrev = p1
+ repo._rebasestate[rev] = nrev
+ return newrev
+
+def cmdrebase(orig, ui, repo, *args, **kwargs):
+
+ reallykeep = kwargs.get('keep', False)
+ kwargs = dict(kwargs)
+ kwargs['keep'] = True
+ repo._rebasekeep = reallykeep
+
+ # We want to mark rebased revision as obsolete and set their
+ # replacements if any. Doing it in concludenode() prevents
+ # aborting the rebase, and is not called with all relevant
+ # revisions in --collapse case. Instead, we try to track the
+ # rebase state structure by sampling/updating it in
+ # defineparents() and concludenode(). The obsolete markers are
+ # added from this state after a successful call.
+ repo._rebasestate = {}
+ repo._rebasetarget = None
+ try:
+ l = repo.lock()
+ try:
+ res = orig(ui, repo, *args, **kwargs)
+ if not reallykeep:
+ # Filter nullmerge or unrebased entries
+ repo._rebasestate = dict(p for p in repo._rebasestate.iteritems()
+ if p[1] >= 0)
+ if not res and not kwargs.get('abort') and repo._rebasestate:
+ # Rebased revisions are assumed to be descendants of
+ # targetrev. If a source revision is mapped to targetrev
+ # or to another rebased revision, it must have been
+ # removed.
+ markers = []
+ if kwargs.get('collapse'):
+ # collapse assume revision disapear because they are all
+ # in the created revision
+ newrevs = set(repo._rebasestate.values())
+ newrevs.remove(repo._rebasetarget)
+ if newrevs:
+ # we create new revision.
+ # A single one by --collapse design
+ assert len(newrevs) == 1
+ new = tuple(repo[n] for n in newrevs)
+ else:
+ # every body died. no new changeset created
+ new = (repo[repo._rebasetarget],)
+ for rev, newrev in sorted(repo._rebasestate.items()):
+ markers.append((repo[rev], new))
+ else:
+ # no collapse assume revision disapear because they are
+ # contained in parent
+ for rev, newrev in sorted(repo._rebasestate.items()):
+ markers.append((repo[rev], (repo[newrev],)))
+ createmarkers(repo, markers)
+ return res
+ finally:
+ l.release()
+ finally:
+ delattr(repo, '_rebasestate')
+ delattr(repo, '_rebasetarget')
+
+@eh.extsetup
+def _rebasewrapping(ui):
+ # warning about more obsolete
+ try:
+ rebase = extensions.find('rebase')
+ if rebase:
+ entry = extensions.wrapcommand(rebase.cmdtable, 'rebase', warnobserrors)
+ extensions.wrapfunction(rebase, 'buildstate', buildstate)
+ extensions.wrapfunction(rebase, 'defineparents', defineparents)
+ extensions.wrapfunction(rebase, 'concludenode', concludenode)
+ extensions.wrapcommand(rebase.cmdtable, "rebase", cmdrebase)
+ except KeyError:
+ pass # rebase not found
+
+
+#####################################################################
+### Old Evolve extension content ###
+#####################################################################
+
+# XXX need clean up and proper sorting in other section
### util function
#############################
@@ -118,8 +1193,9 @@
if created:
updatebookmarks(newid)
# add evolution metadata
- collapsed = set([u.node() for u in updates] + [old.node()])
- repo.addcollapsedobsolete(collapsed, new.node())
+ markers = [(u, (new,)) for u in updates]
+ markers.append((old, (new,)))
+ createmarkers(repo, markers)
else:
# newid is an existing revision. It could make sense to
# replace revisions with existing ones but probably not by
@@ -130,6 +1206,9 @@
return newid, created
+class MergeFailure(util.Abort):
+ pass
+
def relocate(repo, orig, dest):
"""rewrite <rev> on dest"""
try:
@@ -151,19 +1230,19 @@
try:
nodenew = rebase.concludenode(repo, orig.node(), dest.node(),
node.nullid)
- except util.Abort:
- repo.ui.write_err(_('/!\\ stabilize failed /!\\\n'))
- repo.ui.write_err(_('/!\\ Their is no "hg stabilize --continue" /!\\\n'))
- repo.ui.write_err(_('/!\\ use "hg up -C . ; hg stabilize --dry-run" /!\\\n'))
+ except util.Abort, exc:
+ class LocalMergeFailure(MergeFailure, exc.__class__):
+ pass
+ exc.__class__ = LocalMergeFailure
raise
oldbookmarks = repo.nodebookmarks(nodesrc)
if nodenew is not None:
phases.retractboundary(repo, destphase, [nodenew])
- repo.addobsolete(nodenew, nodesrc)
+ createmarkers(repo, [(repo[nodesrc], (repo[nodenew],))])
for book in oldbookmarks:
repo._bookmarks[book] = nodenew
else:
- repo.addobsolete(node.nullid, nodesrc)
+ createmarkers(repo, [(repo[nodesrc], ())])
# Behave like rebase, move bookmarks to dest
for book in oldbookmarks:
repo._bookmarks[book] = dest.node()
@@ -171,12 +1250,13 @@
repo._bookmarks[book] = dest.node()
if oldbookmarks or destbookmarks:
bookmarks.write(repo)
+ return nodenew
except util.Abort:
# Invalidate the previous setparents
repo.dirstate.invalidate()
raise
-def stabilizableunstable(repo, pctx):
+def _stabilizableunstable(repo, pctx):
"""Return a changectx for an unstable changeset which can be
stabilized on top of pctx or one of its descendants. None if none
can be found.
@@ -189,7 +1269,7 @@
# Look for an unstable which can be stabilized as a child of
# node. The unstable must be a child of one of node predecessors.
for ctx in selfanddescendants(repo, pctx):
- unstables = list(repo.set('unstable() and children(obsancestors(%d))',
+ unstables = list(repo.set('unstable() and children(allprecursors(%d))',
ctx.rev()))
if unstables:
return unstables[0]
@@ -219,51 +1299,101 @@
cmdtable = {}
command = cmdutil.command(cmdtable)
-@command('^stabilize|evolve|solve',
+@command('^evolve|stabilize|evolve|solve',
[('n', 'dry-run', False, 'do not perform actions, print what to be done'),
- ('A', 'any', False, 'stabilize any unstable changeset'),],
+ ('A', 'any', False, 'evolve any troubled changeset'),
+ ('c', 'continue', False, 'continue an interrupted evolution'), ],
_('[OPTIONS]...'))
-def stabilize(ui, repo, **opts):
- """rebase an unstable changeset to make it stable again
+def evolve(ui, repo, **opts):
+ """Solve trouble in your repository
+
+ - rebase unstable changeset to make it stable again,
+ - create proper diff from latecomer changeset,
+ - merge conflicting changeset.
- By default, take the first unstable changeset which could be
- rebased as child of the working directory parent revision or one
- of its descendants and rebase it.
+ By default, take the first troubles changeset that looks relevant.
+
+ (The logic is still a bit fuzzy)
- With --any, stabilize any unstable changeset.
+ - For unstable, that mean the first which could be rebased as child of the
+ working directory parent revision or one of its descendants and rebase
+ it.
- The working directory is updated to the rebased revision.
+ - For conflicting this mean "." if applicable.
+
+ With --any, evolve pick any troubled changeset to solve
+
+ The working directory is updated to the newly created revision.
"""
- obsolete = extensions.find('obsolete')
+ contopt = opts['continue']
+ anyopt = opts['any']
+
+ if contopt:
+ if anyopt:
+ raise util.Abort('can not specify both "--any" and "--continue"')
+ graftcmd = commands.table['graft'][0]
+ return graftcmd(ui, repo, old_obsolete=True, **{'continue': True})
+
+ troubled = list(repo.revs('troubled()'))
+ tr = _picknexttroubled(ui, repo, anyopt)
+ if tr is None:
+ if troubled:
+ ui.write_err(_('nothing to evolve here\n'))
+ ui.status(_('(%i troubled changesets, do you want --any ?)\n')
+ % len(troubled))
+ return 2
+ else:
+ ui.write_err(_('no troubled changeset\n'))
+ return 1
+ cmdutil.bailifchanged(repo)
+ troubles = tr.troubles()
+ if 'unstable' in troubles:
+ return _solveunstable(ui, repo, tr, opts['dry_run'])
+ elif 'latecomer' in troubles:
+ return _solvelatecomer(ui, repo, tr, opts['dry_run'])
+ elif 'conflicting' in troubles:
+ return _solveconflicting(ui, repo, tr, opts['dry_run'])
+ else:
+ assert False # WHAT? unknown troubles
- node = None
- if not opts['any']:
- node = stabilizableunstable(repo, repo['.'])
- if node is None:
- unstables = list(repo.set('unstable()'))
- if unstables and not opts['any']:
- ui.write_err(_('nothing to stabilize here\n'))
- ui.status(_('(%i unstable changesets, do you want --any ?)\n')
- % len(unstables))
- return 2
- elif not unstables:
- ui.write_err(_('no unstable changeset\n'))
- return 1
- node = unstables[0]
+def _picknexttroubled(ui, repo, any=False):
+ """Pick a the next trouble changeset to solve"""
+ tr = _stabilizableunstable(repo, repo['.'])
+ if tr is None:
+ wdp = repo['.']
+ if 'conflicting' in wdp.troubles():
+ tr = wdp
+ if tr is None and any:
+ troubled = list(repo.set('unstable()'))
+ if not troubled:
+ troubled = list(repo.set('latecomer()'))
+ if not troubled:
+ troubled = list(repo.set('conflicting()'))
+ if troubled:
+ tr = troubled[0]
- obs = node.parents()[0]
+ return tr
+
+
+def _solveunstable(ui, repo, orig, dryrun=False):
+ """Stabilize a unstable changeset"""
+ obs = orig.parents()[0]
if not obs.obsolete():
- obs = node.parents()[1]
+ obs = orig.parents()[1]
assert obs.obsolete()
- newer = obsolete.newerversion(repo, obs.node())
+ newer = newerversion(repo, obs.node())
+ # search of a parent which is not killed
+ while newer == [()]:
+ ui.debug("stabilize target %s is plain dead,"
+ " trying to stabilize on it's parent")
+ obs = obs.parents()[0]
+ newer = newerversion(repo, obs.node())
if len(newer) > 1:
ui.write_err(_("conflict rewriting. can't choose destination\n"))
return 2
targets = newer[0]
- if not targets:
- ui.write_err(_("does not handle kill parent yet\n"))
- return 2
+ assert targets
if len(targets) > 1:
ui.write_err(_("does not handle splitted parent yet\n"))
return 2
@@ -272,21 +1402,221 @@
target = repo[target]
repo.ui.status(_('move:'))
if not ui.quiet:
- displayer.show(node)
+ displayer.show(orig)
repo.ui.status(_('atop:'))
if not ui.quiet:
displayer.show(target)
- todo= 'hg rebase -Dr %s -d %s\n' % (node, target)
- if opts['dry_run']:
+ todo = 'hg rebase -Dr %s -d %s\n' % (orig, target)
+ if dryrun:
repo.ui.write(todo)
else:
repo.ui.note(todo)
lock = repo.lock()
try:
- relocate(repo, node, target)
+ relocate(repo, orig, target)
+ except MergeFailure:
+ repo.opener.write('graftstate', orig.hex() + '\n')
+ repo.ui.write_err(_('evolve failed!\n'))
+ repo.ui.write_err(_('fix conflict and run "hg evolve --continue"\n'))
+ raise
finally:
lock.release()
+def _solvelatecomer(ui, repo, latecomer, dryrun=False):
+ """Stabilize a latecomer changeset"""
+ # For now we deny latecomer merge
+ if len(latecomer.parents()) > 1:
+ raise util.Abort('late comer stabilization is confused by latecomer'
+ ' %s being a merge' % latecomer)
+ prec = repo.set('last(allprecursors(%d) and public())', latecomer).next()
+ # For now we deny target merge
+ if len(prec.parents()) > 1:
+ raise util.Abort('late comer evolution is confused by precursors'
+ ' %s being a merge' % prec)
+
+ displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate})
+ repo.ui.status(_('recreate:'))
+ if not ui.quiet:
+ displayer.show(latecomer)
+ repo.ui.status(_('atop:'))
+ if not ui.quiet:
+ displayer.show(prec)
+ if dryrun:
+ todo = 'hg rebase --rev %s --detach %s;\n' % (latecomer, prec.p1())
+ repo.ui.write(todo)
+ repo.ui.write('hg update %s;\n' % prec)
+ repo.ui.write('hg revert --all --rev %s;\n' % latecomer)
+ repo.ui.write('hg commit --msg "latecomer update to %s"')
+ return 0
+ wlock = repo.wlock()
+ try:
+ newid = tmpctx = None
+ tmpctx = latecomer
+ lock = repo.lock()
+ try:
+ bmupdate = _bookmarksupdater(repo, latecomer.node())
+ # Basic check for common parent. Far too complicated and fragile
+ tr = repo.transaction('latecomer-stabilize')
+ try:
+ if not list(repo.set('parents(%d) and parents(%d)', latecomer, prec)):
+ # Need to rebase the changeset at the right place
+ repo.ui.status(_('rebasing to destination parent: %s\n') % prec.p1())
+ try:
+ tmpid = relocate(repo, latecomer, prec.p1())
+ if tmpid is not None:
+ tmpctx = repo[tmpid]
+ createmarkers(repo, [(latecomer, (tmpctx,))])
+ except MergeFailure:
+ repo.opener.write('graftstate', latecomer.hex() + '\n')
+ repo.ui.write_err(_('evolution failed!\n'))
+ repo.ui.write_err(_('fix conflict and run "hg evolve --continue"\n'))
+ raise
+ # Create the new commit context
+ repo.ui.status(_('computing new diff\n'))
+ files = set()
+ copied = copies.pathcopies(prec, latecomer)
+ precmanifest = prec.manifest()
+ for key, val in latecomer.manifest().iteritems():
+ if precmanifest.pop(key, None) != val:
+ files.add(key)
+ files.update(precmanifest) # add missing files
+ # commit it
+ if files: # something to commit!
+ def filectxfn(repo, ctx, path):
+ if path in latecomer:
+ fctx = latecomer[path]
+ flags = fctx.flags()
+ mctx = context.memfilectx(fctx.path(), fctx.data(),
+ islink='l' in flags,
+ isexec='x' in flags,
+ copied=copied.get(path))
+ return mctx
+ raise IOError()
+ text = 'latecomer update to %s:\n\n' % prec
+ text += latecomer.description()
+
+ new = context.memctx(repo,
+ parents=[prec.node(), node.nullid],
+ text=text,
+ files=files,
+ filectxfn=filectxfn,
+ user=latecomer.user(),
+ date=latecomer.date(),
+ extra=latecomer.extra())
+
+ newid = repo.commitctx(new)
+ if newid is None:
+ createmarkers(repo, [(tmpctx, ())])
+ newid = prec.node()
+ else:
+ phases.retractboundary(repo, latecomer.phase(), [newid])
+ createmarkers(repo, [(tmpctx, (repo[newid],))],
+ flag=latediff)
+ bmupdate(newid)
+ tr.close()
+ repo.ui.status(_('commited as %s\n') % node.short(newid))
+ finally:
+ tr.release()
+ finally:
+ lock.release()
+ # reroute the working copy parent to the new changeset
+ repo.dirstate.setparents(newid, node.nullid)
+ finally:
+ wlock.release()
+
+def _solveconflicting(ui, repo, conflicting, dryrun=False):
+ base, others = conflictingdata(conflicting)
+ if len(others) > 1:
+ raise util.Abort("We do not handle split yet")
+ other = others[0]
+ if conflicting.phase() <= phases.public:
+ raise util.Abort("We can't resolve this conflict from the public side")
+ if len(other.parents()) > 1:
+ raise util.Abort("conflicting changeset can't be a merge (yet)")
+ if other.p1() not in conflicting.parents():
+ raise util.Abort("parent are not common (not handled yet)")
+
+ displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate})
+ ui.status(_('merge:'))
+ if not ui.quiet:
+ displayer.show(conflicting)
+ ui.status(_('with: '))
+ if not ui.quiet:
+ displayer.show(other)
+ ui.status(_('base: '))
+ if not ui.quiet:
+ displayer.show(base)
+ if dryrun:
+ ui.write('hg update -c %s &&\n' % conflicting)
+ ui.write('hg merge %s && \n' % other)
+ ui.write('hg commit -m "auto merge resolving conflict between %s and %s"&&\n'
+ % (conflicting, other))
+ ui.write('hg up -C %s &&\n' % base)
+ ui.write('hg revert --all --rev tip &&\n')
+ ui.write('hg commit -m "`hg log -r %s --template={desc}`";\n' % conflicting)
+ return
+ #oldphase = max(conflicting.phase(), other.phase())
+ wlock = repo.wlock()
+ try:
+ lock = repo.lock()
+ try:
+ if conflicting not in repo[None].parents():
+ repo.ui.status(_('updating to "local" conflict\n'))
+ hg.update(repo, conflicting.rev())
+ repo.ui.note(_('merging conflicting changeset\n'))
+ stats = merge.update(repo,
+ other.node(),
+ branchmerge=True,
+ force=False,
+ partial=None,
+ ancestor=base.node(),
+ mergeancestor=True)
+ hg._showstats(repo, stats)
+ if stats[3]:
+ repo.ui.status(_("use 'hg resolve' to retry unresolved file merges "
+ "or 'hg update -C .' to abandon\n"))
+ #repo.dirstate.write()
+ if stats[3] > 0:
+ raise util.Abort('GASP! Merge Conflict! You are on you own chap!',
+ hint='/!\\ hg evolve --continue will NOT work /!\\')
+ tr = repo.transaction('stabilize-conflicting')
+ try:
+ repo.dirstate.setparents(conflicting.node(), node.nullid)
+ oldlen = len(repo)
+ amend(ui, repo)
+ if oldlen == len(repo):
+ new = conflicting
+ # no changes
+ else:
+ new = repo['.']
+ createmarkers(repo, [(other, (new,))])
+ phases.retractboundary(repo, other.phase(), [new.node()])
+ tr.close()
+ finally:
+ tr.release()
+ finally:
+ lock.release()
+ finally:
+ wlock.release()
+
+
+def conflictingdata(ctx):
+ """return base, other part of a conflict
+
+ This only return the first one.
+
+ XXX this woobly function won't survive XXX
+ """
+ for base in ctx._repo.set('reverse(precursors(%d))', ctx):
+ newer = newerversion(ctx._repo, base.node())
+ # drop filter and solution including the original ctx
+ newer = [n for n in newer if n and ctx.node() not in n]
+ if newer:
+ return base, tuple(ctx._repo[o] for o in newer[0])
+ raise KeyError('Base seem unknown. This case is not handled yet.')
+
+
+
shorttemplate = '[{rev}] {desc|firstline}\n'
@command('^gdown',
@@ -353,25 +1683,29 @@
"""
wlock = repo.wlock()
try:
- new = set(noderange(repo, opts['new']))
- targetnodes = set(noderange(repo, revs))
- if not new:
- new = [node.nullid]
- for n in targetnodes:
- if not repo[n].mutable():
- ui.warn(_("cannot kill immutable changeset %s\n") % repo[n])
+ lock = repo.lock()
+ try:
+ new = set(noderange(repo, opts['new']))
+ targetnodes = set(noderange(repo, revs))
+ if new:
+ sucs = tuple(repo[n] for n in new)
else:
- for ne in new:
- repo.addobsolete(ne, n)
- # update to an unkilled parent
- wdp = repo['.']
- newnode = wdp
- while newnode.obsolete():
- newnode = newnode.parents()[0]
- if newnode.node() != wdp.node():
- commands.update(ui, repo, newnode.rev())
- ui.status(_('working directory now at %s\n') % newnode)
+ sucs = ()
+ markers = []
+ for n in targetnodes:
+ markers.append((repo[n], sucs))
+ createmarkers(repo, markers)
+ # update to an unkilled parent
+ wdp = repo['.']
+ newnode = wdp
+ while newnode.obsolete():
+ newnode = newnode.parents()[0]
+ if newnode.node() != wdp.node():
+ commands.update(ui, repo, newnode.rev())
+ ui.status(_('working directory now at %s\n') % newnode)
+ finally:
+ lock.release()
finally:
wlock.release()
@@ -465,7 +1799,7 @@
# the intermediate revision if any. No need to update
# phases or parents.
if tempid is not None:
- repo.addobsolete(node.nullid, tempid)
+ createmarkers(repo, [(repo[tempid], ())])
# XXX: need another message in collapse case.
tr.close()
raise error.Abort(_('no updates found'))
@@ -601,7 +1935,7 @@
if newid is None:
raise util.Abort(_('nothing to uncommit'))
# Move local changes on filtered changeset
- repo.addobsolete(newid, old.node())
+ createmarkers(repo, [(old, (repo[newid],))])
phases.retractboundary(repo, oldphase, [newid])
repo.dirstate.setparents(newid, node.nullid)
_uncommitdirstate(repo, old, match)
@@ -614,8 +1948,12 @@
finally:
lock.release()
+@eh.wrapcommand('commit')
def commitwrapper(orig, ui, repo, *arg, **kwargs):
- lock = repo.lock()
+ if kwargs.get('amend', False):
+ lock = None
+ else:
+ lock = repo.lock()
try:
obsoleted = kwargs.get('obsolete', [])
if obsoleted:
@@ -624,19 +1962,122 @@
if not result: # commit successed
new = repo['-1']
oldbookmarks = []
+ markers = []
for old in obsoleted:
oldbookmarks.extend(repo.nodebookmarks(old.node()))
- repo.addobsolete(new.node(), old.node())
+ markers.append((old, (new,)))
+ if markers:
+ createmarkers(repo, markers)
for book in oldbookmarks:
repo._bookmarks[book] = new.node()
if oldbookmarks:
bookmarks.write(repo)
return result
finally:
- lock.release()
+ if lock is not None:
+ lock.release()
+
+@command('^touch',
+ [('r', 'rev', [], 'revision to update'),],
+ # allow to choose the seed ?
+ _('[-r] revs'))
+def touch(ui, repo, *revs, **opts):
+ """Create successors with exact same property but hash
+
+ This is used to "resurect" changeset"""
+ revs = list(revs)
+ revs.extend(opts['rev'])
+ if not revs:
+ revs = ['.']
+ revs = scmutil.revrange(repo, revs)
+ if not revs:
+ ui.write_err('no revision to touch\n')
+ return 1
+ if repo.revs('public() and %ld', revs):
+ raise util.Abort("can't touch public revision")
+ wlock = repo.wlock()
+ try:
+ lock = repo.lock()
+ try:
+ tr = repo.transaction('touch')
+ try:
+ for r in revs:
+ ctx = repo[r]
+ extra = ctx.extra().copy()
+ extra['__touch-noise__'] = random.randint(0, 0xffffffff)
+ new, _ = rewrite(repo, ctx, [], ctx,
+ [ctx.p1().node(), ctx.p2().node()],
+ commitopts={'extra': extra})
+ createmarkers(repo, [(ctx, (repo[new],))])
+ phases.retractboundary(repo, ctx.phase(), [new])
+ if ctx in repo[None].parents():
+ repo.dirstate.setparents(new, node.nullid)
+ tr.close()
+ finally:
+ tr.release()
+ finally:
+ lock.release()
+ finally:
+ wlock.release()
+@command('^fold',
+ [('r', 'rev', [], 'revision to fold'),],
+ # allow to choose the seed ?
+ _('[-r] revs'))
+def fold(ui, repo, *revs, **opts):
+ """Fold multiple revision into a single one"""
+ revs = list(revs)
+ revs.extend(opts['rev'])
+ if not revs:
+ revs = ['.']
+ revs = scmutil.revrange(repo, revs)
+ if not revs:
+ ui.write_err('no revision to fold\n')
+ return 1
+ roots = repo.revs('roots(%ld)', revs)
+ if len(roots) > 1:
+ raise util.Abort("set have multiple roots")
+ root = repo[roots[0]]
+ if root.phase() <= phases.public:
+ raise util.Abort("can't touch public revision")
+ heads = repo.revs('heads(%ld)', revs)
+ if len(heads) > 1:
+ raise util.Abort("set have multiple heads")
+ head = repo[heads[0]]
+ wlock = repo.wlock()
+ try:
+ lock = repo.lock()
+ try:
+ tr = repo.transaction('touch')
+ try:
+ allctx = [repo[r] for r in revs]
+ targetphase = max(c.phase() for c in allctx)
+ msg = '\n\n***\n\n'.join(c.description() for c in allctx)
+ newid, _ = rewrite(repo, root, allctx, head,
+ [root.p1().node(), root.p2().node()],
+ commitopts={'message': msg})
+ phases.retractboundary(repo, targetphase, [newid])
+ createmarkers(repo, [(ctx, (repo[newid],))
+ for ctx in allctx])
+ tr.close()
+ finally:
+ tr.release()
+ finally:
+ lock.release()
+ ui.status('%i changesets folded\n' % len(revs))
+ if repo.revs('. and %ld', revs):
+ repo.dirstate.setparents(newid, node.nullid)
+ finally:
+ wlock.release()
+
+
+@eh.wrapcommand('graft')
def graftwrapper(orig, ui, repo, *revs, **kwargs):
+ kwargs = dict(kwargs)
+ revs = list(revs) + kwargs.get('rev', [])
+ kwargs['rev'] = []
obsoleted = kwargs.setdefault('obsolete', [])
+
lock = repo.lock()
try:
if kwargs.get('old_obsolete'):
@@ -655,25 +2096,21 @@
finally:
lock.release()
-def extsetup(ui):
- try:
- obsolete = extensions.find('obsolete')
- except KeyError:
- raise error.Abort(_('evolution extension requires obsolete extension.'))
+@eh.extsetup
+def oldevolveextsetup(ui):
try:
rebase = extensions.find('rebase')
except KeyError:
- rebase = None
raise error.Abort(_('evolution extension requires rebase extension.'))
for cmd in ['amend', 'kill', 'uncommit']:
entry = extensions.wrapcommand(cmdtable, cmd,
- obsolete.warnobserrors)
+ warnobserrors)
- entry = extensions.wrapcommand(commands.table, 'commit', commitwrapper)
+ entry = cmdutil.findcmd('commit', commands.table)[1]
entry[1].append(('o', 'obsolete', [],
_("make commit obsolete this revision")))
- entry = extensions.wrapcommand(commands.table, 'graft', graftwrapper)
+ entry = cmdutil.findcmd('graft', commands.table)[1]
entry[1].append(('o', 'obsolete', [],
_("make graft obsoletes this revision")))
entry[1].append(('O', 'old-obsolete', False,
--- a/hgext/obsolete.py Tue Aug 21 12:43:21 2012 +0200
+++ b/hgext/obsolete.py Fri Aug 24 11:53:55 2012 +0200
@@ -5,777 +5,60 @@
#
# 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 Obsolete concept to mercurial
-
-General concept
-===============
-
-This extension introduces the *obsolete* concept. It adds a new *obsolete*
-relation between two changesets. A relation ``<changeset B> obsolete <changeset
-A>`` is set to denote that ``<changeset B>`` is new version of ``<changeset
-A>``.
-
-The *obsolete* relation act as a **perpendicular history** to the standard
-changeset history. Standard changeset history versions files. The *obsolete*
-relation versions changesets.
-
-:obsolete: a changeset that has been replaced by another one.
-:unstable: a changeset that is not obsolete but has an obsolete ancestor.
-:suspended: an obsolete changeset with unstable descendant.
-:extinct: an obsolete changeset without unstable descendant.
- (subject to garbage collection)
-
-Another name for unstable could be out of sync.
-
-
-Usage and Feature
-=================
-
-Display and Exchange
---------------------
-
-obsolete changesets are hidden. (except if they have non obsolete changeset)
+"""Deprecated extension that formely introduces "Changeset Obsolescence".
-obsolete changesets are not exchanged. This will probably change later but it
-was the simpler solution for now.
-
-New commands
-------------
-
-Note that rebased changesets are now marked obsolete instead of being stripped.
-
-Context object
---------------
-
-Context gains a ``obsolete`` method that will return True if a changeset is
-obsolete False otherwise.
-
-revset
-------
+This concept is now partially in Mercurial core (starting with mercurial 2.3). The remaining logic have been grouped with the evolve extension.
-Add an ``obsolete()`` entry.
-
-repo extension
---------------
-
-To Do
-~~~~~
-
-- refuse to obsolete published changesets
-
-- handle split
-
-- handle conflict
-
-- handle unstable // out of sync
-
+Some code cemains in this extensions to detect and convert prehistoric format of obsolete marker than early user may have create. Keep it enabled if you were such user.
"""
-import os
-try:
- from cStringIO import StringIO
-except ImportError:
- from StringIO import StringIO
-
-from mercurial.i18n import _
-
-import base64
-import json
-
-import struct
-from mercurial import util, base85
-
-_pack = struct.pack
-_unpack = struct.unpack
-
from mercurial import util
-from mercurial import context
-from mercurial import revset
-from mercurial import scmutil
-from mercurial import extensions
-from mercurial import pushkey
-from mercurial import discovery
-from mercurial import error
-from mercurial import commands
-from mercurial import changelog
-from mercurial import phases
-from mercurial.node import hex, bin, short, nullid
-from mercurial.lock import release
-from mercurial import localrepo
-from mercurial import cmdutil
-from mercurial import templatekw
try:
- from mercurial.localrepo import storecache
- storecache('babar') # to trigger import
-except (TypeError, ImportError):
- def storecache(*args):
- return scmutil.filecache(*args, instore=True)
-
-
-### Patch changectx
-#############################
-
-def obsolete(ctx):
- """is the changeset obsolete by other"""
- if ctx.node()is None:
- return False
- return bool(ctx._repo.obsoletedby(ctx.node())) and ctx.phase()
-
-context.changectx.obsolete = obsolete
-
-def unstable(ctx):
- """is the changeset unstable (have obsolete ancestor)"""
- if ctx.node() is None:
- return False
- return ctx.rev() in ctx._repo._unstableset
-
-context.changectx.unstable = unstable
-
-def extinct(ctx):
- """is the changeset extinct by other"""
- if ctx.node() is None:
- return False
- return ctx.rev() in ctx._repo._extinctset
-
-context.changectx.extinct = extinct
-
-def latecomer(ctx):
- """is the changeset latecomer (Try to succeed to public change)"""
- if ctx.node() is None:
- return False
- return ctx.rev() in ctx._repo._latecomerset
-
-context.changectx.latecomer = latecomer
-
-def conflicting(ctx):
- """is the changeset conflicting (Try to succeed to public change)"""
- if ctx.node() is None:
- return False
- return ctx.rev() in ctx._repo._conflictingset
-
-context.changectx.conflicting = conflicting
-
-
-### revset
-#############################
-
-def revsethidden(repo, subset, x):
- """hidden changesets"""
- args = revset.getargs(x, 0, 0, 'hidden takes no argument')
- return [r for r in subset if r in repo.changelog.hiddenrevs]
-
-def revsetobsolete(repo, subset, x):
- """obsolete changesets"""
- args = revset.getargs(x, 0, 0, 'obsolete takes no argument')
- return [r for r in subset if r in repo._obsoleteset and repo._phasecache.phase(repo, r) > 0]
-
-# XXX Backward compatibility, to be removed once stabilized
-if '_phasecache' not in vars(localrepo.localrepository): # new api
- def revsetobsolete(repo, subset, x):
- """obsolete changesets"""
- args = revset.getargs(x, 0, 0, 'obsolete takes no argument')
- return [r for r in subset if r in repo._obsoleteset and repo._phaserev[r] > 0]
-
-def revsetunstable(repo, subset, x):
- """non obsolete changesets descendant of obsolete one"""
- args = revset.getargs(x, 0, 0, 'unstable takes no arguments')
- return [r for r in subset if r in repo._unstableset]
-
-def revsetsuspended(repo, subset, x):
- """obsolete changesets with non obsolete descendants"""
- args = revset.getargs(x, 0, 0, 'suspended takes no arguments')
- return [r for r in subset if r in repo._suspendedset]
-
-def revsetextinct(repo, subset, x):
- """obsolete changesets without obsolete descendants"""
- args = revset.getargs(x, 0, 0, 'extinct takes no arguments')
- return [r for r in subset if r in repo._extinctset]
-
-def revsetlatecomer(repo, subset, x):
- """latecomer, Try to succeed to public change"""
- args = revset.getargs(x, 0, 0, 'latecomer takes no arguments')
- return [r for r in subset if r in repo._latecomerset]
-
-def revsetconflicting(repo, subset, x):
- """conflicting, Try to succeed to public change"""
- args = revset.getargs(x, 0, 0, 'conflicting takes no arguments')
- return [r for r in subset if r in repo._conflictingset]
+ from mercurial import obsolete
+ if not obsolete._enabled:
+ obsolete._enabled = True
+except ImportError:
+ raise util.Abort('Obsolete extension requires Mercurial 2.3 (or later)')
-def _precursors(repo, s):
- """Precursor of a changeset"""
- cs = set()
- nm = repo.changelog.nodemap
- markerbysubj = repo.obsstore.successors
- for r in s:
- for p in markerbysubj.get(repo[r].node(), ()):
- pr = nm.get(p[0])
- if pr is not None:
- cs.add(pr)
- return cs
-
-def revsetprecursors(repo, subset, x):
- """precursors of a subset"""
- s = revset.getset(repo, range(len(repo)), x)
- cs = _precursors(repo, s)
- return [r for r in subset if r in cs]
-
-def _allprecursors(repo, s): # XXX we need a better naming
- """transitive precursors of a subset"""
- toproceed = [repo[r].node() for r in s]
- seen = set()
- allsubjects = repo.obsstore.successors
- while toproceed:
- nc = toproceed.pop()
- for mark in allsubjects.get(nc, ()):
- np = mark[0]
- if np not in seen:
- seen.add(np)
- toproceed.append(np)
- nm = repo.changelog.nodemap
- cs = set()
- for p in seen:
- pr = nm.get(p)
- if pr is not None:
- cs.add(pr)
- return cs
-
-def revsetallprecursors(repo, subset, x):
- """obsolete parents"""
- s = revset.getset(repo, range(len(repo)), x)
- cs = _allprecursors(repo, s)
- return [r for r in subset if r in cs]
+import sys
+import json
-def _successors(repo, s):
- """Successors of a changeset"""
- cs = set()
- nm = repo.changelog.nodemap
- markerbyobj = repo.obsstore.precursors
- for r in s:
- for p in markerbyobj.get(repo[r].node(), ()):
- for sub in p[1]:
- sr = nm.get(sub)
- if sr is not None:
- cs.add(sr)
- return cs
-
-def revsetsuccessors(repo, subset, x):
- """successors of a subset"""
- s = revset.getset(repo, range(len(repo)), x)
- cs = _successors(repo, s)
- return [r for r in subset if r in cs]
-
-def _allsuccessors(repo, s): # XXX we need a better naming
- """transitive successors of a subset"""
- toproceed = [repo[r].node() for r in s]
- seen = set()
- allobjects = repo.obsstore.precursors
- while toproceed:
- nc = toproceed.pop()
- for mark in allobjects.get(nc, ()):
- for sub in mark[1]:
- if sub == nullid:
- continue # should not be here!
- if sub not in seen:
- seen.add(sub)
- toproceed.append(sub)
- nm = repo.changelog.nodemap
- cs = set()
- for s in seen:
- sr = nm.get(s)
- if sr is not None:
- cs.add(sr)
- return cs
-
-def revsetallsuccessors(repo, subset, x):
- """obsolete parents"""
- s = revset.getset(repo, range(len(repo)), x)
- cs = _allsuccessors(repo, s)
- return [r for r in subset if r in cs]
-
-
-### template keywords
-#####################
-
-def obsoletekw(repo, ctx, templ, **args):
- """:obsolete: String. The obsolescence level of the node, could be
- ``stable``, ``unstable``, ``suspended`` or ``extinct``.
- """
- rev = ctx.rev()
- if rev in repo._extinctset:
- return 'extinct'
- if rev in repo._suspendedset:
- return 'suspended'
- if rev in repo._unstableset:
- return 'unstable'
- return 'stable'
-
-### Other Extension compat
-############################
+from mercurial import cmdutil
+from mercurial import error
+from mercurial.node import bin, nullid
-def buildstate(orig, repo, dest, rebaseset, *ags, **kws):
- """wrapper for rebase 's buildstate that exclude obsolete changeset"""
- rebaseset = repo.revs('%ld - extinct()', rebaseset)
- return orig(repo, dest, rebaseset, *ags, **kws)
-
-def defineparents(orig, repo, rev, target, state, *args, **kwargs):
- rebasestate = getattr(repo, '_rebasestate', None)
- if rebasestate is not None:
- repo._rebasestate = dict(state)
- repo._rebasetarget = target
- return orig(repo, rev, target, state, *args, **kwargs)
-
-def concludenode(orig, repo, rev, p1, *args, **kwargs):
- """wrapper for rebase 's concludenode that set obsolete relation"""
- newrev = orig(repo, rev, p1, *args, **kwargs)
- rebasestate = getattr(repo, '_rebasestate', None)
- if rebasestate is not None:
- if newrev is not None:
- nrev = repo[newrev].rev()
- else:
- nrev = p1
- repo._rebasestate[rev] = nrev
- return newrev
-
-def cmdrebase(orig, ui, repo, *args, **kwargs):
-
- reallykeep = kwargs.get('keep', False)
- kwargs = dict(kwargs)
- kwargs['keep'] = True
+#####################################################################
+### Older format management ###
+#####################################################################
- # We want to mark rebased revision as obsolete and set their
- # replacements if any. Doing it in concludenode() prevents
- # aborting the rebase, and is not called with all relevant
- # revisions in --collapse case. Instead, we try to track the
- # rebase state structure by sampling/updating it in
- # defineparents() and concludenode(). The obsolete markers are
- # added from this state after a successful call.
- repo._rebasestate = {}
- repo._rebasetarget = None
- try:
- res = orig(ui, repo, *args, **kwargs)
- if not reallykeep:
- # Filter nullmerge or unrebased entries
- repo._rebasestate = dict(p for p in repo._rebasestate.iteritems()
- if p[1] >= 0)
- if not res and not kwargs.get('abort') and repo._rebasestate:
- # Rebased revisions are assumed to be descendants of
- # targetrev. If a source revision is mapped to targetrev
- # or to another rebased revision, it must have been
- # removed.
- targetrev = repo[repo._rebasetarget].rev()
- newrevs = set([targetrev])
- replacements = {}
- for rev, newrev in sorted(repo._rebasestate.items()):
- oldnode = repo[rev].node()
- if newrev not in newrevs:
- newnode = repo[newrev].node()
- newrevs.add(newrev)
- else:
- newnode = nullid
- replacements[oldnode] = newnode
-
- if kwargs.get('collapse'):
- newnodes = set(n for n in replacements.values() if n != nullid)
- if newnodes:
- # Collapsing into more than one revision?
- assert len(newnodes) == 1, newnodes
- newnode = newnodes.pop()
- else:
- newnode = nullid
- repo.addcollapsedobsolete(replacements, newnode)
- else:
- for oldnode, newnode in replacements.iteritems():
- repo.addobsolete(newnode, oldnode)
- return res
- finally:
- delattr(repo, '_rebasestate')
- delattr(repo, '_rebasetarget')
+# Code related to detection and management of older legacy format never
+# handled by core
-def extsetup(ui):
-
- revset.symbols["hidden"] = revsethidden
- revset.symbols["obsolete"] = revsetobsolete
- revset.symbols["unstable"] = revsetunstable
- revset.symbols["suspended"] = revsetsuspended
- revset.symbols["extinct"] = revsetextinct
- revset.symbols["latecomer"] = revsetlatecomer
- revset.symbols["conflicting"] = revsetconflicting
- revset.symbols["obsparents"] = revsetprecursors # DEPR
- revset.symbols["precursors"] = revsetprecursors
- revset.symbols["obsancestors"] = revsetallprecursors # DEPR
- revset.symbols["allprecursors"] = revsetallprecursors # bad name
- revset.symbols["successors"] = revsetsuccessors
- revset.symbols["allsuccessors"] = revsetallsuccessors # bad name
-
- templatekw.keywords['obsolete'] = obsoletekw
-
- # warning about more obsolete
- for cmd in ['commit', 'push', 'pull', 'graft', 'phase', 'unbundle']:
- entry = extensions.wrapcommand(commands.table, cmd, warnobserrors)
- try:
- rebase = extensions.find('rebase')
- if rebase:
- entry = extensions.wrapcommand(rebase.cmdtable, 'rebase', warnobserrors)
- extensions.wrapfunction(rebase, 'buildstate', buildstate)
- extensions.wrapfunction(rebase, 'defineparents', defineparents)
- extensions.wrapfunction(rebase, 'concludenode', concludenode)
- extensions.wrapcommand(rebase.cmdtable, "rebase", cmdrebase)
- except KeyError:
- pass # rebase not found
-
-# Pushkey mechanism for mutable
-#########################################
-
-def listmarkers(repo):
- """List markers over pushkey"""
- if not repo.obsstore:
- return {}
- data = repo.obsstore._writemarkers()
- encdata = base85.b85encode(data)
- return {'dump0': encdata,
- 'dump': encdata} # legacy compat
-
-def pushmarker(repo, key, old, new):
- """Push markers over pushkey"""
- if not key.startswith('dump'):
- repo.ui.warn(_('unknown key: %r') % key)
- return 0
- if old:
- repo.ui.warn(_('unexpected old value') % key)
- return 0
- data = base85.b85decode(new)
- lock = repo.lock()
- try:
- try:
- repo.obsstore.mergemarkers(data)
- return 1
- except util.Abort:
- return 0
- finally:
- lock.release()
-
-pushkey.register('obsolete', pushmarker, listmarkers)
-
-### Discovery wrapping
-#############################
-
-class blist(list, object):
- """silly class to have non False but empty list"""
-
- def __nonzero__(self):
- return bool(len(self.orig))
-
-def wrapfindcommonoutgoing(orig, repo, *args, **kwargs):
- """wrap mercurial.discovery.findcommonoutgoing to remove extinct changeset
-
- Such excluded changeset are removed from excluded and will *not* appear
- are excluded secret changeset.
- """
- outgoing = orig(repo, *args, **kwargs)
- orig = outgoing.excluded
- outgoing.excluded = blist(n for n in orig if not repo[n].extinct())
- # when no revision is specified (push everything) a shortcut is taken when
- # nothign was exclude. taking this code path when extinct changeset have
- # been excluded leads to repository corruption.
- outgoing.excluded.orig = orig
- return outgoing
-
-def wrapcheckheads(orig, repo, remote, outgoing, *args, **kwargs):
- """wrap mercurial.discovery.checkheads
-
- * prevent unstability to be pushed
- * patch remote to ignore obsolete heads on remote
- """
- # do not push instability
- for h in outgoing.missingheads:
- # checking heads only is enought because any thing base on obsolete
- # changeset is either obsolete or unstable.
- ctx = repo[h]
- if ctx.unstable():
- raise util.Abort(_("push includes an unstable changeset: %s!")
- % ctx)
- if ctx.obsolete():
- raise util.Abort(_("push includes an obsolete changeset: %s!")
- % ctx)
- if ctx.latecomer():
- raise util.Abort(_("push includes an latecomer changeset: %s!")
- % ctx)
- if ctx.conflicting():
- raise util.Abort(_("push includes conflicting changeset: %s!")
- % ctx)
- ### patch remote branch map
- # do not read it this burn eyes
- try:
- if 'oldbranchmap' not in vars(remote):
- remote.oldbranchmap = remote.branchmap
- def branchmap():
- newbm = {}
- oldbm = None
- if (util.safehasattr(phases, 'visiblebranchmap')
- and not util.safehasattr(remote, 'ignorevisiblebranchmap')
- ):
- remote.ignorevisiblebranchmap = False
- remote.branchmap = remote.oldbranchmap
- oldbm = phases.visiblebranchmap(remote)
- remote.branchmap = remote.newbranchmap
- remote.ignorevisiblebranchmap = True
- if oldbm is None:
- oldbm = remote.oldbranchmap()
- for branch, nodes in oldbm.iteritems():
- nodes = list(nodes)
- new = set()
- while nodes:
- n = nodes.pop()
- if n in repo.obsstore.precursors:
- markers = repo.obsstore.precursors[n]
- for mark in markers:
- for newernode in mark[1]:
- if newernode is not None:
- nodes.append(newernode)
- else:
- new.add(n)
- if new:
- newbm[branch] = list(new)
- return newbm
- remote.ignorevisiblebranchmap = True
- remote.branchmap = branchmap
- remote.newbranchmap = branchmap
- return orig(repo, remote, outgoing, *args, **kwargs)
- finally:
- remote.__dict__.pop('branchmap', None) # restore class one
- remote.__dict__.pop('oldbranchmap', None)
- remote.__dict__.pop('newbranchmap', None)
- remote.__dict__.pop('ignorevisiblebranchmap', None)
-
-# eye are still burning
-def wrapvisiblebranchmap(orig, repo):
- ignore = getattr(repo, 'ignorevisiblebranchmap', None)
- if ignore is None:
- return orig(repo)
- elif ignore:
- return repo.branchmap()
- else:
- return None # break recursion
-
-def wrapclearcache(orig, repo, *args, **kwargs):
- try:
- return orig(repo, *args, **kwargs)
- finally:
- repo._clearobsoletecache()
-
-
-### New commands
-#############################
-
-cmdtable = {}
-command = cmdutil.command(cmdtable)
-
-@command('debugobsolete', [], _('SUBJECT OBJECT'))
-def cmddebugobsolete(ui, repo, subject, object):
- """add an obsolete relation between two nodes
-
- The subject is expected to be a newer version of the object.
+def reposetup(ui, repo):
+ """Detect that a repo still contains some old obsolete format
"""
- lock = repo.lock()
- try:
- sub = repo[subject]
- obj = repo[object]
- repo.addobsolete(sub.node(), obj.node())
- finally:
- lock.release()
- return 0
-
-@command('debugconvertobsolete', [], '')
-def cmddebugconvertobsolete(ui, repo):
- """import markers from an .hg/obsolete-relations file"""
- cnt = 0
- err = 0
- l = repo.lock()
- some = False
- try:
- repo._importoldobsolete = True
- store = repo.obsstore
- ### very first format
- try:
- f = repo.opener('obsolete-relations')
- try:
- some = True
- for line in f:
- subhex, objhex = line.split()
- suc = bin(subhex)
- prec = bin(objhex)
- sucs = (suc==nullid) and [] or [suc]
- meta = {
- 'date': '%i %i' % util.makedate(),
- 'user': ui.username(),
- }
- try:
- store.create(prec, sucs, 0, meta)
- cnt += 1
- except ValueError:
- repo.ui.write_err("invalid old marker line: %s"
- % (line))
- err += 1
- finally:
- f.close()
- util.unlink(repo.join('obsolete-relations'))
- except IOError:
- pass
- ### second (json) format
- data = repo.sopener.tryread('obsoletemarkers')
+ if not repo.local():
+ return
+ for arg in sys.argv:
+ if 'debugc' in arg:
+ break
+ else:
+ data = repo.opener.tryread('obsolete-relations')
+ if not data:
+ data = repo.sopener.tryread('obsoletemarkers')
if data:
- some = True
- for oldmark in json.loads(data):
- del oldmark['id'] # dropped for now
- del oldmark['reason'] # unused until then
- oldobject = str(oldmark.pop('object'))
- oldsubjects = [str(s) for s in oldmark.pop('subjects', [])]
- LOOKUP_ERRORS = (error.RepoLookupError, error.LookupError)
- if len(oldobject) != 40:
- try:
- oldobject = repo[oldobject].node()
- except LOOKUP_ERRORS:
- pass
- if any(len(s) != 40 for s in oldsubjects):
- try:
- oldsubjects = [repo[s].node() for s in oldsubjects]
- except LOOKUP_ERRORS:
- pass
-
- oldmark['date'] = '%i %i' % tuple(oldmark['date'])
- meta = dict((k.encode('utf-8'), v.encode('utf-8'))
- for k, v in oldmark.iteritems())
- try:
- succs = [bin(n) for n in oldsubjects]
- succs = [n for n in succs if n != nullid]
- store.create(bin(oldobject), succs,
- 0, meta)
- cnt += 1
- except ValueError:
- repo.ui.write_err("invalid marker %s -> %s\n"
- % (oldobject, oldsubjects))
- err += 1
- util.unlink(repo.sjoin('obsoletemarkers'))
- finally:
- del repo._importoldobsolete
- l.release()
- if not some:
- ui.warn('nothing to do\n')
- ui.status('%i obsolete marker converted\n' % cnt)
- if err:
- ui.write_err('%i conversion failed. check you graph!\n' % err)
-
-@command('debugsuccessors', [], '')
-def cmddebugsuccessors(ui, repo):
- """dump obsolete changesets and their successors
-
- Each line matches an existing marker, the first identifier is the
- obsolete changeset identifier, followed by it successors.
- """
- lock = repo.lock()
- try:
- allsuccessors = repo.obsstore.precursors
- for old in sorted(allsuccessors):
- successors = [sorted(m[1]) for m in allsuccessors[old]]
- for i, group in enumerate(sorted(successors)):
- ui.write('%s' % short(old))
- for new in group:
- ui.write(' %s' % short(new))
- ui.write('\n')
- finally:
- lock.release()
-
-### Altering existing command
-#############################
-
-def wrapmayobsoletewc(origfn, ui, repo, *args, **opts):
- res = origfn(ui, repo, *args, **opts)
- if repo['.'].obsolete():
- ui.warn(_('Working directory parent is obsolete\n'))
- return res
-
-def warnobserrors(orig, ui, repo, *args, **kwargs):
- """display warning is the command resulted in more instable changeset"""
- priorunstables = len(repo.revs('unstable()'))
- priorlatecomers = len(repo.revs('latecomer()'))
- priorconflictings = len(repo.revs('conflicting()'))
- #print orig, priorunstables
- #print len(repo.revs('secret() - obsolete()'))
- try:
- return orig(ui, repo, *args, **kwargs)
- finally:
- newunstables = len(repo.revs('unstable()')) - priorunstables
- newlatecomers = len(repo.revs('latecomer()')) - priorlatecomers
- newconflictings = len(repo.revs('conflicting()')) - priorconflictings
- #print orig, newunstables
- #print len(repo.revs('secret() - obsolete()'))
- if newunstables > 0:
- ui.warn(_('%i new unstables changesets\n') % newunstables)
- if newlatecomers > 0:
- ui.warn(_('%i new latecomers changesets\n') % newlatecomers)
- if newconflictings > 0:
- ui.warn(_('%i new conflictings changesets\n') % newconflictings)
-
-def noextinctsvisibleheads(orig, repo):
- repo._turn_extinct_secret()
- return orig(repo)
-
-def wrapcmdutilamend(orig, ui, repo, commitfunc, old, *args, **kwargs):
- oldnode = old.node()
- new = orig(ui, repo, commitfunc, old, *args, **kwargs)
- if new != oldnode:
- lock = repo.lock()
- try:
- meta = {
- 'subjects': [new],
- 'object': oldnode,
- 'date': util.makedate(),
- 'user': ui.username(),
- 'reason': 'commit --amend',
- }
- repo.obsstore.create(oldnode, [new], 0, meta)
- repo._clearobsoletecache()
- repo._turn_extinct_secret()
- finally:
- lock.release()
- return new
-
-def uisetup(ui):
- extensions.wrapcommand(commands.table, "update", wrapmayobsoletewc)
- extensions.wrapcommand(commands.table, "pull", wrapmayobsoletewc)
- if util.safehasattr(cmdutil, 'amend'):
- extensions.wrapfunction(cmdutil, 'amend', wrapcmdutilamend)
- extensions.wrapfunction(discovery, 'findcommonoutgoing', wrapfindcommonoutgoing)
- extensions.wrapfunction(discovery, 'checkheads', wrapcheckheads)
- extensions.wrapfunction(phases, 'visibleheads', noextinctsvisibleheads)
- extensions.wrapfunction(phases, 'advanceboundary', wrapclearcache)
- if util.safehasattr(phases, 'visiblebranchmap'):
- extensions.wrapfunction(phases, 'visiblebranchmap', wrapvisiblebranchmap)
-
-### serialisation
-#############################
-
-def _obsserialise(obssubrels, flike):
- """serialise an obsolete relation mapping in a plain text one
-
- this is for subject -> [objects] mapping
-
- format is::
-
- <subject-full-hex> <object-full-hex>\n"""
- for sub, objs in obssubrels.iteritems():
- for obj in objs:
- if sub is None:
- sub = nullid
- flike.write('%s %s\n' % (hex(sub), hex(obj)))
+ raise util.Abort('old format of obsolete marker detected!\n'
+ 'run `hg debugconvertobsolete` once.')
def _obsdeserialise(flike):
"""read a file like object serialised with _obsserialise
- this desierialize into a {subject -> objects} mapping"""
+ this desierialize into a {subject -> objects} mapping
+
+ this was the very first format ever."""
rels = {}
for line in flike:
subhex, objhex = line.split()
@@ -785,527 +68,92 @@
rels.setdefault( subnode, set()).add(bin(objhex))
return rels
-### diagnostique tools
-#############################
-
-def unstables(repo):
- """Return all unstable changeset"""
- return scmutil.revrange(repo, ['obsolete():: and (not obsolete())'])
-
-def newerversion(repo, obs):
- """Return the newer version of an obsolete changeset"""
- toproceed = set([(obs,)])
- # XXX known optimization available
- newer = set()
- objectrels = repo.obsstore.precursors
- while toproceed:
- current = toproceed.pop()
- assert len(current) <= 1, 'splitting not handled yet. %r' % current
- current = [n for n in current if n != nullid]
- if current:
- n, = current
- if n in objectrels:
- markers = objectrels[n]
- for mark in markers:
- toproceed.add(tuple(mark[1]))
- else:
- newer.add(tuple(current))
- else:
- newer.add(())
- return sorted(newer)
-
-### obsolete relation storage
-#############################
-def add2set(d, key, mark):
- """add <mark> to a `set` in <d>[<key>]"""
- d.setdefault(key, []).append(mark)
-
-def markerid(marker):
- KEYS = ['subjects', "object", "date", "user", "reason"]
- for key in KEYS:
- assert key in marker
- keys = sorted(marker.keys())
- a = util.sha1()
- for key in keys:
- if key == 'subjects':
- for sub in sorted(marker[key]):
- a.update(sub)
- elif key == 'id':
- pass
- else:
- a.update(str(marker[key]))
- a.update('\0')
- return a.digest()
-
-# mercurial backport
-
-def encodemeta(meta):
- """Return encoded metadata string to string mapping.
-
- Assume no ':' in key and no '\0' in both key and value."""
- for key, value in meta.iteritems():
- if ':' in key or '\0' in key:
- raise ValueError("':' and '\0' are forbidden in metadata key'")
- if '\0' in value:
- raise ValueError("':' are forbidden in metadata value'")
- return '\0'.join(['%s:%s' % (k, meta[k]) for k in sorted(meta)])
-
-def decodemeta(data):
- """Return string to string dictionary from encoded version."""
- d = {}
- for l in data.split('\0'):
- if l:
- key, value = l.split(':')
- d[key] = value
- return d
-
-# data used for parsing and writing
-_fmversion = 0
-_fmfixed = '>BIB20s'
-_fmnode = '20s'
-_fmfsize = struct.calcsize(_fmfixed)
-_fnodesize = struct.calcsize(_fmnode)
-
-def _readmarkers(data):
- """Read and enumerate markers from raw data"""
- off = 0
- diskversion = _unpack('>B', data[off:off + 1])[0]
- off += 1
- if diskversion != _fmversion:
- raise util.Abort(_('parsing obsolete marker: unknown version %r')
- % diskversion)
-
- # Loop on markers
- l = len(data)
- while off + _fmfsize <= l:
- # read fixed part
- cur = data[off:off + _fmfsize]
- off += _fmfsize
- nbsuc, mdsize, flags, pre = _unpack(_fmfixed, cur)
- # read replacement
- sucs = ()
- if nbsuc:
- s = (_fnodesize * nbsuc)
- cur = data[off:off + s]
- sucs = _unpack(_fmnode * nbsuc, cur)
- off += s
- # read metadata
- # (metadata will be decoded on demand)
- metadata = data[off:off + mdsize]
- if len(metadata) != mdsize:
- raise util.Abort(_('parsing obsolete marker: metadata is too '
- 'short, %d bytes expected, got %d')
- % (len(metadata), mdsize))
- off += mdsize
- yield (pre, sucs, flags, metadata)
-
-class obsstore(object):
- """Store obsolete markers
-
- Markers can be accessed with two mappings:
- - precursors: old -> set(new)
- - successors: new -> set(old)
- """
-
- def __init__(self):
- self._all = []
- # new markers to serialize
- self._new = []
- self.precursors = {}
- self.successors = {}
-
- def __iter__(self):
- return iter(self._all)
-
- def __nonzero__(self):
- return bool(self._all)
-
- def create(self, prec, succs=(), flag=0, metadata=None):
- """obsolete: add a new obsolete marker
-
- * ensuring it is hashable
- * check mandatory metadata
- * encode metadata
- """
- if metadata is None:
- metadata = {}
- if len(prec) != 20:
- raise ValueError(repr(prec))
- for succ in succs:
- if len(succ) != 20:
- raise ValueError((succs))
- marker = (str(prec), tuple(succs), int(flag), encodemeta(metadata))
- self.add(marker)
-
- def add(self, marker):
- """Add a new marker to the store
-
- This marker still needs to be written to disk"""
- self._new.append(marker)
- self._load(marker)
-
- def loadmarkers(self, data):
- """Load all markers in data, mark them as known."""
- for marker in _readmarkers(data):
- self._load(marker)
-
- def mergemarkers(self, data):
- other = set(_readmarkers(data))
- local = set(self._all)
- new = other - local
- for marker in new:
- self.add(marker)
-
- def flushmarkers(self, stream):
- """Write all markers to a stream
-
- After this operation, "new" markers are considered "known"."""
- self._writemarkers(stream)
- self._new[:] = []
-
- def _load(self, marker):
- self._all.append(marker)
- pre, sucs = marker[:2]
- self.precursors.setdefault(pre, set()).add(marker)
- for suc in sucs:
- self.successors.setdefault(suc, set()).add(marker)
-
- def _writemarkers(self, stream=None):
- # Kept separate from flushmarkers(), it will be reused for
- # markers exchange.
- if stream is None:
- final = []
- w = final.append
- else:
- w = stream.write
- w(_pack('>B', _fmversion))
- for marker in self._all:
- pre, sucs, flags, metadata = marker
- nbsuc = len(sucs)
- format = _fmfixed + (_fmnode * nbsuc)
- data = [nbsuc, len(metadata), flags, pre]
- data.extend(sucs)
- w(_pack(format, *data))
- w(metadata)
- if stream is None:
- return ''.join(final)
-
-
-### repo subclassing
-#############################
-
-def reposetup(ui, repo):
- if not repo.local():
- return
-
- if not util.safehasattr(repo.opener, 'tryread'):
- raise util.Abort('Obsolete extension requires Mercurial 2.2 (or later)')
- opull = repo.pull
- opush = repo.push
- olock = repo.lock
- o_rollback = repo._rollback
- o_updatebranchcache = repo.updatebranchcache
-
- # /!\ api change in Hg 2.2 (97efd26eb9576f39590812ea9) /!\
- if util.safehasattr(repo, '_journalfiles'): # Hg 2.2
- o_journalfiles = repo._journalfiles
- o_writejournal = repo._writejournal
- o_hook = repo.hook
-
-
- class obsoletingrepo(repo.__class__):
-
- # workaround
- def hook(self, name, throw=False, **args):
- if 'pushkey' in name:
- args.pop('new')
- args.pop('old')
- return o_hook(name, throw=False, **args)
-
- ### Public method
- def obsoletedby(self, node):
- """return the set of node that make <node> obsolete (obj)"""
- others = set()
- for marker in self.obsstore.precursors.get(node, []):
- others.update(marker[1])
- return others
-
- def obsolete(self, node):
- """return the set of node that <node> make obsolete (sub)"""
- return set(marker[0] for marker in self.obsstore.successors.get(node, []))
-
- @storecache('obsstore')
- def obsstore(self):
- if not getattr(self, '_importoldobsolete', False):
- data = repo.opener.tryread('obsolete-relations')
- if not data:
- data = repo.sopener.tryread('obsoletemarkers')
- if data:
- raise util.Abort('old format of obsolete marker detected!\n'
- 'run `hg debugconvertobsolete` once.')
- store = obsstore()
- data = self.sopener.tryread('obsstore')
+cmdtable = {}
+command = cmdutil.command(cmdtable)
+@command('debugconvertobsolete', [], '')
+def cmddebugconvertobsolete(ui, repo):
+ """import markers from an .hg/obsolete-relations file"""
+ cnt = 0
+ err = 0
+ l = repo.lock()
+ some = False
+ try:
+ unlink = []
+ tr = repo.transaction('convert-obsolete')
+ try:
+ repo._importoldobsolete = True
+ store = repo.obsstore
+ ### very first format
+ try:
+ f = repo.opener('obsolete-relations')
+ try:
+ some = True
+ for line in f:
+ subhex, objhex = line.split()
+ suc = bin(subhex)
+ prec = bin(objhex)
+ sucs = (suc==nullid) and [] or [suc]
+ meta = {
+ 'date': '%i %i' % util.makedate(),
+ 'user': ui.username(),
+ }
+ try:
+ store.create(tr, prec, sucs, 0, meta)
+ cnt += 1
+ except ValueError:
+ repo.ui.write_err("invalid old marker line: %s"
+ % (line))
+ err += 1
+ finally:
+ f.close()
+ unlink.append(repo.join('obsolete-relations'))
+ except IOError:
+ pass
+ ### second (json) format
+ data = repo.sopener.tryread('obsoletemarkers')
if data:
- store.loadmarkers(data)
- return store
-
- @util.propertycache
- def _obsoleteset(self):
- """the set of obsolete revision"""
- obs = set()
- nm = self.changelog.nodemap
- for obj in self.obsstore.precursors:
- try: # /!\api change in Hg 2.2 (e8d37b78acfb22ae2c1fb126c2)/!\
- rev = nm.get(obj)
- except TypeError: #XXX to remove while breaking Hg 2.1 support
- rev = nm.get(obj, None)
- if rev is not None:
- obs.add(rev)
- return obs
-
- @util.propertycache
- def _unstableset(self):
- """the set of non obsolete revision with obsolete parent"""
- return set(self.revs('(obsolete()::) - obsolete()'))
-
- @util.propertycache
- def _suspendedset(self):
- """the set of obsolete parent with non obsolete descendant"""
- return set(self.revs('obsolete() and obsolete()::unstable()'))
-
- @util.propertycache
- def _extinctset(self):
- """the set of obsolete parent without non obsolete descendant"""
- return set(self.revs('obsolete() - obsolete()::unstable()'))
-
- @util.propertycache
- def _latecomerset(self):
- """the set of rev trying to obsolete public revision"""
- query = 'allsuccessors(public()) - obsolete() - public()'
- return set(self.revs(query))
-
- @util.propertycache
- def _conflictingset(self):
- """the set of rev trying to obsolete public revision"""
- conflicting = set()
- obsstore = self.obsstore
- newermap = {}
- for ctx in self.set('(not public()) - obsolete()'):
- prec = obsstore.successors.get(ctx.node(), ())
- toprocess = set(prec)
- while toprocess:
- prec = toprocess.pop()[0]
- if prec not in newermap:
- newermap[prec] = newerversion(self, prec)
- newer = [n for n in newermap[prec] if n] # filter kill
- if len(newer) > 1:
- conflicting.add(ctx.rev())
- break
- toprocess.update(obsstore.successors.get(prec, ()))
- return conflicting
-
- def _clearobsoletecache(self):
- if '_obsoleteset' in vars(self):
- del self._obsoleteset
- self._clearunstablecache()
-
- def updatebranchcache(self):
- o_updatebranchcache()
- self._clearunstablecache()
-
- def _clearunstablecache(self):
- if '_unstableset' in vars(self):
- del self._unstableset
- if '_suspendedset' in vars(self):
- del self._suspendedset
- if '_extinctset' in vars(self):
- del self._extinctset
- if '_latecomerset' in vars(self):
- del self._latecomerset
- if '_conflictingset' in vars(self):
- del self._conflictingset
-
- def addobsolete(self, sub, obj):
- """Add a relation marking that node <sub> is a new version of <obj>"""
- assert sub != obj
- if not repo[obj].phase():
- if sub is None:
- self.ui.warn(
- _("trying to kill immutable changeset %(obj)s\n")
- % {'obj': short(obj)})
- if sub is not None:
- self.ui.warn(
- _("%(sub)s try to obsolete immutable changeset %(obj)s\n")
- % {'sub': short(sub), 'obj': short(obj)})
- lock = self.lock()
- try:
- meta = {
- 'date': util.makedate(),
- 'user': ui.username(),
- 'reason': 'unknown',
- }
- subs = (sub == nullid) and [] or [sub]
- mid = self.obsstore.create(obj, subs, 0, meta)
- self._clearobsoletecache()
- self._turn_extinct_secret()
- return mid
- finally:
- lock.release()
-
- def addcollapsedobsolete(self, oldnodes, newnode):
- """Mark oldnodes as collapsed into newnode."""
- # Assume oldnodes are all descendants of a single rev
- rootrevs = self.revs('roots(%ln)', oldnodes)
- assert len(rootrevs) == 1, rootrevs
- rootnode = self[rootrevs[0]].node()
- for n in oldnodes:
- self.addobsolete(newnode, n)
-
- def _turn_extinct_secret(self):
- """ensure all extinct changeset are secret"""
- self._clearobsoletecache()
- # this is mainly for safety purpose
- # both pull and push
- query = '(obsolete() - obsolete()::(unstable() - secret())) - secret()'
- expobs = [c.node() for c in repo.set(query)]
- phases.retractboundary(repo, 2, expobs)
-
- ### Disk IO
+ some = True
+ for oldmark in json.loads(data):
+ del oldmark['id'] # dropped for now
+ del oldmark['reason'] # unused until then
+ oldobject = str(oldmark.pop('object'))
+ oldsubjects = [str(s) for s in oldmark.pop('subjects', [])]
+ LOOKUP_ERRORS = (error.RepoLookupError, error.LookupError)
+ if len(oldobject) != 40:
+ try:
+ oldobject = repo[oldobject].node()
+ except LOOKUP_ERRORS:
+ pass
+ if any(len(s) != 40 for s in oldsubjects):
+ try:
+ oldsubjects = [repo[s].node() for s in oldsubjects]
+ except LOOKUP_ERRORS:
+ pass
- def lock(self, *args, **kwargs):
- l = olock(*args, **kwargs)
- if not getattr(l.releasefn, 'obspatched', False):
- oreleasefn = l.releasefn
- def releasefn(*args, **kwargs):
- if 'obsstore' in vars(self) and self.obsstore._new:
- f = self.sopener('obsstore', 'wb', atomictemp=True)
- try:
- self.obsstore.flushmarkers(f)
- f.close()
- except: # re-raises
- f.discard()
- raise
- oreleasefn(*args, **kwargs)
- releasefn.obspatched = True
- l.releasefn = releasefn
- return l
-
-
- ### pull // push support
-
- def pull(self, remote, *args, **kwargs):
- """wrapper around push that push obsolete relation"""
- l = repo.lock()
- try:
- result = opull(remote, *args, **kwargs)
- remoteobs = remote.listkeys('obsolete')
- if 'dump' in remoteobs:
- remoteobs['dump0'] = remoteobs.pop('dump')
- if 'dump0' in remoteobs:
- for key, values in remoteobs.iteritems():
- if key.startswith('dump'):
- data = base85.b85decode(remoteobs['dump0'])
- self.obsstore.mergemarkers(data)
- self._clearobsoletecache()
- self._turn_extinct_secret()
- return result
- finally:
- l.release()
-
- def push(self, remote, *args, **opts):
- """wrapper around pull that pull obsolete relation"""
- self._turn_extinct_secret()
- try:
- result = opush(remote, *args, **opts)
- except util.Abort, ex:
- hint = _("use 'hg stabilize' to get a stable history (or --force to proceed)")
- if (len(ex.args) >= 1
- and ex.args[0].startswith('push includes ')
- and ex.hint is None):
- ex.hint = hint
- raise
- if 'obsolete' in remote.listkeys('namespaces') and self.obsstore:
- data = self.obsstore._writemarkers()
- r = remote.pushkey('obsolete', 'dump0', '',
- base85.b85encode(data))
- if not r:
- self.ui.warn(_('failed to push obsolete markers!\n'))
- self._turn_extinct_secret()
-
- return result
-
-
- ### rollback support
-
- # /!\ api change in Hg 2.2 (97efd26eb9576f39590812ea9) /!\
- if util.safehasattr(repo, '_journalfiles'): # Hg 2.2
- def _journalfiles(self):
- return o_journalfiles() + (self.sjoin('journal.obsstore'),)
-
- def _writejournal(self, desc):
- """wrapped version of _writejournal that save obsolete data"""
- o_writejournal(desc)
- filename = 'obsstore'
- filepath = self.sjoin(filename)
- if os.path.exists(filepath):
- journalname = 'journal.' + filename
- journalpath = self.sjoin(journalname)
- util.copyfile(filepath, journalpath)
-
- else: # XXX removing this bloc will break Hg 2.1 support
- def _writejournal(self, desc):
- """wrapped version of _writejournal that save obsolete data"""
- entries = list(o_writejournal(desc))
- filename = 'obsstore'
- filepath = self.sjoin(filename)
- if os.path.exists(filepath):
- journalname = 'journal.' + filename
- journalpath = self.sjoin(journalname)
- util.copyfile(filepath, journalpath)
- entries.append(journalpath)
- return tuple(entries)
-
- def _rollback(self, dryrun, force):
- """wrapped version of _rollback that restore obsolete data"""
- ret = o_rollback(dryrun, force)
- if not (ret or dryrun): #rollback did not failed
- src = self.sjoin('undo.obsstore')
- dst = self.sjoin('obsstore')
- if os.path.exists(src):
- util.rename(src, dst)
- elif os.path.exists(dst):
- # If no state was saved because the file did not existed before.
- os.unlink(dst)
- # invalidate cache
- self.__dict__.pop('obsstore', None)
- return ret
-
- @storecache('00changelog.i')
- def changelog(self):
- # << copy pasted from mercurial source
- c = changelog.changelog(self.sopener)
- if 'HG_PENDING' in os.environ:
- p = os.environ['HG_PENDING']
- if p.startswith(self.root):
- c.readpending('00changelog.i.a')
- # >> end of the copy paste
- old = c.__dict__.pop('hiddenrevs', ())
- if old:
- ui.warn("old wasn't empty ? %r" % old)
- def _sethidden(c, value):
- assert not value
-
-
- class hchangelog(c.__class__):
- @util.propertycache
- def hiddenrevs(c):
- shown = ['not obsolete()', '.', 'bookmark()', 'tagged()',
- 'public()']
- basicquery = 'obsolete() - (::(%s))' % (' or '.join(shown))
- # !!! self is repo not changelog
- result = set(scmutil.revrange(self, [basicquery]))
- return result
- c.__class__ = hchangelog
- return c
-
- repo.__class__ = obsoletingrepo
+ oldmark['date'] = '%i %i' % tuple(oldmark['date'])
+ meta = dict((k.encode('utf-8'), v.encode('utf-8'))
+ for k, v in oldmark.iteritems())
+ try:
+ succs = [bin(n) for n in oldsubjects]
+ succs = [n for n in succs if n != nullid]
+ store.create(tr, bin(oldobject), succs,
+ 0, meta)
+ cnt += 1
+ except ValueError:
+ repo.ui.write_err("invalid marker %s -> %s\n"
+ % (oldobject, oldsubjects))
+ err += 1
+ unlink.append(repo.sjoin('obsoletemarkers'))
+ tr.close()
+ for path in unlink:
+ util.unlink(path)
+ finally:
+ tr.release()
+ finally:
+ del repo._importoldobsolete
+ l.release()
+ if not some:
+ ui.warn('nothing to do\n')
+ ui.status('%i obsolete marker converted\n' % cnt)
+ if err:
+ ui.write_err('%i conversion failed. check you graph!\n' % err)
--- a/hgext/qsync.py Tue Aug 21 12:43:21 2012 +0200
+++ b/hgext/qsync.py Fri Aug 24 11:53:55 2012 +0200
@@ -83,8 +83,8 @@
review_list.append(patch_name)
except IOError:
oldnode = oldfiles[patch_name]
- obsolete = extensions.find('obsolete')
- newnodes = obsolete.newerversion(repo, oldnode)
+ evolve = extensions.find('evolve')
+ newnodes = evolve.newerversion(repo, oldnode)
if newnodes:
newnodes = [n for n in newnodes if n and n[0] in repo] # remove killing
if not newnodes:
@@ -166,14 +166,14 @@
currentdrafts = set(d[0] for d in newdata)
usednew = set()
usedold = set()
- obsolete = extensions.find('obsolete')
+ evolve = extensions.find('evolve')
for oldhex, oldname in olddata:
if oldhex in usedold:
continue # no duplicate
usedold.add(oldhex)
oldname = str(oldname)
oldnode = bin(oldhex)
- newnodes = obsolete.newerversion(repo, oldnode)
+ newnodes = evolve.newerversion(repo, oldnode)
if newnodes:
newnodes = [n for n in newnodes if n and n[0] in repo] # remove killing
if len(newnodes) > 1:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/setup.py Fri Aug 24 11:53:55 2012 +0200
@@ -0,0 +1,25 @@
+# Copied from histedit setup.py
+# Credit to Augie Fackler <durin42@gmail.com>
+
+from distutils.core import setup
+
+requires = []
+try:
+ import mercurial
+except ImportError:
+ requires.append('mercurial')
+
+setup(
+ name='hg-evolve',
+ version='0.1.0',
+ author='Pierre-Yves David',
+ maintainer='Pierre-Yves David',
+ maintainer_email='pierre-yves.david@logilab.fr',
+ url='https://bitbucket.org/marmoute/mutable-history',
+ description='Flexible evolution of Mercurial history.',
+ long_description=open('README').read(),
+ keywords='hg mercurial',
+ license='GPLv2+',
+ py_modules=['hgext.evolve'],
+ install_requires=requires,
+)
--- a/tests/test-amend.t Tue Aug 21 12:43:21 2012 +0200
+++ b/tests/test-amend.t Fri Aug 24 11:53:55 2012 +0200
@@ -5,7 +5,6 @@
> hgext.rebase=
> hgext.graphlog=
> EOF
- $ echo "obsolete=$(echo $(dirname $TESTDIR))/hgext/obsolete.py" >> $HGRCPATH
$ echo "evolve=$(echo $(dirname $TESTDIR))/hgext/evolve.py" >> $HGRCPATH
$ glog() {
@@ -24,9 +23,9 @@
marked working directory as branch foo
(branches are permanent and global, did you want a bookmark?)
$ hg amend
- $ hg debugsuccessors
- 07f494440405 a34b93d251e4
- bd19cbe78fbf a34b93d251e4
+ $ hg debugobsolete
+ bd19cbe78fbfbd87eb33420c63986fe5f3154f2c a34b93d251e49c93d5685ebacad785c73a7e8605 0 {'date': '* *', 'user': 'test'} (glob)
+ 07f4944404050f47db2e5c5071e0e84e7a27bba9 a34b93d251e49c93d5685ebacad785c73a7e8605 0 {'date': '* *', 'user': 'test'} (glob)
$ hg branch
foo
$ hg branches
@@ -58,13 +57,24 @@
$ echo a >> a
$ hg ci -m changea
$ echo a > a
+ $ hg status
+ M a
+ $ hg pstatus
+ $ hg diff
+ diff -r 2f97fe38810f a
+ --- a/a Thu Jan 01 00:00:00 1970 +0000
+ +++ b/a * +0000 (glob)
+ @@ -1,2 +1,1 @@
+ a
+ -a
+ $ hg pdiff
$ hg ci -m reseta
$ hg amend --change 2
abort: no updates found
[255]
- $ hg debugsuccessors
- 07f494440405 a34b93d251e4
- bd19cbe78fbf a34b93d251e4
+ $ hg debugobsolete
+ bd19cbe78fbfbd87eb33420c63986fe5f3154f2c a34b93d251e49c93d5685ebacad785c73a7e8605 0 {'date': '* *', 'user': 'test'} (glob)
+ 07f4944404050f47db2e5c5071e0e84e7a27bba9 a34b93d251e49c93d5685ebacad785c73a7e8605 0 {'date': '* *', 'user': 'test'} (glob)
$ hg phase 2
2: draft
$ glog
@@ -88,12 +98,12 @@
$ hg amend --change 2
abort: no updates found
[255]
- $ hg debugsuccessors
- 07f494440405 a34b93d251e4
- 7384bbcba36f 000000000000
- bd19cbe78fbf a34b93d251e4
+ $ hg debugobsolete
+ bd19cbe78fbfbd87eb33420c63986fe5f3154f2c a34b93d251e49c93d5685ebacad785c73a7e8605 0 {'date': '* *', 'user': 'test'} (glob)
+ 07f4944404050f47db2e5c5071e0e84e7a27bba9 a34b93d251e49c93d5685ebacad785c73a7e8605 0 {'date': '* *', 'user': 'test'} (glob)
+ 7384bbcba36fde1a789cd00f9cd6f9b919ab5910 0 {'date': '* *', 'user': 'test'} (glob)
$ glog
- @ 6@foo(secret) amends a34b93d251e49c93d5685ebacad785c73a7e8605
+ @ 6@foo(draft) amends a34b93d251e49c93d5685ebacad785c73a7e8605
|
o 5@default(draft) resetbranch
|
--- a/tests/test-corrupt.t Tue Aug 21 12:43:21 2012 +0200
+++ b/tests/test-corrupt.t Fri Aug 24 11:53:55 2012 +0200
@@ -16,7 +16,6 @@
> hgext.rebase=
> hgext.graphlog=
> EOF
- $ echo "obsolete=$(echo $(dirname $TESTDIR))/hgext/obsolete.py" >> $HGRCPATH
$ echo "evolve=$(echo $(dirname $TESTDIR))/hgext/evolve.py" >> $HGRCPATH
$ mkcommit() {
> echo "$1" >> "$1"
--- a/tests/test-evolve.t Tue Aug 21 12:43:21 2012 +0200
+++ b/tests/test-evolve.t Fri Aug 24 11:53:55 2012 +0200
@@ -15,7 +15,6 @@
> hgext.rebase=
> hgext.graphlog=
> EOF
- $ echo "obsolete=$(echo $(dirname $TESTDIR))/hgext/obsolete.py" >> $HGRCPATH
$ echo "evolve=$(echo $(dirname $TESTDIR))/hgext/evolve.py" >> $HGRCPATH
$ mkcommit() {
> echo "$1" > "$1"
@@ -59,7 +58,8 @@
$ hg log -r 1 --template '{rev} {phase} {obsolete}\n'
1 public stable
$ hg kill 1
- cannot kill immutable changeset 7c3bad9141dc
+ abort: Cannot obsolete immutable changeset: 7c3bad9141dc
+ [255]
$ hg log -r 1 --template '{rev} {phase} {obsolete}\n'
1 public stable
@@ -245,7 +245,7 @@
[255]
$ hg amend --note 'french looks better'
- 1 new unstables changesets
+ 1 new unstable changesets
$ hg log
6 feature-A: a nifty feature - test
4 feature-B: another feature - test
@@ -255,24 +255,24 @@
$ glog --hidden
o 6:23409eba69a0@default(draft) a nifty feature
|
- | o 5:e416e48b2742@default(secret) french looks better
+ | x 5:e416e48b2742@default(draft) french looks better
| |
| | o 4:f8111a076f09@default(draft) another feature
| |/
- | | o 3:524e478d4811@default(secret) fix spelling of Zwei
+ | | x 3:524e478d4811@default(draft) fix spelling of Zwei
| | |
- | | o 2:7b36850622b2@default(secret) another feature
+ | | x 2:7b36850622b2@default(draft) another feature
| |/
- | o 1:568a468b60fc@default(draft) a nifty feature
+ | x 1:568a468b60fc@default(draft) a nifty feature
|/
@ 0:e55e0562ee93@default(public) base
- $ hg debugsuccessors
- 524e478d4811 f8111a076f09
- 568a468b60fc 23409eba69a0
- 7b36850622b2 f8111a076f09
- e416e48b2742 23409eba69a0
- $ hg stabilize
+ $ hg debugobsolete
+ 524e478d4811d405c8771e4c441de4483bdf8b33 f8111a076f0975cbecb336e2bd3411be22b673fb 0 {'date': '* *', 'user': 'test'} (glob)
+ 7b36850622b2fd159fa30a4fb2a1edd2043b4a14 f8111a076f0975cbecb336e2bd3411be22b673fb 0 {'date': '* *', 'user': 'test'} (glob)
+ e416e48b27428695d00c2a2cc4a0b9619482e63f 23409eba69a0986e90cd42252852c1e6da97af5b 0 {'date': '* *', 'user': 'test'} (glob)
+ 568a468b60fc99a42d5d4ddbe181caff1eef308d 23409eba69a0986e90cd42252852c1e6da97af5b 0 {'date': '* *', 'user': 'test'} (glob)
+ $ hg evolve
move:[4] another feature
atop:[6] a nifty feature
merging main-file-1
@@ -299,7 +299,42 @@
phase change turning obsolete changeset public issue a latecomer warning
$ hg phase --public 7
- 1 new latecomers changesets
+ 1 new latecomer changesets
+
+all solving latecomer troubled
+
+ $ hg glog
+ @ 8 feature-B: another feature that rox - test
+ |
+ | o 7 : another feature - test
+ |/
+ o 6 feature-A: a nifty feature - test
+ |
+ o 0 : base - test
+
+ $ hg evolve --any --traceback
+ recreate:[8] another feature that rox
+ atop:[7] another feature
+ computing new diff
+ commited as 8d77fa12ab0c
+ $ hg glog
+ @ 9 feature-B: latecomer update to 5f4744038ed5: - test
+ |
+ o 7 : another feature - test
+ |
+ o 6 feature-A: a nifty feature - test
+ |
+ o 0 : base - test
+
+ $ hg diff -r 9 -r 8
+ $ hg diff -r 9^ -r 9
+ diff --git a/main-file-1 b/main-file-1
+ --- a/main-file-1
+ +++ b/main-file-1
+ @@ -3,1 +3,1 @@
+ -Zwei
+ +deux
+ $ hg log -r 'latecomer()' # no more latecomer
$ cd ..
@@ -405,9 +440,9 @@
|/
o 0:8685c6d34325@default(draft) add 0
- $ hg graft 3 -O
+ $ hg graft -r3 -O
grafting revision 3
- $ hg graft 1 -o 2
+ $ hg graft -r1 -o 2
grafting revision 1
$ glog --hidden
@ 6:acb28cd497b7@default(draft) add 1
@@ -416,17 +451,17 @@
|
o 4:ce341209337f@default(draft) add 4
|
- | o 3:0e84df4912da@default(secret) add 3
+ | x 3:0e84df4912da@default(draft) add 3
| |
- | o 2:db038628b9e5@default(secret) add 2
+ | x 2:db038628b9e5@default(draft) add 2
| |
| o 1:73d38bb17fd7@default(draft) add 1
|/
o 0:8685c6d34325@default(draft) add 0
- $ hg debugsuccessors
- 0e84df4912da 0b9e50c35132
- db038628b9e5 acb28cd497b7
+ $ hg debugobsolete
+ 0e84df4912da4c7cad22a3b4fcfd58ddfb7c8ae9 0b9e50c35132ff548ec0065caea6a87e1ebcef32 0 {'date': '* *', 'user': 'test'} (glob)
+ db038628b9e56f51a454c0da0c508df247b41748 acb28cd497b7f8767e01ef70f68697a959573c2d 0 {'date': '* *', 'user': 'test'} (glob)
Test graft --continue
@@ -452,7 +487,7 @@
$ glog --hidden
@ 8:920e58bb443b@default(draft) conflict
|
- | o 7:a5bfd90a2f29@default(secret) conflict
+ | x 7:a5bfd90a2f29@default(draft) conflict
| |
o | 6:acb28cd497b7@default(draft) add 1
| |
@@ -460,18 +495,87 @@
| |
o | 4:ce341209337f@default(draft) add 4
|/
- | o 3:0e84df4912da@default(secret) add 3
+ | x 3:0e84df4912da@default(draft) add 3
| |
- | o 2:db038628b9e5@default(secret) add 2
+ | x 2:db038628b9e5@default(draft) add 2
| |
| o 1:73d38bb17fd7@default(draft) add 1
|/
o 0:8685c6d34325@default(draft) add 0
- $ hg debugsuccessors
- 0e84df4912da 0b9e50c35132
- a5bfd90a2f29 920e58bb443b
- db038628b9e5 acb28cd497b7
+ $ hg debugobsolete
+ 0e84df4912da4c7cad22a3b4fcfd58ddfb7c8ae9 0b9e50c35132ff548ec0065caea6a87e1ebcef32 0 {'date': '* *', 'user': 'test'} (glob)
+ db038628b9e56f51a454c0da0c508df247b41748 acb28cd497b7f8767e01ef70f68697a959573c2d 0 {'date': '* *', 'user': 'test'} (glob)
+ a5bfd90a2f29c7ccb8f917ff4e5013a9053d0a04 920e58bb443b73eea9d6d65570b4241051ea3229 0 {'date': '* *', 'user': 'test'} (glob)
+
+Test touch
- $ cd ..
+ $ glog
+ @ 8:920e58bb443b@default(draft) conflict
+ |
+ o 6:acb28cd497b7@default(draft) add 1
+ |
+ o 5:0b9e50c35132@default(draft) add 3
+ |
+ o 4:ce341209337f@default(draft) add 4
+ |
+ | o 1:73d38bb17fd7@default(draft) add 1
+ |/
+ o 0:8685c6d34325@default(draft) add 0
+
+ $ hg touch
+ $ glog
+ @ 9:*@default(draft) conflict (glob)
+ |
+ o 6:acb28cd497b7@default(draft) add 1
+ |
+ o 5:0b9e50c35132@default(draft) add 3
+ |
+ o 4:ce341209337f@default(draft) add 4
+ |
+ | o 1:73d38bb17fd7@default(draft) add 1
+ |/
+ o 0:8685c6d34325@default(draft) add 0
+
+ $ hg touch .
+ $ glog
+ @ 10:*@default(draft) conflict (glob)
+ |
+ o 6:acb28cd497b7@default(draft) add 1
+ |
+ o 5:0b9e50c35132@default(draft) add 3
+ |
+ o 4:ce341209337f@default(draft) add 4
+ |
+ | o 1:73d38bb17fd7@default(draft) add 1
+ |/
+ o 0:8685c6d34325@default(draft) add 0
+
+Test fold
+
+ $ hg fold 6::10
+ 2 changesets folded
+ $ glog
+ @ 11:*@default(draft) add 1 (glob)
+ |
+ o 5:0b9e50c35132@default(draft) add 3
+ |
+ o 4:ce341209337f@default(draft) add 4
+ |
+ | o 1:73d38bb17fd7@default(draft) add 1
+ |/
+ o 0:8685c6d34325@default(draft) add 0
+
+ $ hg log -r 11 --template '{desc}\n'
+ add 1
+
+ ***
+
+ conflict
+
+Test olog
+
+ $ hg olog
+ 6 : add 1 - test
+ 10 : conflict - test
--- a/tests/test-obsolete-push.t Tue Aug 21 12:43:21 2012 +0200
+++ b/tests/test-obsolete-push.t Fri Aug 24 11:53:55 2012 +0200
@@ -5,7 +5,6 @@
> hgext.rebase=
> hgext.graphlog=
> EOF
- $ echo "obsolete=$(echo $(dirname $TESTDIR))/hgext/obsolete.py" >> $HGRCPATH
$ echo "evolve=$(echo $(dirname $TESTDIR))/hgext/evolve.py" >> $HGRCPATH
$ template='{rev}:{node|short}@{branch}({obsolete}/{phase}) {desc|firstline}\n'
@@ -28,13 +27,13 @@
$ hg ci -qAm C c
$ hg phase --secret --force .
$ hg kill 0 1
- 1 new unstables changesets
+ 1 new unstable changesets
$ glog --hidden
@ 2:244232c2222a@default(unstable/secret) C
|
- | o 1:6c81ed0049f8@default(extinct/secret) B
+ | x 1:6c81ed0049f8@default(extinct/draft) B
|/
- o 0:1994f17a630e@default(suspended/secret) A
+ x 0:1994f17a630e@default(suspended/draft) A
$ hg init ../clone
$ cat > ../clone/.hg/hgrc <<EOF
@@ -44,5 +43,4 @@
$ hg outgoing ../clone --template "$template"
comparing with ../clone
searching for changes
- no changes found (ignored 2 secret changesets)
- [1]
+ 0:1994f17a630e@default(suspended/draft) A
--- a/tests/test-obsolete-rebase.t Tue Aug 21 12:43:21 2012 +0200
+++ b/tests/test-obsolete-rebase.t Fri Aug 24 11:53:55 2012 +0200
@@ -5,7 +5,7 @@
> hgext.rebase=
> hgext.graphlog=
> EOF
- $ echo "obsolete=$(echo $(dirname $TESTDIR))/hgext/obsolete.py" >> $HGRCPATH
+ $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext/evolve.py" >> $HGRCPATH
$ glog() {
> hg glog --template '{rev}:{node|short}@{branch}({phase}) {desc|firstline}\n'\
@@ -30,6 +30,15 @@
created new head
$ echo e > e
$ hg ci -Am adde e
+
+(phase compliance)
+
+ $ hg phase --public 3
+ $ hg rebase -d 1 -r 3
+ abort: can't rebase immutable changeset 98e4a024635e
+ (see hg help phases for details)
+ [255]
+ $ hg phase --draft --force 0
$ hg rebase -d 1 -r 3 --detach --keep
$ glog
@ 4:9c5494949763@default(draft) adde
@@ -53,7 +62,7 @@
|/
o 0:07f494440405@default(draft) adda
- $ hg debugsuccessors
+ $ hg debugobsolete
$ hg --config extensions.hgext.mq= strip tip
0 files updated, 0 files merged, 1 files removed, 0 files unresolved
saved backup bundle to $TESTTMP/repo/.hg/strip-backup/9c5494949763-backup.hg
@@ -70,7 +79,7 @@
$ glog --hidden
@ 4:9c5494949763@default(draft) adde
|
- | o 3:98e4a024635e@default(secret) adde
+ | x 3:98e4a024635e@default(draft) adde
| |
| o 2:102a90ea7b4a@default(draft) addb
| |
@@ -78,8 +87,8 @@
|/
o 0:07f494440405@default(draft) adda
- $ hg debugsuccessors
- 98e4a024635e 9c5494949763
+ $ hg debugobsolete
+ 98e4a024635e8c50928144c9277a4388d26bd786 9c54949497631abfb5a255d96746bbd3a42ed2ba 0 {'date': '* *', 'user': 'test'} (glob)
Test rebase with deleted empty revision
@@ -92,11 +101,11 @@
$ hg ci -m changea
$ hg rebase -d 1
$ glog --hidden
- o 5:4e322f7ce8e3@foo(secret) changea
+ x 5:4e322f7ce8e3@foo(draft) changea
|
| o 4:9c5494949763@default(draft) adde
| |
- | | o 3:98e4a024635e@default(secret) adde
+ | | x 3:98e4a024635e@default(draft) adde
| | |
+---o 2:102a90ea7b4a@default(draft) addb
| |
@@ -104,9 +113,9 @@
|/
o 0:07f494440405@default(draft) adda
- $ hg debugsuccessors
- 4e322f7ce8e3 000000000000
- 98e4a024635e 9c5494949763
+ $ hg debugobsolete
+ 98e4a024635e8c50928144c9277a4388d26bd786 9c54949497631abfb5a255d96746bbd3a42ed2ba 0 {'date': '* *', 'user': 'test'} (glob)
+ 4e322f7ce8e3e4203950eac9ece27bf7e45ffa6c 540395c442253af3b991be882b539e7e198b5808 0 {'date': '* *', 'user': 'test'} (glob)
Test rebase --collapse
@@ -123,15 +132,15 @@
$ glog --hidden
@ 8:a7773ffa7edc@default(draft) Collapsed revision
|
- | o 7:03f31481307a@default(secret) changec
+ | x 7:03f31481307a@default(draft) changec
| |
- | o 6:076e9b2ffbe1@default(secret) addc
+ | x 6:076e9b2ffbe1@default(draft) addc
| |
- | | o 5:4e322f7ce8e3@foo(secret) changea
+ | | x 5:4e322f7ce8e3@foo(draft) changea
| |/
+---o 4:9c5494949763@default(draft) adde
| |
- | | o 3:98e4a024635e@default(secret) adde
+ | | x 3:98e4a024635e@default(draft) adde
| | |
| | o 2:102a90ea7b4a@default(draft) addb
| |/
@@ -139,15 +148,15 @@
|/
o 0:07f494440405@default(draft) adda
- $ hg debugsuccessors
- 03f31481307a a7773ffa7edc
- 076e9b2ffbe1 a7773ffa7edc
- 4e322f7ce8e3 000000000000
- 98e4a024635e 9c5494949763
+ $ hg debugobsolete
+ 98e4a024635e8c50928144c9277a4388d26bd786 9c54949497631abfb5a255d96746bbd3a42ed2ba 0 {'date': '* *', 'user': 'test'} (glob)
+ 4e322f7ce8e3e4203950eac9ece27bf7e45ffa6c 540395c442253af3b991be882b539e7e198b5808 0 {'date': '* *', 'user': 'test'} (glob)
+ 076e9b2ffbe11c7bcb9ee97f5c0c8b88a1a10b93 a7773ffa7edcfac27b5dcdb2d5c1036e15a49861 0 {'date': '* *', 'user': 'test'} (glob)
+ 03f31481307aaf5275d07ec28c1c59931759ccd2 a7773ffa7edcfac27b5dcdb2d5c1036e15a49861 0 {'date': '* *', 'user': 'test'} (glob)
Test rebase --abort
- $ hg debugsuccessors > ../successors.old
+ $ hg debugobsolete > ../successors.old
$ hg up 0
1 files updated, 0 files merged, 1 files removed, 0 files unresolved
$ echo d > d
@@ -164,7 +173,7 @@
$ hg rebase --abort
saved backup bundle to $TESTTMP/repo/.hg/strip-backup/03f165c84ea8-backup.hg
rebase aborted
- $ hg debugsuccessors > ../successors.new
+ $ hg debugobsolete > ../successors.new
$ diff -u ../successors.old ../successors.new
Test rebase --continue
@@ -182,21 +191,21 @@
|
o 11:03f165c84ea8@default(draft) addd
|
- | o 10:4b9d80f48523@default(secret) appendab
+ | x 10:4b9d80f48523@default(draft) appendab
| |
- | o 9:a31943eabc43@default(secret) addd
+ | x 9:a31943eabc43@default(draft) addd
| |
+---o 8:a7773ffa7edc@default(draft) Collapsed revision
| |
- | | o 7:03f31481307a@default(secret) changec
+ | | x 7:03f31481307a@default(draft) changec
| | |
- | | o 6:076e9b2ffbe1@default(secret) addc
+ | | x 6:076e9b2ffbe1@default(draft) addc
| |/
- | | o 5:4e322f7ce8e3@foo(secret) changea
+ | | x 5:4e322f7ce8e3@foo(draft) changea
| |/
+---o 4:9c5494949763@default(draft) adde
| |
- | | o 3:98e4a024635e@default(secret) adde
+ | | x 3:98e4a024635e@default(draft) adde
| | |
| | o 2:102a90ea7b4a@default(draft) addb
| |/
@@ -204,15 +213,14 @@
|/
o 0:07f494440405@default(draft) adda
- $ hg debugsuccessors > ../successors.new
+ $ hg debugobsolete > ../successors.new
$ diff -u ../successors.old ../successors.new
--- ../successors.old* (glob)
+++ ../successors.new* (glob)
- @@ -1,4 +1,6 @@
- 03f31481307a a7773ffa7edc
- 076e9b2ffbe1 a7773ffa7edc
- +4b9d80f48523 1951ead97108
- 4e322f7ce8e3 000000000000
- 98e4a024635e 9c5494949763
- +a31943eabc43 03f165c84ea8
+ @@ -2,3 +2,5 @@
+ 4e322f7ce8e3e4203950eac9ece27bf7e45ffa6c 540395c442253af3b991be882b539e7e198b5808 0 {'date': '* *', 'user': 'test'} (glob)
+ 076e9b2ffbe11c7bcb9ee97f5c0c8b88a1a10b93 a7773ffa7edcfac27b5dcdb2d5c1036e15a49861 0 {'date': '* *', 'user': 'test'} (glob)
+ 03f31481307aaf5275d07ec28c1c59931759ccd2 a7773ffa7edcfac27b5dcdb2d5c1036e15a49861 0 {'date': '* *', 'user': 'test'} (glob)
+ +a31943eabc4327df16f9eca71bf7779c32f815f7 03f165c84ea8889fc35a64a392caa7a0084dd212 0 {'date': '* *', 'user': 'test'} (glob)
+ +4b9d80f48523e296f4402cc8e37236b768dfb981 1951ead9710803dbf117e95901954d5ed717f80b 0 {'date': '* *', 'user': 'test'} (glob)
[1]
--- a/tests/test-obsolete.t Tue Aug 21 12:43:21 2012 +0200
+++ b/tests/test-obsolete.t Fri Aug 24 11:53:55 2012 +0200
@@ -8,13 +8,17 @@
> odiff=diff --rev 'limit(precursors(.),1)' --rev .
> [extensions]
> hgext.graphlog=
+ > hgext.rebase=
> EOF
- $ echo "obsolete=$(echo $(dirname $TESTDIR))/hgext/obsolete.py" >> $HGRCPATH
+ $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext/evolve.py" >> $HGRCPATH
$ mkcommit() {
> echo "$1" > "$1"
> hg add "$1"
> hg ci -m "add $1"
> }
+ $ getid() {
+ > hg id --debug -ir "$1"
+ > }
$ alias qlog="hg log --template='{rev}\n- {node|short}\n'"
$ hg init local
@@ -27,9 +31,21 @@
0 files updated, 0 files merged, 1 files removed, 0 files unresolved
$ mkcommit obsol_c # 3
created new head
- $ hg debugobsolete 3 2
+ $ getid 2
+ 4538525df7e2b9f09423636c61ef63a4cb872a2d
+ $ getid 3
+ 0d3f46688ccc6e756c7e96cf64c391c411309597
+ $ hg debugobsolete 4538525df7e2b9f09423636c61ef63a4cb872a2d 0d3f46688ccc6e756c7e96cf64c391c411309597
+ $ hg debugobsolete
+ 4538525df7e2b9f09423636c61ef63a4cb872a2d 0d3f46688ccc6e756c7e96cf64c391c411309597 0 {'date': '', 'user': 'test'} (glob)
+Test hidden() revset
+
+ $ qlog -r 'hidden()' --hidden
+ 2
+ - 4538525df7e2
+
Test that obsolete changeset are hidden
$ qlog
@@ -82,7 +98,7 @@
$ hg up 1 -q
$ mkcommit "obsol_c'" # 4 (on 1)
created new head
- $ hg debugobsolete 4 3
+ $ hg debugobsolete `getid 3` `getid 4`
$ qlog
4
- 725c380fe99b
@@ -108,7 +124,7 @@
$ hg up 3 -q
Working directory parent is obsolete
$ mkcommit d # 5 (on 3)
- 1 new unstables changesets
+ 1 new unstable changesets
$ qlog -r 'obsolete()'
3
- 0d3f46688ccc
@@ -127,13 +143,13 @@
$ hg glog --template '{rev}:{node|short}@{branch}({obsolete}/{phase}) {desc|firstline}\n' \
> --hidden
- @ 5:a7a6f2b5d8a5@default(unstable/secret) add d
+ @ 5:a7a6f2b5d8a5@default(unstable/draft) add d
|
| o 4:725c380fe99b@default(stable/draft) add obsol_c'
| |
- o | 3:0d3f46688ccc@default(suspended/secret) add obsol_c
+ x | 3:0d3f46688ccc@default(suspended/draft) add obsol_c
|/
- | o 2:4538525df7e2@default(extinct/secret) add c
+ | x 2:4538525df7e2@default(extinct/draft) add c
|/
o 1:7c3bad9141dc@default(stable/draft) add b
|
@@ -144,11 +160,13 @@
$ hg init ../other-new
$ hg phase --draft 'secret() - extinct()' # until we fix exclusion
+ abort: empty revision set
+ [255]
$ hg push ../other-new
pushing to ../other-new
searching for changes
abort: push includes an unstable changeset: a7a6f2b5d8a5!
- (use 'hg stabilize' to get a stable history (or --force to proceed))
+ (use 'hg evolve' to get a stable history or --force to ignore warnings)
[255]
$ hg push -f ../other-new
pushing to ../other-new
@@ -181,8 +199,8 @@
Working directory parent is obsolete
$ mkcommit obsol_d # 6
created new head
- 1 new unstables changesets
- $ hg debugobsolete 6 5
+ 1 new unstable changesets
+ $ hg debugobsolete `getid 5` `getid 6`
$ qlog
6
- 95de7fc6918d
@@ -201,7 +219,7 @@
pushing to ../other-new
searching for changes
abort: push includes an unstable changeset: 95de7fc6918d!
- (use 'hg stabilize' to get a stable history (or --force to proceed))
+ (use 'hg evolve' to get a stable history or --force to ignore warnings)
[255]
$ hg push ../other-new -f # use f because there is unstability
pushing to ../other-new
@@ -230,15 +248,15 @@
$ hg push ../other-new
pushing to ../other-new
searching for changes
- no changes found (ignored 0 secret changesets)
+ no changes found
[1]
$ hg up -q .^ # 3
Working directory parent is obsolete
$ mkcommit "obsol_d'" # 7
created new head
- 1 new unstables changesets
- $ hg debugobsolete 7 6
+ 1 new unstable changesets
+ $ hg debugobsolete `getid 6` `getid 7`
$ hg pull -R ../other-new .
pulling from .
searching for changes
@@ -261,55 +279,58 @@
pushing to stuff that doesn't support obsolete
- $ hg init ../other-old
- > # XXX I don't like this but changeset get published otherwise
- > # remove it when we will get a --keep-state flag for push
- $ echo '[extensions]' > ../other-old/.hg/hgrc
- $ echo "obsolete=!$(echo $(dirname $TESTDIR))/obsolete.py" >> ../other-old/.hg/hgrc
- $ hg push ../other-old
- pushing to ../other-old
- searching for changes
- abort: push includes an unstable changeset: 909a0fb57e5d!
- (use 'hg stabilize' to get a stable history (or --force to proceed))
- [255]
- $ hg push -f ../other-old
- pushing to ../other-old
- searching for changes
- adding changesets
- adding manifests
- adding file changes
- added 5 changesets with 5 changes to 5 files (+1 heads)
- $ qlog -R ../other-old
- 4
- - 909a0fb57e5d
- 3
- - 725c380fe99b
- 2
- - 0d3f46688ccc
- 1
- - 7c3bad9141dc
- 0
- - 1f0dee641bb7
+DISABLED. the _enable switch it global :-/
+
+.. $ hg init ../other-old
+.. > # XXX I don't like this but changeset get published otherwise
+.. > # remove it when we will get a --keep-state flag for push
+.. $ echo '[extensions]' > ../other-old/.hg/hgrc
+.. $ echo "obsolete=!$(echo $(dirname $TESTDIR))/obsolete.py" >> ../other-old/.hg/hgrc
+.. $ hg push ../other-old
+.. pushing to ../other-old
+.. searching for changes
+.. abort: push includes an unstable changeset: 909a0fb57e5d!
+.. (use 'hg evolve' to get a stable history or --force to ignore warnings)
+.. [255]
+.. $ hg push -f ../other-old
+.. pushing to ../other-old
+.. searching for changes
+.. adding changesets
+.. adding manifests
+.. adding file changes
+.. added 5 changesets with 5 changes to 5 files (+1 heads)
+.. $ qlog -R ../other-ol
+.. 4
+.. - 909a0fb57e5d
+.. 3
+.. - 725c380fe99b
+.. 2
+.. - 0d3f46688ccc
+.. 1
+.. - 7c3bad9141dc
+.. 0
+.. - 1f0dee641bb7
clone support
$ hg clone . ../cloned
> # The warning should go away once we have default value to set ready before we pull
- requesting all changes
- adding changesets
- adding manifests
- adding file changes
- added 5 changesets with 5 changes to 5 files (+1 heads)
updating to branch default
4 files updated, 0 files merged, 0 files removed, 0 files unresolved
- $ qlog -R ../cloned
+ $ qlog -R ../cloned --hidden
+ 7
+ - 909a0fb57e5d
+ 6
+ - 95de7fc6918d
+ 5
+ - a7a6f2b5d8a5
4
- - 909a0fb57e5d
+ - 725c380fe99b
3
- - 725c380fe99b
+ - 0d3f46688ccc
2
- - 0d3f46688ccc
+ - 4538525df7e2
1
- 7c3bad9141dc
0
@@ -321,8 +342,8 @@
Working directory parent is obsolete
$ mkcommit "obsol_d''"
created new head
- 1 new unstables changesets
- $ hg debugobsolete 8 7
+ 1 new unstable changesets
+ $ hg debugobsolete `getid 7` `getid 8`
$ cd ../other-new
$ hg up -q 3
$ hg pull ../local/
@@ -355,6 +376,8 @@
branch: default
commit: 1 deleted, 2 unknown (clean)
update: 4 new changesets, 4 branch heads (merge)
+ unstable: 1 changesets
+ conflicting: 1 changesets
$ qlog
6
- 909a0fb57e5d
@@ -379,8 +402,10 @@
created new head
$ hg id -n
9
- $ hg debugobsolete 9 0
- 83b5778897ad try to obsolete immutable changeset 1f0dee641bb7
+ $ hg debugobsolete `getid 0` `getid 9`
+83b5778897ad try to obsolete immutable changeset 1f0dee641bb7
+# at core level the warning is not issued
+# this is now a big issue now that we have latecomer warning
$ qlog -r 'obsolete()'
3
- 0d3f46688ccc
@@ -400,7 +425,7 @@
0
- 1f0dee641bb7
- $ hg debugobsolete null 9 #kill
+ $ hg debugobsolete `getid 9` #kill
$ hg up null -q # to be not based on 9 anymore
$ qlog
8
@@ -414,6 +439,23 @@
0
- 1f0dee641bb7
+Check that auto update ignore hidden changeset
+ $ hg up 0
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ hg up
+ 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ hg id -n
+ 8
+
+Check that named update do too
+
+ $ hg update default
+ 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ hg id -n
+ 8
+
+ $ hg up null -q # to be not based on 9 anymore
+
check rebase compat
$ hg glog -r 'not extinct()' --template='{rev} - {node|short}\n'
@@ -421,29 +463,29 @@
|
| o 4 - 725c380fe99b
| |
- o | 3 - 0d3f46688ccc
+ x | 3 - 0d3f46688ccc
|/
o 1 - 7c3bad9141dc
|
o 0 - 1f0dee641bb7
- $ hg glog --template='{rev} - {node|short}\n' `(hg --version | grep -q 'version 2.1') || echo '--hidden'`
- o 9 - 83b5778897ad
+ $ hg glog --template='{rev} - {node|short}\n' --hidden
+ x 9 - 83b5778897ad
o 8 - 159dfc9fa5d3
|
- | o 7 - 909a0fb57e5d
+ | x 7 - 909a0fb57e5d
|/
- | o 6 - 95de7fc6918d
+ | x 6 - 95de7fc6918d
|/
- | o 5 - a7a6f2b5d8a5
+ | x 5 - a7a6f2b5d8a5
|/
| o 4 - 725c380fe99b
| |
- o | 3 - 0d3f46688ccc
+ x | 3 - 0d3f46688ccc
|/
- | o 2 - 4538525df7e2
+ | x 2 - 4538525df7e2
|/
o 1 - 7c3bad9141dc
|
@@ -452,6 +494,10 @@
should not rebase extinct changeset
+ $ hg --config extensions.hgext.rebase= rebase -s 7 -d 4
+ whole rebase set is extinct and ignored.
+ nothing to rebase
+ [1]
$ hg --config extensions.hgext.rebase= rebase -b 3 -d 4 --traceback
$ hg --config extensions.graphlog= glog -r 'not extinct()' --template='{rev} - {node|short}\n'
@ 11 - 9468a5f5d8b2
@@ -477,7 +523,7 @@
$ hg up -q 10
$ mkcommit "obsol_d'''"
created new head
- $ hg debugobsolete 12 11
+ $ hg debugobsolete `getid 11` `getid 12`
$ hg push ../other-new --traceback
pushing to ../other-new
searching for changes
@@ -492,7 +538,7 @@
$ cd local
$ hg phase --public 11
- 1 new latecomers changesets
+ 1 new latecomer changesets
$ hg --config extensions.graphlog=glog glog --template='{rev} - ({phase}) {node|short} {desc}\n'
@ 12 - (draft) 6db5e282cb91 add obsol_d'''
|
@@ -517,8 +563,8 @@
$ hg push ../other-new/
pushing to ../other-new/
searching for changes
- abort: push includes an latecomer changeset: 6db5e282cb91!
- (use 'hg stabilize' to get a stable history (or --force to proceed))
+ abort: push includes a latecomer changeset: 6db5e282cb91!
+ (use 'hg evolve' to get a stable history or --force to ignore warnings)
[255]
Check hg commit --amend compat
@@ -571,18 +617,18 @@
date: Thu Jan 01 00:00:00 1970 +0000
summary: add a
- $ hg debugsuccessors
- 0b1b6dd009c0 3734a65252e6
- 0d3f46688ccc 2033b4e49474
- 0d3f46688ccc 725c380fe99b
- 159dfc9fa5d3 9468a5f5d8b2
- 1f0dee641bb7 83b5778897ad
- 4538525df7e2 0d3f46688ccc
- 83b5778897ad 000000000000
- 909a0fb57e5d 159dfc9fa5d3
- 9468a5f5d8b2 6db5e282cb91
- 95de7fc6918d 909a0fb57e5d
- a7a6f2b5d8a5 95de7fc6918d
+ $ hg debugobsolete
+ 4538525df7e2b9f09423636c61ef63a4cb872a2d 0d3f46688ccc6e756c7e96cf64c391c411309597 0 {'date': '', 'user': 'test'} (glob)
+ 0d3f46688ccc6e756c7e96cf64c391c411309597 725c380fe99b5e76613493f0903e8d11ddc70d54 0 {'date': '', 'user': 'test'} (glob)
+ a7a6f2b5d8a54b81bc7aa2fba2934ad6d700a79e 95de7fc6918dea4c9c8d5382f50649794b474c4a 0 {'date': '', 'user': 'test'} (glob)
+ 95de7fc6918dea4c9c8d5382f50649794b474c4a 909a0fb57e5d909f353d89e394ffd7e0890fec88 0 {'date': '', 'user': 'test'} (glob)
+ 909a0fb57e5d909f353d89e394ffd7e0890fec88 159dfc9fa5d334d7e03a0aecfc7f7ab4c3431fea 0 {'date': '', 'user': 'test'} (glob)
+ 1f0dee641bb7258c56bd60e93edfa2405381c41e 83b5778897adafb967ef2f75be3aaa4fce49a4cc 0 {'date': '', 'user': 'test'} (glob)
+ 83b5778897adafb967ef2f75be3aaa4fce49a4cc 0 {'date': '', 'user': 'test'} (glob)
+ 0d3f46688ccc6e756c7e96cf64c391c411309597 2033b4e494742365851fac84d276640cbf52833e 0 {'date': '* *', 'user': 'test'} (glob)
+ 159dfc9fa5d334d7e03a0aecfc7f7ab4c3431fea 9468a5f5d8b2c5d91e17474e95ae4791e9718fdf 0 {'date': '* *', 'user': 'test'} (glob)
+ 9468a5f5d8b2c5d91e17474e95ae4791e9718fdf 6db5e282cb91df5c43ff1f1287c119ff83230d42 0 {'date': '', 'user': 'test'} (glob)
+ 0b1b6dd009c037985363e2290a0b579819f659db 3734a65252e69ddcced85901647a4f335d40de1e 0 {'date': '* *', 'user': 'test'} (glob)
Check conflict detection
@@ -595,7 +641,8 @@
branch: default
commit: (clean)
update: 9 new changesets, 9 branch heads (merge)
- $ hg debugobsolete 50f11e5e3a63 a7a6f2b5d8a5
+ latecomer: 1 changesets
+ $ hg debugobsolete `getid a7a6f2b5d8a5` `getid 50f11e5e3a63`
$ hg log -r 'conflicting()'
changeset: 14:50f11e5e3a63
tag: tip
--- a/tests/test-oldconvert.t Tue Aug 21 12:43:21 2012 +0200
+++ b/tests/test-oldconvert.t Fri Aug 24 11:53:55 2012 +0200
@@ -53,8 +53,8 @@
date: Thu Jan 01 00:00:00 1970 +0000
summary: add a
- $ hg debugsuccessors
- 7c3bad9141dc d67cd0334eee
+ $ hg debugobsolete
+ 7c3bad9141dcb46ff89abf5f61856facd56e476c d67cd0334eeecfded222fed9009f0db4beb57585 0 {'date': '* *', 'user': 'test'} (glob)
$ hg debugconvertobsolete
nothing to do
0 obsolete marker converted
@@ -107,8 +107,8 @@
[255]
$ hg debugconvertobsolete --traceback
3 obsolete marker converted
- $ hg debugsuccessors
- 2c3784e102bb
- 3e03d82708d4 3218406b50ed
- 5c722672795c
- 7c3bad9141dc d67cd0334eee
+ $ hg debugobsolete
+ 7c3bad9141dcb46ff89abf5f61856facd56e476c d67cd0334eeecfded222fed9009f0db4beb57585 0 {'date': '* *', 'user': 'test'} (glob)
+ 3e03d82708d4da97a92158558dd13386d8f09ad5 3218406b50ed13480765e7c260669620f37fba6e 0 {'date': '* *', 'user': 'Pierre-Yves David <pierre-yves.david@ens-lyon.org>'} (glob)
+ 5c722672795c3a2cb94d0cc9a821c394c1475f87 0 {'date': '* *', 'user': 'Pierre-Yves David <pierre-yves.david@logilab.fr>'} (glob)
+ 2c3784e102bb34ccc93862af5bd6d609ee30c577 0 {'date': '* *', 'user': 'Pierre-Yves David <pierre-yves.david@logilab.fr>'} (glob)
--- a/tests/test-qsync.t Tue Aug 21 12:43:21 2012 +0200
+++ b/tests/test-qsync.t Fri Aug 24 11:53:55 2012 +0200
@@ -17,7 +17,6 @@
> hgext.graphlog=
> hgext.mq=
> EOF
- $ echo "obsolete=$(echo $(dirname $TESTDIR))/hgext/obsolete.py" >> $HGRCPATH
$ echo "evolve=$(echo $(dirname $TESTDIR))/hgext/evolve.py" >> $HGRCPATH
$ echo "qsync=$(echo $(dirname $TESTDIR))/hgext/qsync.py" >> $HGRCPATH
$ mkcommit() {
@@ -84,7 +83,7 @@
0 files updated, 0 files merged, 1 files removed, 0 files unresolved
$ echo "a" >> a
$ hg amend
- 1 new unstables changesets
+ 1 new unstable changesets
$ hg graft -O 3
grafting revision 3
$ hg qsync -a
@@ -182,7 +181,6 @@
pulling from ../local2
searching for changes
no changes found
- (run 'hg update' to get a working copy)
$ hg pull --mq ../local2/.hg/patches
pulling from ../local2/.hg/patches
searching for changes
--- a/tests/test-stabilize-order.t Tue Aug 21 12:43:21 2012 +0200
+++ b/tests/test-stabilize-order.t Fri Aug 24 11:53:55 2012 +0200
@@ -5,7 +5,6 @@
> hgext.rebase=
> hgext.graphlog=
> EOF
- $ echo "obsolete=$(echo $(dirname $TESTDIR))/hgext/obsolete.py" >> $HGRCPATH
$ echo "evolve=$(echo $(dirname $TESTDIR))/hgext/evolve.py" >> $HGRCPATH
$ glog() {
@@ -40,13 +39,13 @@
[2] addb
$ echo b >> b
$ hg amend
- 1 new unstables changesets
+ 1 new unstable changesets
$ hg gdown
0 files updated, 0 files merged, 1 files removed, 0 files unresolved
[1] adda
$ echo a >> a
$ hg amend
- 1 new unstables changesets
+ 1 new unstable changesets
$ glog
@ 7:f5ff10856e5a@default(draft) adda
|
@@ -54,16 +53,16 @@
| |
| | o 3:7a7552255fb5@default(draft) addc
| | |
- | | o 2:ef23d6ef94d6@default(draft) addb
+ | | x 2:ef23d6ef94d6@default(draft) addb
| |/
- | o 1:93418d2c0979@default(draft) adda
+ | x 1:93418d2c0979@default(draft) adda
|/
o 0:c471ef929e6a@default(draft) addroot
Test stabilizing a predecessor child
- $ hg stabilize -v
+ $ hg evolve -v
move:[5] addb
atop:[7] adda
hg rebase -Dr ab8cbb6d87ff -d f5ff10856e5a
@@ -77,9 +76,9 @@
|
| o 3:7a7552255fb5@default(draft) addc
| |
- | o 2:ef23d6ef94d6@default(draft) addb
+ | x 2:ef23d6ef94d6@default(draft) addb
| |
- | o 1:93418d2c0979@default(draft) adda
+ | x 1:93418d2c0979@default(draft) adda
|/
o 0:c471ef929e6a@default(draft) addroot
@@ -88,8 +87,8 @@
$ hg up 7
0 files updated, 0 files merged, 1 files removed, 0 files unresolved
- $ hg debugsuccessors > successors.old
- $ hg stabilize -v
+ $ hg debugobsolete > successors.old
+ $ hg evolve -v
move:[3] addc
atop:[8] addb
hg rebase -Dr 7a7552255fb5 -d 6bf44048e43f
@@ -98,17 +97,15 @@
resolving manifests
getting c
c
- $ hg debugsuccessors > successors.new
+ $ hg debugobsolete > successors.new
$ diff -u successors.old successors.new
--- successors.old* (glob)
+++ successors.new* (glob)
- @@ -1,5 +1,6 @@
- 3a4a591493f8 f5ff10856e5a
- 3ca0ded0dc50 ab8cbb6d87ff
- +7a7552255fb5 5e819fbb0d27
- 93418d2c0979 f5ff10856e5a
- ab8cbb6d87ff 6bf44048e43f
- ef23d6ef94d6 ab8cbb6d87ff
+ @@ -3,3 +3,4 @@
+ 3a4a591493f80708e46f2bf6d3b4debfad8ff91e f5ff10856e5ab3c8dc420b9c11460e6832a3b78c 0 {'date': '* *', 'user': 'test'} (glob)
+ 93418d2c0979643ad446f621195e78720edb05b4 f5ff10856e5ab3c8dc420b9c11460e6832a3b78c 0 {'date': '* *', 'user': 'test'} (glob)
+ ab8cbb6d87ff3ab5526735a051cba6b63f3d6775 6bf44048e43f830accbf7d2bd7bc252ad7a3b99c 0 {'date': '* *', 'user': 'test'} (glob)
+ +7a7552255fb5f8bd745e46fba6f0ca633a4dd716 5e819fbb0d278117c0a83b7f6f6486689732cfb2 0 {'date': '* *', 'user': 'test'} (glob)
[1]
$ glog
@ 9:5e819fbb0d27@default(draft) addc
@@ -119,8 +116,8 @@
|
o 0:c471ef929e6a@default(draft) addroot
- $ hg stabilize -v
- no unstable changeset
+ $ hg evolve -v
+ no troubled changeset
[1]
Test behaviour with --any
@@ -129,13 +126,13 @@
0 files updated, 0 files merged, 1 files removed, 0 files unresolved
$ echo b >> b
$ hg amend
- 1 new unstables changesets
+ 1 new unstable changesets
$ glog
@ 11:4e7cec6b4afe@default(draft) addb
|
| o 9:5e819fbb0d27@default(draft) addc
| |
- | o 8:6bf44048e43f@default(draft) addb
+ | x 8:6bf44048e43f@default(draft) addb
|/
o 7:f5ff10856e5a@default(draft) adda
|
@@ -143,11 +140,11 @@
$ hg up 9
2 files updated, 0 files merged, 0 files removed, 0 files unresolved
- $ hg stabilize -v
- nothing to stabilize here
- (1 unstable changesets, do you want --any ?)
+ $ hg evolve -v
+ nothing to evolve here
+ (1 troubled changesets, do you want --any ?)
[2]
- $ hg stabilize --any -v
+ $ hg evolve --any -v
move:[9] addc
atop:[11] addb
hg rebase -Dr 5e819fbb0d27 -d 4e7cec6b4afe
@@ -166,6 +163,6 @@
|
o 0:c471ef929e6a@default(draft) addroot
- $ hg stabilize --any -v
- no unstable changeset
+ $ hg evolve --any -v
+ no troubled changeset
[1]
--- a/tests/test-stabilize-result.t Tue Aug 21 12:43:21 2012 +0200
+++ b/tests/test-stabilize-result.t Fri Aug 24 11:53:55 2012 +0200
@@ -5,7 +5,6 @@
> hgext.rebase=
> hgext.graphlog=
> EOF
- $ echo "obsolete=$(echo $(dirname $TESTDIR))/hgext/obsolete.py" >> $HGRCPATH
$ echo "evolve=$(echo $(dirname $TESTDIR))/hgext/evolve.py" >> $HGRCPATH
$ glog() {
@@ -13,7 +12,7 @@
> '{rev}:{node|short}@{branch}({phase}) bk:[{bookmarks}] {desc|firstline}\n' "$@"
> }
-Test stabilize removing the changeset being stabilized
+Test evolve removing the changeset being evolved
$ hg init empty
$ cd empty
@@ -28,8 +27,8 @@
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
$ echo a >> a
$ hg amend -m changea
- 1 new unstables changesets
- $ hg stabilize -v
+ 1 new unstable changesets
+ $ hg evolve -v
move:[2] changea
atop:[4] changea
hg rebase -Dr cce2c55b8965 -d 1447e1c4828d
@@ -37,15 +36,255 @@
$ glog --hidden
@ 4:1447e1c4828d@default(draft) bk:[changea] changea
|
- | o 3:41ad4fe8c795@default(secret) bk:[] amends 102a90ea7b4a3361e4082ed620918c261189a36a
+ | x 3:41ad4fe8c795@default(draft) bk:[] amends 102a90ea7b4a3361e4082ed620918c261189a36a
| |
- | | o 2:cce2c55b8965@default(secret) bk:[] changea
+ | | x 2:cce2c55b8965@default(draft) bk:[] changea
| |/
- | o 1:102a90ea7b4a@default(secret) bk:[] addb
+ | x 1:102a90ea7b4a@default(draft) bk:[] addb
|/
o 0:07f494440405@default(draft) bk:[] adda
- $ hg debugsuccessors
- 102a90ea7b4a 1447e1c4828d
- 41ad4fe8c795 1447e1c4828d
- cce2c55b8965 000000000000
+ $ hg debugobsolete
+ 41ad4fe8c79565a06c89f032ef0937b3cbd68a04 1447e1c4828d2347df8f858aa041305fa4cf7db1 0 {'date': '* *', 'user': 'test'} (glob)
+ 102a90ea7b4a3361e4082ed620918c261189a36a 1447e1c4828d2347df8f858aa041305fa4cf7db1 0 {'date': '* *', 'user': 'test'} (glob)
+ cce2c55b896511e0b6e04173c9450ba822ebc740 0 {'date': '* *', 'user': 'test'} (glob)
+
+Test evolve with conflict
+
+ $ ls
+ a
+ b
+ $ hg pdiff a
+ diff -r 07f494440405 a
+ --- a/a * (glob)
+ +++ b/a * (glob)
+ @@ -1,1 +1,2 @@
+ a
+ +a
+ $ echo 'newer a' >> a
+ $ hg ci -m 'newer a'
+ $ hg gdown
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ [4] changea
+ $ echo 'a' > a
+ $ hg amend
+ 1 new unstable changesets
+ $ hg evolve
+ move:[5] newer a
+ atop:[7] changea
+ merging a
+ warning: conflicts during merge.
+ merging a incomplete! (edit conflicts, then use 'hg resolve --mark')
+ evolve failed!
+ fix conflict and run "hg evolve --continue"
+ abort: unresolved merge conflicts (see hg help resolve)
+ [255]
+ $ hg revert -r 'unstable()' a
+ $ hg diff
+ diff -r e8cc1b534401 a
+ --- a/a * (glob)
+ +++ b/a * (glob)
+ @@ -1,1 +1,3 @@
+ a
+ +a
+ +newer a
+ $ hg evolve --continue
+ grafting revision 5
+ abort: unresolved merge conflicts (see hg help resolve)
+ [255]
+ $ hg resolve -m a
+ $ hg evolve --continue
+ grafting revision 5
+
+Stabilize of late comer with different parent
+==================================================
+
+(the with same parent is handled in test-evolve.t)
+
+ $ glog
+ @ 8:e3183e9c0961@default(draft) bk:[] newer a
+ |
+ o 7:e8cc1b534401@default(draft) bk:[changea] changea
+ |
+ o 0:07f494440405@default(draft) bk:[] adda
+
+Add another commit
+
+ $ hg gdown
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ [7] changea
+ $ echo 'c' > c
+ $ hg add c
+ $ hg commit -m 'add c'
+ created new head
+
+Get a successors of 8 on it
+
+ $ hg graft -O 8
+ grafting revision 8
+
+Add real change to the successors
+
+ $ echo 'babar' >> a
+ $ hg amend
+
+Make precursors public
+
+ $ hg phase --public 8
+ 1 new latecomer changesets
+ $ glog
+ @ 12:15c83af6f3a3@default(draft) bk:[] newer a
+ |
+ o 9:355c5cda4de1@default(draft) bk:[] add c
+ |
+ | o 8:e3183e9c0961@default(public) bk:[] newer a
+ |/
+ o 7:e8cc1b534401@default(public) bk:[changea] changea
+ |
+ o 0:07f494440405@default(public) bk:[] adda
+
+
+Stabilize !
+
+ $ hg evolve --any --dry-run
+ recreate:[12] newer a
+ atop:[8] newer a
+ hg rebase --rev 15c83af6f3a3 --detach e8cc1b534401;
+ hg update e3183e9c0961;
+ hg revert --all --rev 15c83af6f3a3;
+ hg commit --msg "latecomer update to %s" (no-eol)
+ $ hg evolve --any
+ recreate:[12] newer a
+ atop:[8] newer a
+ rebasing to destination parent: e8cc1b534401
+ computing new diff
+ commited as 1d94fef80e85
+ $ glog
+ @ 14:1d94fef80e85@default(draft) bk:[] latecomer update to e3183e9c0961:
+ |
+ | o 9:355c5cda4de1@default(draft) bk:[] add c
+ | |
+ o | 8:e3183e9c0961@default(public) bk:[] newer a
+ |/
+ o 7:e8cc1b534401@default(public) bk:[changea] changea
+ |
+ o 0:07f494440405@default(public) bk:[] adda
+
+
+Stabilize of conflicting changeset with same parent
+====================================================
+
+ $ rm a.orig
+ $ hg up 9
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ cat << EOF >> a
+ > flore
+ > arthur
+ > zephir
+ > some
+ > less
+ > conflict
+ > EOF
+ $ hg ci -m 'More addition'
+ created new head
+ $ glog
+ @ 15:7391601a4bfa@default(draft) bk:[] More addition
+ |
+ | o 14:1d94fef80e85@default(draft) bk:[] latecomer update to e3183e9c0961:
+ | |
+ o | 9:355c5cda4de1@default(draft) bk:[] add c
+ | |
+ | o 8:e3183e9c0961@default(public) bk:[] newer a
+ |/
+ o 7:e8cc1b534401@default(public) bk:[changea] changea
+ |
+ o 0:07f494440405@default(public) bk:[] adda
+
+ $ echo 'babar' >> a
+ $ hg amend
+ $ hg up 15
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ Working directory parent is obsolete
+ $ mv a a.old
+ $ echo 'jungle' > a
+ $ cat a.old >> a
+ $ rm a.old
+ $ hg amend
+ 2 new conflicting changesets
+ $ glog
+ @ 19:3883461cc228@default(draft) bk:[] More addition
+ |
+ | o 17:4754d61bc2db@default(draft) bk:[] More addition
+ |/
+ | o 14:1d94fef80e85@default(draft) bk:[] latecomer update to e3183e9c0961:
+ | |
+ o | 9:355c5cda4de1@default(draft) bk:[] add c
+ | |
+ | o 8:e3183e9c0961@default(public) bk:[] newer a
+ |/
+ o 7:e8cc1b534401@default(public) bk:[changea] changea
+ |
+ o 0:07f494440405@default(public) bk:[] adda
+
+
+Stabilize It
+
+ $ hg evolve -qn
+ hg update -c 3883461cc228 &&
+ hg merge 4754d61bc2db &&
+ hg commit -m "auto merge resolving conflict between 3883461cc228 and 4754d61bc2db"&&
+ hg up -C 7391601a4bfa &&
+ hg revert --all --rev tip &&
+ hg commit -m "`hg log -r 3883461cc228 --template={desc}`";
+ $ hg evolve -v
+ merge:[19] More addition
+ with: [17] More addition
+ base: [15] More addition
+ merging conflicting changeset
+ resolving manifests
+ merging a
+ 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
+ a
+ a
+ $ hg st
+ $ hg amend -d '0 0' -m 'More addition' # kill date variation XXX should be done in evolve
+ $ glog
+ @ 22:ac6d600735a4@default(draft) bk:[] More addition
+ |
+ | o 14:1d94fef80e85@default(draft) bk:[] latecomer update to e3183e9c0961:
+ | |
+ o | 9:355c5cda4de1@default(draft) bk:[] add c
+ | |
+ | o 8:e3183e9c0961@default(public) bk:[] newer a
+ |/
+ o 7:e8cc1b534401@default(public) bk:[changea] changea
+ |
+ o 0:07f494440405@default(public) bk:[] adda
+
+ $ hg summary
+ parent: 22:ac6d600735a4 tip
+ More addition
+ branch: default
+ commit: (clean)
+ update: 19 new changesets, 14 branch heads (merge)
+ $ hg export .
+ # HG changeset patch
+ # User test
+ # Date 0 0
+ # Node ID ac6d600735a49ee377e29d1f74a0576e8c972e7b
+ # Parent 355c5cda4de162658ed9f961a98a73a10b3167b1
+ More addition
+
+ diff -r 355c5cda4de1 -r ac6d600735a4 a
+ --- a/a Thu Jan 01 00:00:00 1970 +0000
+ +++ b/a Thu Jan 01 00:00:00 1970 +0000
+ @@ -1,1 +1,9 @@
+ +jungle
+ a
+ +flore
+ +arthur
+ +zephir
+ +some
+ +less
+ +conflict
+ +babar
--- a/tests/test-tutorial.t Tue Aug 21 12:43:21 2012 +0200
+++ b/tests/test-tutorial.t Fri Aug 24 11:53:55 2012 +0200
@@ -1,1 +1,772 @@
-../docs/tutorials/tutorial.t
\ No newline at end of file
+
+Initial setup
+-------------
+
+This Mercurial configuration example is used for testing.
+.. Various setup
+
+ $ cat >> $HGRCPATH << EOF
+ > [ui]
+ > logtemplate ="{node|short} ({phase}): {desc}\n"
+ > [diff]
+ > git = 1
+ > [alias]
+ > # "-d '0 0'" means that the new commit will be at January 1st 1970.
+ > # This is used for stable hash during test
+ > amend = amend -d '0 0'
+ > [extensions]
+ > hgext.graphlog=
+ > EOF
+
+ $ hg init local
+ $ cat >> local/.hg/hgrc << EOF
+ > [paths]
+ > remote = ../remote
+ > other = ../other
+ > [ui]
+ > user = Babar the King
+ > EOF
+
+ $ hg init remote
+ $ cat >> remote/.hg/hgrc << EOF
+ > [paths]
+ > local = ../local
+ > [ui]
+ > user = Celestine the Queen
+ > EOF
+
+ $ hg init other
+ $ cat >> other/.hg/hgrc << EOF
+ > [ui]
+ > user = Princess Flore
+ > EOF
+
+
+This tutorial use the following configuration for Mercurial:
+
+A compact log template with phase data:
+
+ $ hg showconfig ui
+ ui.slash=True
+ ui.logtemplate="{node|short} ({phase}): {desc}\n"
+
+Improved git format diff:
+
+ $ hg showconfig diff
+ diff.git=1
+
+And the graphlog extension
+ $ hg showconfig extensions
+ extensions.hgext.graphlog=
+
+And of course, we anabled the experimental extensions for mutable history:
+
+ $ $(dirname $TESTDIR)/enable.sh >> $HGRCPATH 2> /dev/null
+
+
+-----------------------
+Single Developer Usage
+-----------------------
+
+This tutorial shows how to use evolution to rewrite history locally.
+
+
+Fixing mistake with `hg amend`
+--------------------------------
+
+We are versionning a shopping list
+
+ $ cd local
+ $ cat >> shopping << EOF
+ > Spam
+ > Whizzo butter
+ > Albatross
+ > Rat (rather a lot)
+ > Jugged fish
+ > Blancmange
+ > Salmon mousse
+ > EOF
+ $ hg commit -A -m "Monthy Python Shopping list"
+ adding shopping
+
+Its first version is shared with the outside.
+
+ $ hg push remote
+ pushing to $TESTTMP/remote
+ searching for changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 1 changesets with 1 changes to 1 files
+
+Later I add additional item to my list
+
+ $ cat >> shopping << EOF
+ > Egg
+ > Suggar
+ > Vinegar
+ > Oil
+ > EOF
+ $ hg commit -m "adding condiment"
+ $ cat >> shopping << EOF
+ > Bananos
+ > Pear
+ > Apple
+ > EOF
+ $ hg commit -m "adding fruit"
+
+This history is very linear
+
+ $ hg glog
+ @ d85de4546133 (draft): adding fruit
+ |
+ o 4d5dc8187023 (draft): adding condiment
+ |
+ o 7e82d3f3c2cb (public): Monthy Python Shopping list
+
+
+But a typo was made in Babanas!
+
+ $ hg export tip
+ # HG changeset patch
+ # User test
+ # Date 0 0
+ # Node ID d85de4546133030c82d257bbcdd9b1b416d0c31c
+ # Parent 4d5dc81870237d492284826e21840b2ca00e26d1
+ adding fruit
+
+ diff --git a/shopping b/shopping
+ --- a/shopping
+ +++ b/shopping
+ @@ -9,3 +9,6 @@
+ Suggar
+ Vinegar
+ Oil
+ +Bananos
+ +Pear
+ +Apple
+
+The faulty changeset is in the "draft" phase because he was not exchanged with
+the outside. The first one have been exchanged and is an immutable public
+changeset.
+
+ $ hg glog
+ @ d85de4546133 (draft): adding fruit
+ |
+ o 4d5dc8187023 (draft): adding condiment
+ |
+ o 7e82d3f3c2cb (public): Monthy Python Shopping list
+
+
+hopefully. I can use hg amend to rewrite my faulty changeset!
+
+ $ sed -i'' -e s/Bananos/Banana/ shopping
+ $ hg diff
+ diff --git a/shopping b/shopping
+ --- a/shopping
+ +++ b/shopping
+ @@ -9,6 +9,6 @@
+ Suggar
+ Vinegar
+ Oil
+ -Bananos
+ +Banana
+ Pear
+ Apple
+ $ hg amend
+
+A new changeset with the right diff replace the wrong one.
+
+ $ hg glog
+ @ 0cacb48f4482 (draft): adding fruit
+ |
+ o 4d5dc8187023 (draft): adding condiment
+ |
+ o 7e82d3f3c2cb (public): Monthy Python Shopping list
+
+ $ hg export tip
+ # HG changeset patch
+ # User test
+ # Date 0 0
+ # Node ID 0cacb48f44828d2fd31c4e45e18fde32a5b2f07b
+ # Parent 4d5dc81870237d492284826e21840b2ca00e26d1
+ adding fruit
+
+ diff --git a/shopping b/shopping
+ --- a/shopping
+ +++ b/shopping
+ @@ -9,3 +9,6 @@
+ Suggar
+ Vinegar
+ Oil
+ +Banana
+ +Pear
+ +Apple
+
+Getting Ride of branchy history
+----------------------------------
+
+While I was working on my list. someone help made a change remotly.
+
+ $ cd ../remote
+ $ hg up -q
+ $ sed -i'' -e 's/Spam/Spam Spam Spam/' shopping
+ $ hg ci -m 'SPAM'
+ $ cd ../local
+
+I'll get this remote changeset when pulling
+
+ $ hg pull remote
+ pulling from $TESTTMP/remote
+ searching for changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 1 changesets with 1 changes to 1 files (+1 heads)
+ (run 'hg heads .' to see heads, 'hg merge' to merge)
+
+I now have a new heads. Note that this remote head is immutable
+
+ $ hg log -G
+ o 9ca060c80d74 (public): SPAM
+ |
+ | @ 0cacb48f4482 (draft): adding fruit
+ | |
+ | o 4d5dc8187023 (draft): adding condiment
+ |/
+ o 7e82d3f3c2cb (public): Monthy Python Shopping list
+
+
+instead of merging my head with the new one. I'm going to rebase my work
+
+ $ hg diff
+ $ hg rebase -d 9ca060c80d74 -s 4d5dc8187023
+ merging shopping
+ merging shopping
+
+
+My local work is now rebased on the remote one.
+
+ $ hg log -G
+ @ 387187ad9bd9 (draft): adding fruit
+ |
+ o dfd3a2d7691e (draft): adding condiment
+ |
+ o 9ca060c80d74 (public): SPAM
+ |
+ o 7e82d3f3c2cb (public): Monthy Python Shopping list
+
+
+Removing changeset
+------------------------
+
+I add new item to my list
+
+ $ cat >> shopping << EOF
+ > car
+ > bus
+ > plane
+ > boat
+ > EOF
+ $ hg ci -m 'transport'
+ $ hg log -G
+ @ d58c77aa15d7 (draft): transport
+ |
+ o 387187ad9bd9 (draft): adding fruit
+ |
+ o dfd3a2d7691e (draft): adding condiment
+ |
+ o 9ca060c80d74 (public): SPAM
+ |
+ o 7e82d3f3c2cb (public): Monthy Python Shopping list
+
+
+I have a new commit but I realize that don't want it. (transport shop list does
+not fit well in my standard shopping list)
+
+ $ hg prune . # . is for working directory parent
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ working directory now at 387187ad9bd9
+
+The silly changeset is gone.
+
+ $ hg log -G
+ @ 387187ad9bd9 (draft): adding fruit
+ |
+ o dfd3a2d7691e (draft): adding condiment
+ |
+ o 9ca060c80d74 (public): SPAM
+ |
+ o 7e82d3f3c2cb (public): Monthy Python Shopping list
+
+
+Reordering changeset
+------------------------
+
+
+We create two changesets.
+
+
+ $ cat >> shopping << EOF
+ > Shampoo
+ > Toothbrush
+ > ... More bathroom stuff to come
+ > Towel
+ > Soap
+ > EOF
+ $ hg ci -m 'bathroom stuff' -q # XXX remove the -q
+
+ $ sed -i'' -e 's/Spam/Spam Spam Spam/g' shopping
+ $ hg ci -m 'SPAM SPAM'
+ $ hg log -G
+ @ c48f32fb1787 (draft): SPAM SPAM
+ |
+ o 8d39a843582d (draft): bathroom stuff
+ |
+ o 387187ad9bd9 (draft): adding fruit
+ |
+ o dfd3a2d7691e (draft): adding condiment
+ |
+ o 9ca060c80d74 (public): SPAM
+ |
+ o 7e82d3f3c2cb (public): Monthy Python Shopping list
+
+
+.. note: don't amend changeset 7e82d3f3c2cb or 9ca060c80d74 as they are immutable.
+
+I now want to push to remote all my change but the bathroom one that i'm not
+totally happy with yet. To be able to push "SPAM SPAM" I need a version of "SPAM SPAM" not children of
+"bathroom stuff"
+
+You can use 'rebase -r' or 'graft -O' for that:
+
+ $ hg up 'p1(8d39a843582d)' # going on "bathroom stuff" parent
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ hg graft -O c48f32fb1787 # moving "SPAM SPAM" to the working directory parent
+ grafting revision 10
+ merging shopping
+ $ hg log -G
+ @ a2fccc2e7b08 (draft): SPAM SPAM
+ |
+ | o 8d39a843582d (draft): bathroom stuff
+ |/
+ o 387187ad9bd9 (draft): adding fruit
+ |
+ o dfd3a2d7691e (draft): adding condiment
+ |
+ o 9ca060c80d74 (public): SPAM
+ |
+ o 7e82d3f3c2cb (public): Monthy Python Shopping list
+
+
+We have a new SPAM SPAM version without the bathroom stuff
+
+ $ grep Spam shopping # enouth spam
+ Spam Spam Spam Spam Spam Spam Spam Spam Spam
+ $ grep Toothbrush shopping # no Toothbrush
+ [1]
+ $ hg export .
+ # HG changeset patch
+ # User test
+ # Date 0 0
+ # Node ID a2fccc2e7b08bbce6af7255b989453f7089e4cf0
+ # Parent 387187ad9bd9d8f9a00a9fa804a26231db547429
+ SPAM SPAM
+
+ diff --git a/shopping b/shopping
+ --- a/shopping
+ +++ b/shopping
+ @@ -1,4 +1,4 @@
+ -Spam Spam Spam
+ +Spam Spam Spam Spam Spam Spam Spam Spam Spam
+ Whizzo butter
+ Albatross
+ Rat (rather a lot)
+
+To make sure I do not push unready changeset by mistake I set the "bathroom
+stuff" changeset in the secret phase.
+
+ $ hg phase --force --secret 8d39a843582d
+
+we can now push our change:
+
+ $ hg push remote
+ pushing to $TESTTMP/remote
+ searching for changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 3 changesets with 3 changes to 1 files
+
+for simplicity shake we get the bathroom change in line again
+
+ $ hg rebase -Dr 8d39a843582d -d a2fccc2e7b08
+ merging shopping
+ $ hg phase --draft .
+ $ hg log -G
+ @ 8a79ae8b029e (draft): bathroom stuff
+ |
+ o a2fccc2e7b08 (public): SPAM SPAM
+ |
+ o 387187ad9bd9 (public): adding fruit
+ |
+ o dfd3a2d7691e (public): adding condiment
+ |
+ o 9ca060c80d74 (public): SPAM
+ |
+ o 7e82d3f3c2cb (public): Monthy Python Shopping list
+
+
+
+
+Splitting change
+------------------
+
+To be done (currently achieve with "two commit + debugobsolete")
+
+Collapsing change
+------------------
+
+To be done (currently achieve with "revert + debugobsolete" or "rebase --collapse")
+
+
+
+
+
+
+-----------------------
+Collaboration
+-----------------------
+
+
+sharing mutable changeset
+----------------------------
+
+To share mutable changeset with other just check that the repo you interact
+with is "not publishing". Otherwise you will get the previously observe
+behavior where exchanged changeset are automatically published.
+
+ $ cd ../remote
+ $ hg -R ../local/ showconfig phases
+
+the localrepo does not have any specific configuration for `phases.publish`. It
+is ``true`` by default.
+
+ $ hg pull local
+ pulling from $TESTTMP/local
+ searching for changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 1 changesets with 1 changes to 1 files
+ (run 'hg update' to get a working copy)
+ $ hg log -G
+ o 8a79ae8b029e (public): bathroom stuff
+ |
+ o a2fccc2e7b08 (public): SPAM SPAM
+ |
+ o 387187ad9bd9 (public): adding fruit
+ |
+ o dfd3a2d7691e (public): adding condiment
+ |
+ @ 9ca060c80d74 (public): SPAM
+ |
+ o 7e82d3f3c2cb (public): Monthy Python Shopping list
+
+
+
+
+We do not want to publish the "bathroom changeset". Let's rollback the last transaction
+
+ $ hg rollback
+ repository tip rolled back to revision 4 (undo pull)
+ $ hg log -G
+ o a2fccc2e7b08 (public): SPAM SPAM
+ |
+ o 387187ad9bd9 (public): adding fruit
+ |
+ o dfd3a2d7691e (public): adding condiment
+ |
+ @ 9ca060c80d74 (public): SPAM
+ |
+ o 7e82d3f3c2cb (public): Monthy Python Shopping list
+
+
+Let's make the local repo "non publishing"
+
+ $ echo '[phases]' >> ../local/.hg/hgrc
+ $ echo 'publish=false' >> ../local/.hg/hgrc
+ $ echo '[phases]' >> .hg/hgrc
+ $ echo 'publish=false' >> .hg/hgrc
+ $ hg showconfig phases
+ phases.publish=false
+ $ hg -R ../local/ showconfig phases
+ phases.publish=false
+
+
+I can now exchange mutable changeset between "remote" and "local" repository.
+
+ $ hg pull local
+ pulling from $TESTTMP/local
+ searching for changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 1 changesets with 1 changes to 1 files
+ (run 'hg update' to get a working copy)
+ $ hg log -G
+ o 8a79ae8b029e (draft): bathroom stuff
+ |
+ o a2fccc2e7b08 (public): SPAM SPAM
+ |
+ o 387187ad9bd9 (public): adding fruit
+ |
+ o dfd3a2d7691e (public): adding condiment
+ |
+ @ 9ca060c80d74 (public): SPAM
+ |
+ o 7e82d3f3c2cb (public): Monthy Python Shopping list
+
+
+Rebasing unstable change after pull
+----------------------------------------------
+
+Remotely someone add a new changeset on top of the mutable "bathroom" on.
+
+ $ hg up 8a79ae8b029e -q
+ $ cat >> shopping << EOF
+ > Giraffe
+ > Rhino
+ > Lion
+ > Bear
+ > EOF
+ $ hg ci -m 'animals'
+
+But at the same time, locally, this same "bathroom changeset" was updated.
+
+ $ cd ../local
+ $ hg up 8a79ae8b029e -q
+ $ sed -i'' -e 's/... More bathroom stuff to come/Bath Robe/' shopping
+ $ hg amend
+ $ hg log -G
+ @ ffa278c50818 (draft): bathroom stuff
+ |
+ o a2fccc2e7b08 (public): SPAM SPAM
+ |
+ o 387187ad9bd9 (public): adding fruit
+ |
+ o dfd3a2d7691e (public): adding condiment
+ |
+ o 9ca060c80d74 (public): SPAM
+ |
+ o 7e82d3f3c2cb (public): Monthy Python Shopping list
+
+
+
+When we pull from remote again we get an unstable state!
+
+
+ $ hg pull remote
+ pulling from $TESTTMP/remote
+ searching for changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 1 changesets with 1 changes to 1 files (+1 heads)
+ (run 'hg heads .' to see heads, 'hg merge' to merge)
+ 1 new unstable changesets
+
+
+The new changeset "animal" is based one an old changeset of "bathroom". You can
+see both version showing up in the log.
+
+ $ hg log -G
+ o 9ac5d0e790a2 (draft): animals
+ |
+ | @ ffa278c50818 (draft): bathroom stuff
+ | |
+ x | 8a79ae8b029e (draft): bathroom stuff
+ |/
+ o a2fccc2e7b08 (public): SPAM SPAM
+ |
+ o 387187ad9bd9 (public): adding fruit
+ |
+ o dfd3a2d7691e (public): adding condiment
+ |
+ o 9ca060c80d74 (public): SPAM
+ |
+ o 7e82d3f3c2cb (public): Monthy Python Shopping list
+
+
+The older version 8a79ae8b029e never ceased to exist in the local repo. It was
+jsut hidden and excluded from pull and push.
+
+.. note:: In hgview there is a nice doted relation highlighting ffa278c50818 as a new version of 8a79ae8b029e. this is not yet ported to graphlog.
+
+Their is **unstable** changeset in this history now. Mercurial will refuse to
+share it with the outside:
+
+ $ hg push other
+ pushing to $TESTTMP/other
+ searching for changes
+ abort: push includes an unstable changeset: 9ac5d0e790a2!
+ (use 'hg evolve' to get a stable history or --force to ignore warnings)
+ [255]
+
+
+
+
+To resolve this unstable state, you need to rebase 9ac5d0e790a2 onto
+ffa278c50818 the "hg evolve" command will make this for you.
+
+It has a --dry-run option to only suggest the next move.
+
+ $ hg evolve --dry-run
+ move:[15] animals
+ atop:[14] bathroom stuff
+ hg rebase -Dr 9ac5d0e790a2 -d ffa278c50818
+
+Let's do it
+
+ $ hg rebase -Dr 9ac5d0e790a2 -d ffa278c50818
+ merging shopping
+
+The old version of bathroom is hidden again.
+
+ $ hg log -G
+ @ 437efbcaf700 (draft): animals
+ |
+ o ffa278c50818 (draft): bathroom stuff
+ |
+ o a2fccc2e7b08 (public): SPAM SPAM
+ |
+ o 387187ad9bd9 (public): adding fruit
+ |
+ o dfd3a2d7691e (public): adding condiment
+ |
+ o 9ca060c80d74 (public): SPAM
+ |
+ o 7e82d3f3c2cb (public): Monthy Python Shopping list
+
+
+
+We can push this evolution to remote
+
+ $ hg push remote
+ pushing to $TESTTMP/remote
+ searching for changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 2 changesets with 2 changes to 1 files (+1 heads)
+
+remote get a warning that current working directory is based on an obsolete changeset
+
+ $ cd ../remote
+ $ hg pull local # we up again to trigger the warning. it was displayed during the push
+ pulling from $TESTTMP/local
+ searching for changes
+ no changes found
+ Working directory parent is obsolete
+
+ $ hg up 437efbcaf700
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+Relocating unstable change after prune
+----------------------------------------------
+
+The remote guy keep working
+
+ $ sed -i'' -e 's/Spam/Spam Spam Spam Spam/g' shopping
+ $ hg commit -m "SPAM SPAM SPAM"
+
+I'm pulling its work locally.
+
+ $ cd ../local
+ $ hg pull remote
+ pulling from $TESTTMP/remote
+ searching for changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 1 changesets with 1 changes to 1 files
+ (run 'hg update' to get a working copy)
+ $ hg log -G
+ o ae45c0c3092a (draft): SPAM SPAM SPAM
+ |
+ @ 437efbcaf700 (draft): animals
+ |
+ o ffa278c50818 (draft): bathroom stuff
+ |
+ o a2fccc2e7b08 (public): SPAM SPAM
+ |
+ o 387187ad9bd9 (public): adding fruit
+ |
+ o dfd3a2d7691e (public): adding condiment
+ |
+ o 9ca060c80d74 (public): SPAM
+ |
+ o 7e82d3f3c2cb (public): Monthy Python Shopping list
+
+
+In the mean time I noticed you can't buy animals in a super market and I prune the animal changeset:
+
+ $ hg prune 437efbcaf700
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ working directory now at ffa278c50818
+ 1 new unstable changesets
+
+
+The animals changeset is still displayed because the "SPAM SPAM SPAM" changeset
+is neither dead or obsolete. My repository is in an unstable state again.
+
+ $ hg log -G
+ o ae45c0c3092a (draft): SPAM SPAM SPAM
+ |
+ x 437efbcaf700 (draft): animals
+ |
+ @ ffa278c50818 (draft): bathroom stuff
+ |
+ o a2fccc2e7b08 (public): SPAM SPAM
+ |
+ o 387187ad9bd9 (public): adding fruit
+ |
+ o dfd3a2d7691e (public): adding condiment
+ |
+ o 9ca060c80d74 (public): SPAM
+ |
+ o 7e82d3f3c2cb (public): Monthy Python Shopping list
+
+
+ $ hg log -r 'unstable()'
+ ae45c0c3092a (draft): SPAM SPAM SPAM
+
+ $ hg evolve --any
+ move:[17] SPAM SPAM SPAM
+ atop:[14] bathroom stuff
+ merging shopping
+
+ $ hg log -G
+ @ d6717f710962 (draft): SPAM SPAM SPAM
+ |
+ o ffa278c50818 (draft): bathroom stuff
+ |
+ o a2fccc2e7b08 (public): SPAM SPAM
+ |
+ o 387187ad9bd9 (public): adding fruit
+ |
+ o dfd3a2d7691e (public): adding condiment
+ |
+ o 9ca060c80d74 (public): SPAM
+ |
+ o 7e82d3f3c2cb (public): Monthy Python Shopping list
+
+
+
+Handling Conflicting amend
+----------------------------------------------
+
+We can detect that multiple diverging//conflicting amend have been made. There
+will be a "evol-merge" command to merge conflicting amend
+
+This command is not ready yet.
--- a/tests/test-uncommit.t Tue Aug 21 12:43:21 2012 +0200
+++ b/tests/test-uncommit.t Fri Aug 24 11:53:55 2012 +0200
@@ -3,7 +3,6 @@
> hgext.rebase=
> hgext.graphlog=
> EOF
- $ echo "obsolete=$(echo $(dirname $TESTDIR))/hgext/obsolete.py" >> $HGRCPATH
$ echo "evolve=$(echo $(dirname $TESTDIR))/hgext/evolve.py" >> $HGRCPATH
$ glog() {
@@ -220,7 +219,7 @@
$ glog --hidden
@ 4:e8db4aa611f6@bar(stable/draft) touncommit
|
- | o 3:5eb72dbe0cb4@bar(extinct/secret) touncommit
+ | x 3:5eb72dbe0cb4@bar(extinct/draft) touncommit
|/
o 2:f63b90038565@default(stable/draft) merge
|\
@@ -232,8 +231,8 @@
* touncommit-bm 4:e8db4aa611f6
touncommit-bm-inactive 4:e8db4aa611f6
unrelated 2:f63b90038565
- $ hg debugsuccessors
- 5eb72dbe0cb4 e8db4aa611f6
+ $ hg debugobsolete
+ 5eb72dbe0cb409d094e3b4ae8eaa30071c1b8730 e8db4aa611f6d5706374288e6898e498f5c44098 0 {'date': '* *', 'user': 'test'} (glob)
Test phase is preserved, no local changes
@@ -242,7 +241,7 @@
Working directory parent is obsolete
$ hg --config extensions.purge= purge
$ hg uncommit -I 'set:added() and e'
- 2 new conflictings changesets
+ 2 new conflicting changesets
$ hg st --copies
A e
$ hg st --copies --change .
@@ -263,11 +262,11 @@
R m
R n
$ glog --hidden
- @ 5:c706fe2c12f8@bar(stable/secret) touncommit
+ @ 5:c706fe2c12f8@bar(stable/draft) touncommit
|
| o 4:e8db4aa611f6@bar(stable/draft) touncommit
|/
- | o 3:5eb72dbe0cb4@bar(extinct/secret) touncommit
+ | x 3:5eb72dbe0cb4@bar(extinct/draft) touncommit
|/
o 2:f63b90038565@default(stable/draft) merge
|\
@@ -275,9 +274,9 @@
|
o 0:07f494440405@default(stable/draft) adda
- $ hg debugsuccessors
- 5eb72dbe0cb4 c706fe2c12f8
- 5eb72dbe0cb4 e8db4aa611f6
+ $ hg debugobsolete
+ 5eb72dbe0cb409d094e3b4ae8eaa30071c1b8730 e8db4aa611f6d5706374288e6898e498f5c44098 0 {'date': '* *', 'user': 'test'} (glob)
+ 5eb72dbe0cb409d094e3b4ae8eaa30071c1b8730 c706fe2c12f83ba5010cb60ea6af3bd1f0c2d6d3 0 {'date': '* *', 'user': 'test'} (glob)
Test --all
@@ -286,7 +285,7 @@
Working directory parent is obsolete
$ hg --config extensions.purge= purge
$ hg uncommit --all -X e
- 1 new conflictings changesets
+ 1 new conflicting changesets
$ hg st --copies
M b
M d
@@ -308,21 +307,21 @@
$ hg st --copies --change .
A e
- $ hg debugsuccessors
- 5eb72dbe0cb4 c4cbebac3751
- 5eb72dbe0cb4 c706fe2c12f8
- 5eb72dbe0cb4 e8db4aa611f6
+ $ hg debugobsolete
+ 5eb72dbe0cb409d094e3b4ae8eaa30071c1b8730 e8db4aa611f6d5706374288e6898e498f5c44098 0 {'date': '* *', 'user': 'test'} (glob)
+ 5eb72dbe0cb409d094e3b4ae8eaa30071c1b8730 c706fe2c12f83ba5010cb60ea6af3bd1f0c2d6d3 0 {'date': '* *', 'user': 'test'} (glob)
+ 5eb72dbe0cb409d094e3b4ae8eaa30071c1b8730 c4cbebac3751269bdf12d1466deabcc78521d272 0 {'date': '* *', 'user': 'test'} (glob)
Display a warning if nothing left
$ hg uncommit e
new changeset is empty
(use "hg kill ." to remove it)
- $ hg debugsuccessors
- 5eb72dbe0cb4 c4cbebac3751
- 5eb72dbe0cb4 c706fe2c12f8
- 5eb72dbe0cb4 e8db4aa611f6
- c4cbebac3751 4f1c269eab68
+ $ hg debugobsolete
+ 5eb72dbe0cb409d094e3b4ae8eaa30071c1b8730 e8db4aa611f6d5706374288e6898e498f5c44098 0 {'date': '* *', 'user': 'test'} (glob)
+ 5eb72dbe0cb409d094e3b4ae8eaa30071c1b8730 c706fe2c12f83ba5010cb60ea6af3bd1f0c2d6d3 0 {'date': '* *', 'user': 'test'} (glob)
+ 5eb72dbe0cb409d094e3b4ae8eaa30071c1b8730 c4cbebac3751269bdf12d1466deabcc78521d272 0 {'date': '* *', 'user': 'test'} (glob)
+ c4cbebac3751269bdf12d1466deabcc78521d272 4f1c269eab68720f54e88ce3c1dc02b2858b6b89 0 {'date': '* *', 'user': 'test'} (glob)
Test instability warning
@@ -333,4 +332,4 @@
0 files updated, 0 files merged, 1 files removed, 0 files unresolved
[8] touncommit
$ hg uncommit aa
- 1 new unstables changesets
+ 1 new unstable changesets