changeset 403:c2ef47fce3f9

add nopushpublish extension.
author Pierre-Yves David <pierre-yves.david@logilab.fr>
date Mon, 30 Jul 2012 14:45:42 +0200
parents 4d63f8a00787 (diff) 8ef1096ee5ab (current diff)
children b5b1bf5166a2
files
diffstat 47 files changed, 8342 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.hgignore	Mon Jul 30 14:45:42 2012 +0200
@@ -0,0 +1,11 @@
+syntax: re
+/figures/[^/]+\.png$
+^docs/build/
+^docs/html/
+^html/
+\.pyc$
+~$
+\.swp$
+\.orig$
+\.rej$
+\.err$
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.hgtags	Mon Jul 30 14:45:42 2012 +0200
@@ -0,0 +1,6 @@
+6c6bb7a23bb5125bf06da73265f039dd3447dafa 0.1.0
+d3f20770b86a31dba56ae7b252089e12b34702da 0.2.0
+c046b083a5e0b21af69027f31cee141800cf894b 0.3.0
+9bbcd274689829d9239978236e16610688978233 0.4.0
+4ecbaec1d664b1e6f8ebc78292e1ced77a8e69c0 0.4.1
+7ef8ab8c6feadb8a9d9e13af144a17cb23e9a38d 0.5
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Makefile	Mon Jul 30 14:45:42 2012 +0200
@@ -0,0 +1,27 @@
+PYTHON=python
+HG=`which hg`
+
+help:
+	@echo 'Commonly used make targets:'
+	@echo '  tests              - run all tests in the automatic test suite'
+	@echo '  all-version-tests - run all tests against many hg versions'
+	@echo '  tests-%s           - run all tests in the specified hg version'
+
+all: help
+
+tests:
+	cd tests && $(PYTHON) run-tests.py --with-hg=$(HG) $(TESTFLAGS)
+
+test-%:
+	cd tests && $(PYTHON) run-tests.py --with-hg=$(HG) $(TESTFLAGS) $@
+
+tests-%:
+	@echo "Path to crew repo is $(CREW) - set this with CREW= if needed."
+	hg -R $(CREW) checkout $$(echo $@ | sed s/tests-//) && \
+	(cd $(CREW) ; $(MAKE) clean ) && \
+	cd tests && $(PYTHON) $(CREW)/tests/run-tests.py $(TESTFLAGS)
+
+all-version-tests: tests-1.3.1 tests-1.4.3 tests-1.5.4 \
+                   tests-1.6.4 tests-1.7.5 tests-1.8 tests-tip
+
+.PHONY: tests all-version-tests
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/README	Mon Jul 30 14:45:42 2012 +0200
@@ -0,0 +1,68 @@
+=============================
+Mutable History For Mercurial
+=============================
+
+:obsolete:
+
+    Introduce an ``obsolete`` concept that tracks new versions of rewritten
+    changesets.
+
+:evolve:
+
+    A collection of commands to rewrite the mutable part of the history.
+
+
+
+**These extensions are experimental and are not meant for production.**
+
+You can quicky enable them using::
+
+    ./enable.sh >> ~/.hgrc
+
+But it's recommended to look at the doc in the first place.
+
+See doc/ directory for details.
+
+Online version of the doc is available:
+
+    http://hg-lab.logilab.org/doc/mutable-history/html/
+
+Contribute
+==================
+
+The simplest way to contribute is to issue a pull request on bitbucket.
+
+However, some cutting edge change may be found in a mutable repository hosted
+by logilab before they are published.
+
+    http://hg-lab.logilab.org/wip/mutable-history/
+
+Make sure to check lastest draft changeset before submitting new changeset.
+
+
+Changelog
+==================
+
+0.3.0 --
+
+- obsolete: Add "latecomer" error detection (stabilize does not handle resolution yet)
+- evolve:    Introduce a new `uncommit` command to remove change from a changeset
+- rebase:    allow the use of --keep again
+- commit:    --amend option create obsolete marker (but still strip)
+- obsolete:  fewer marker are created when collapsing revision.
+- revset:    add, successors(), allsuccessors(), precursors(), allprecursors(),
+             latecomer() and hidden()
+- evolve:    add `prune` alias to `kill`.
+- stabilize: clearly state that stabilize does nto handle conflict
+- template:  add an {obsolete} keyword
+
+0.2.0 -- 2012-06-20
+
+- stabilize: improve choice of the next changeset to stabilize
+- stabilize: improve resolution of several corner case
+- rebase:    handle removing empty changesets
+- rebase:    handle --collapse
+- evolve:   add `obsolete` alias to `kill`
+- evolve:   add `evolve` alias to `stabilize`
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/README	Mon Jul 30 14:45:42 2012 +0200
@@ -0,0 +1,3 @@
+doc generated with sphinx. tutorial exported using sphinxedhg
+
+http://hg.piranha.org.ua/sphinxedhg/
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/conf.py	Mon Jul 30 14:45:42 2012 +0200
@@ -0,0 +1,124 @@
+# Add any Sphinx extension module names here, as strings. They can be extensions
+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = []
+#autoclass_content = 'both'
+# Add any paths that contain templates here, relative to this directory.
+#templates_path = []
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General substitutions.
+project = 'Obsolete experimentation'
+copyright = '2010-2011, pierre-yves.david@logilab.fr'
+
+# The default replacements for |version| and |release|, also used in various
+# other places throughout the built documents.
+#
+# The short X.Y version.
+version = '0.0'
+# The full version, including alpha/beta/rc tags.
+release = '0.0'
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+today_fmt = '%B %d, %Y'
+
+# List of documents that shouldn't be included in the build.
+unused_docs = []
+
+# List of directories, relative to source directories, that shouldn't be searched
+# for source files.
+#exclude_dirs = []
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+#default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+
+# Options for HTML output
+# -----------------------
+
+# The style sheet to use for HTML and HTML Help pages. A file of that name
+# must exist either in Sphinx' static/ path, or in one of the custom paths
+# given in html_static_path.
+#html_style = 'sphinx-default.css'
+
+# The name for this set of Sphinx documents.  If None, it defaults to
+# "<project> v<release> documentation".
+html_title = project
+html_theme = 'haiku'
+html_theme_path = ['.']
+
+# A shorter title for the navigation bar.  Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (within the static path) to place at the top of
+# the sidebar.
+#html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['.static']
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+html_use_modindex = False
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, the reST sources are included in the HTML build as _sources/<name>.
+#html_copy_source = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it.  The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = '.html'
+
+# Output file base name for HTML help builder.
+#htmlhelp_basename = ''
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/evolve-collaboration.rst	Mon Jul 30 14:45:42 2012 +0200
@@ -0,0 +1,133 @@
+
+------------------------------------------------
+Collaboration Using Evolve: A user story
+------------------------------------------------
+
+
+After having written some code for ticket #42, Alice starts a patch
+(this will be kind of like a 'work-in-progress' checkpoint
+initially)::
+
+    $ hg ci -m '[entities] remove magic'
+
+Instant patch ! Note how the default phase of this changeset is (still)
+in "draft" state.
+
+This is easily checkable::
+
+    $ hg phase tip
+    827: draft
+
+See? Until the day it becomes a "public" changeset, this can be
+altered to no end. How? It happens with an explicit::
+
+    $ hg phase --public
+
+In practice, pushing to a "publishing" repository can also turn draft
+changesets into public ones. Older Mercurial releases are automatically
+"publishing" since they do not have the notion of non-public changesets
+(or mutable history).
+
+During the transition from older mercurial servers to new ones, this will
+happen often, so be careful.
+
+Now let's come back to our patch. Next hour sees good progress and Alice
+wants to complete the patch with the recent stuff (all that's shown by
+an "hg diff") to share with a co-worker, Bob::
+
+    $ hg amend -m '[entities] fix frobulator (closes #42)'
+
+Note that we also fix the commit message. (For recovering mq users: this
+is just like "hg qrefresh -m").
+
+Before leaving, let's push to the central shared repository. That will
+give Bob the signal that something is ripe for review / further amendments::
+
+    $ hg push # was done with a modern mercurial, draft phase is preserved
+
+The next day, Bob, who arrives very early, can immediately work out
+some glitches in the patch.
+
+He then starts two others, for ticket #43 and #44 and finally commits them.
+Then, as original worker arrives, he pushes his stuff.
+
+Alice, now equipped with enough properly sugared coffee to survive the
+next two hours::
+
+    $ hg pull
+
+Then::
+
+    $ hg up "tip ~ 2"
+
+brings her to yesterday's patch. Indeed the patch serial number has
+increased (827 still exists but has been obsoleted).
+
+She understands that his original patch has been altered. But how did it
+evolve?
+
+The enhanced hgview shows the two patches. By default only the most
+recent version of a patch is shown.
+
+Now, when Alice installed the mutable-history extensions, she got an alias
+that allows her to see the diff between two amendments, defined like this::
+
+    odiff=diff --rev 'limit(obsparents(.),1)' --rev .
+
+She can see exactly how Bob amended her work.
+
+* odiff
+
+
+Amend ... Stabilize
+--------------------
+
+Almost perfect ! Alice just needs to fix a half dozen grammar oddities in
+the new docstrings and it will be publishable.
+
+Then, another round of:
+
+    $ hg amend
+
+and a quick look at hgview ... shows something strange (at first).
+
+Ticket #42 yesterday's version is still showing up, with two descendant lineages:
+
+* the next version, containing grammar fixes,
+
+* the two stacked changesets for tickets #43 .. 44 committed by Bob.
+
+Indeed, since this changeset still has non-obsolete descendant
+changesets it cannot be hidden. This branch (old version of #42 and
+the two descendants by C.W.) is said to be _unstable_.
+
+Why would one want such a state? Why not auto-stabilize each time "hg
+amend" is typed out?
+
+Alice for one, wouldn't want to merge each time she amends something that
+might conflict with the descendant changesets. Remember she is
+currently updating the very middle of an history!
+
+Being now done with grammar and typo fixes, Alice decides it is time to
+stabilize again the tree. She does::
+
+    $ hg stabilize
+
+two times, one for each unstable descendant. The last time, hgview
+shows her a straight line again. Wow! that feels a bit like a
+well-planned surgical operation. At the end, the patient tree has
+been properly trimmed and any conflict properly handled.
+
+Of course nothing fancy really happened: each "stablilize" can be
+understood in terms of a rebase of the next unstable descendant to the
+newest version of its parent (including the possible manual conflict
+resolution intermission ...).
+
+Except that rebase is a destructive (it removes information from the
+repository), unrecoverable operation, and the "evolve + obsolete"
+combo, using changeset copy and obsolescence marker, provide evolution
+semantics by only adding new information to the repository (but more
+on that later).
+
+She pushes again.
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/evolve-faq.rst	Mon Jul 30 14:45:42 2012 +0200
@@ -0,0 +1,236 @@
+
+---------------------------------------------------------------------
+Evolve How To
+---------------------------------------------------------------------
+
+
+
+Add a changeset: ``commit``
+------------------------------------------------------------
+
+Just use commit as usual. New changesets will be in the `draft` phase.
+
+Rewrite a changeset: ``amend``
+------------------------------------------------------------
+
+A new command ``hg amend`` is added by the extension. It writes a new
+changeset combining working-directory parent changes and parent. It
+will work on any `draft` or `secret` changeset. It will not work on
+`public` changesets.
+
+To understand what the result of amend will be I use the two following
+aliases  [#]_::
+
+    # diff what amend will look likes
+    pdiff=diff --rev .^
+
+    # status what amend will look likes
+    pstatus=status --rev .^
+
+It takes various options to pick an author, a date and the branch of the
+result... (see ``hg help amend`` for details).
+
+This command can even be invoked on changesets with children (provided
+none is public) !
+
+.. note:: the amend command is very similar to mq's ``qrefresh``, a ``refresh``
+          alias for amend is also available. But note that contrary to
+          ``qrefresh``, ``amend`` does not exclude changes on file not specified
+          on the command line.
+
+          XXX add idank example
+
+
+.. [#] (added by enable.sh)
+
+
+
+Move a changeset: ``graft``
+------------------------------------------------------------
+
+The graft command introduced in 2.0 allows to "copy changes from other
+branches onto the current branch".
+
+The graft command has been altered to allow the creation of an
+obsolete marker indicating both the result cset and its source
+(actually recording changeset movements).
+
+This is achieved using a new flag `-O` (or `old-obsolete`) [#]_.
+
+
+XXX example
+
+.. warning:: when using graft --continue after conflict resolution you **MUST**
+             pass `-O` or `-o` flag again because they are not saved for now
+
+
+.. [#] add this `-O` to graft instead of a dedicated command is probably
+       abusive. But this was very convenient for experimental purposes.
+       This will likely change in non experimental release.
+
+Delete a changeset: ``prune``
+------------------------------------------------------------
+
+A new ``prune`` command allows removing a changeset.
+
+Just use ``hg prune <some-rev>``.
+
+Moving within the history: ``up`` ``gdown`` and ``gup``
+------------------------------------------------------------
+
+While working on mutable part of the history you often need to move between
+mutable commit.
+
+You just need to use standard update to work with evolve. For convenience, you
+can use ``hg gup`` to move to children commit or ``hg gdown`` to move to working
+directory parent commit.
+
+.. note:: those command only exist for the convenience of getting qpush and qpop
+          feeling back.
+
+Collapse changesets: ``amend``
+------------------------------------------------------------
+
+you can use amend -c to collapse multiple changeset in a single one.
+
+Getting changes out of a commit
+------------------------------------------------------------
+
+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
+  M celestine
+  $ hg commit babar celestine
+  $ hg st
+  $ hg uncommit celestine
+  $ hg status
+  M celestine
+
+Split a changeset
+-----------------------
+
+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::
+
+  ### you want to split changeset A: 42
+  # update to A parent
+  $ hg up 42^
+  # restore content from A
+  $ hg revert -r 42 --all
+  # partially commit the first part
+  $ hg record
+  # commit the second part
+  $ hg commit
+  # informs mercurial of what appened
+  # current changeset (.) and previous one (.^) replace A (42)
+  $ hg prune --new . --new .^ 42
+
+
+Update my current work in progess after a pull
+----------------------------------------------
+
+Whenever you are working on some changesets, it is more likely that a pull 
+will, eventually, import new changesets in your tree.
+
+And it is likely that you will want your work in progress changsets to be 
+rebased on the top of this newly imported subtree.
+
+Doing so is only a matter of rebasing.
+
+
+
+Move multiple changesets: ``rebase``
+------------------------------------------------------------
+
+You can still use rebase to move a whole segment of the changeset graph together.
+
+.. warning:: Beware that rebasing obsolete changesets will result in
+             conflicting versions of the changesets.
+
+Stabilize history: ``stabilize``
+------------------------------------------------------------
+
+When you rewrite (amend) a changeset with children without rewriting
+those children you create *unstable* changesets and *suspended
+obsolete* changesets.
+
+When you are finished amending a given changeset, you will want to
+declare it stable, in other words rebase its former descendants on its
+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``
+------------------------------------------------------------
+
+Sometimes you need to create an obsolete marker by hand. This may happen when
+upstream has applied some of your patches for example.
+
+you can use ``hg prune --new <new-changeset> <old-changeset>`` to add obsolete
+marker.
+
+Export to mq: ``synchronize``
+------------------------------------------------------------
+
+Another extension allows to export your changes to mq.
+
+View diff from the last amend
+------------------------------------------------------------
+
+An ``odiff`` alias have been added by ``enable.sh``
+
+:: 
+    [alias]
+    odiff = diff --rev 'limit(precursors(.),1)' --rev .
+
+View obsolete markers
+------------------------------------------------------------
+
+hgview is the only viewer that support this feature. You need an experimental
+version available here:
+
+    $ hg clone http://hg-dev.octopoid.net/hgwebdir.cgi/hgview/
+
+You can also use a debug command
+
+    $ hg debugsuccessors
+      5eb72dbe0cb4 e8db4aa611f6
+      c4cbebac3751 4f1c269eab68
+
+
+
+Important Note
+=====================================================================
+
+View change to your file
+------------------------------------------------------------
+
+Extinct changesets are hidden using the *hidden* feature of mercurial.
+
+Only ``hg log``, ``hg glog`` and ``hgview`` support it, other
+graphical viewer do not.
+
+
+
+
+
+
+
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/evolve-good-practice.rst	Mon Jul 30 14:45:42 2012 +0200
@@ -0,0 +1,44 @@
+-----------------------------------------
+Good pratice for (early) user of evolve
+-----------------------------------------
+
+Avoid unstability
+--------------------------------
+
+The less unstability you have the less you need to resolve.
+
+Evolve is not yet able to detect and solve every situation. And your mind is
+not ready neither.
+
+Branch as much as possible
+--------------------------------
+
+This is not MQ, you are not constrainted to linear history.
+
+Making a branch per independent branch will help you avoid unstability
+and conflict.
+
+Rewrite you change only
+------------------------------------------------
+
+There is no descent conflict detection and handling right now.
+Rewriting other people's changesets guarantees that you will get
+conflicts. Communicate with your fellow developers before trying to
+touch other people's work (which is a good pratice in any case).
+
+Using multiple branch will help you to achieve this goal.
+
+Prefer pushing unstability than touching other people changeset
+------------------------------------------------------------------
+
+
+If you have children changesets from other people that you don't really care
+about, prefer not altering them to risking a conflict by stabilizing them.
+
+
+Do not get too confident
+---------------------------
+
+This is an experimental extension and a complex concept. This is beautiful,
+powerful and robust on paper, but the tool and your mind may not be prepared for
+all situations yet.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/figures/error-conflicting.svg	Mon Jul 30 14:45:42 2012 +0200
@@ -0,0 +1,3 @@
+<?xml version="1.0"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xl="http://www.w3.org/1999/xlink" version="1.1" viewBox="342 1890 349 427" width="349pt" height="427pt"><metadata xmlns:dc="http://purl.org/dc/elements/1.1/"><dc:date>2012-03-18 23:47Z</dc:date><!-- Produced by OmniGraffle Professional 5.3.6 --></metadata><defs><filter id="Shadow" filterUnits="userSpaceOnUse"><feGaussianBlur in="SourceAlpha" result="blur" stdDeviation="3.488"/><feOffset in="blur" result="offset" dx="0" dy="4"/><feFlood flood-color="black" flood-opacity=".75" result="flood"/><feComposite in="flood" in2="offset" operator="in"/></filter><font-face font-family="Helvetica" font-size="26" units-per-em="1000" underline-position="-75.683594" underline-thickness="49.31641" slope="0" x-height="522.94922" cap-height="717.2852" ascent="770.0196" descent="-229.98048" font-weight="500"><font-face-src><font-face-name name="Helvetica"/></font-face-src></font-face><marker orient="auto" overflow="visible" markerUnits="strokeWidth" id="FilledDiamond_Marker" viewBox="-1 -3 6 6" markerWidth="6" markerHeight="6" color="blue"><g><path d="M 3.7333333 0 L 1.8666667 -1.4 L 0 0 L 1.8666667 1.4 Z" fill="currentColor" stroke="currentColor" stroke-width="1"/></g></marker><font-face font-family="Helvetica" font-size="17" units-per-em="1000" underline-position="-75.683594" underline-thickness="49.316406" slope="0" x-height="532.22656" cap-height="719.72656" ascent="770.01953" descent="-229.98047" font-weight="bold"><font-face-src><font-face-name name="Helvetica-Bold"/></font-face-src></font-face></defs><g stroke="none" stroke-opacity="1" stroke-dasharray="none" fill="none" fill-opacity="1"><title>Canvas 1</title><g><title>Layer 1</title><g><use xl:href="#id465_Graphic" filter="url(#Shadow)"/><use xl:href="#id466_Graphic" filter="url(#Shadow)"/><use xl:href="#id472_Graphic" filter="url(#Shadow)"/><use xl:href="#id471_Graphic" filter="url(#Shadow)"/><use xl:href="#id475_Graphic" filter="url(#Shadow)"/><use xl:href="#id718_Graphic" filter="url(#Shadow)"/><use xl:href="#id722_Graphic" filter="url(#Shadow)"/></g><g id="id465_Graphic"><rect x="362.1148" y="1906.3591" width="308.04614" height="386.30615" fill="white" fill-opacity=".5"/><rect x="362.1148" y="1906.3591" width="308.04614" height="386.30615" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/></g><g id="id466_Graphic"><path d="M 460.24097 2058.0142 C 471.311 2069.0842 471.311 2087.032 460.24097 2098.102 C 449.171 2109.1721 431.22305 2109.1721 420.15308 2098.102 C 409.08304 2087.032 409.08304 2069.0842 420.15308 2058.0142 C 431.22305 2046.9441 449.171 2046.9441 460.24097 2058.0142" fill="yellow"/><path d="M 460.24097 2058.0142 C 471.311 2069.0842 471.311 2087.032 460.24097 2098.102 C 449.171 2109.1721 431.22305 2109.1721 420.15308 2098.102 C 409.08304 2087.032 409.08304 2069.0842 420.15308 2058.0142 C 431.22305 2046.9441 449.171 2046.9441 460.24097 2058.0142" stroke="red" stroke-linecap="round" stroke-linejoin="round" stroke-width="3"/><text transform="translate(422.51984 2062.5581)" fill="maroon"><tspan font-family="Helvetica" font-size="26" font-weight="500" fill="maroon" x="6.1180782" y="25" textLength="23.118164">A’</tspan></text></g><line x1="508.74466" y1="2219.2893" x2="453.23264" y2="2104.9158" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="3"/><g id="id472_Graphic"><path d="M 525.1753 2222.4712 C 530.71033 2228.0061 530.71033 2236.9802 525.1753 2242.5151 C 519.64032 2248.0503 510.66632 2248.0503 505.13132 2242.5151 C 499.5963 2236.9802 499.5963 2228.0061 505.13132 2222.4712 C 510.66632 2216.936 519.64032 2216.936 525.1753 2222.4712" fill="black"/><path d="M 525.1753 2222.4712 C 530.71033 2228.0061 530.71033 2236.9802 525.1753 2242.5151 C 519.64032 2248.0503 510.66632 2248.0503 505.13132 2242.5151 C 499.5963 2236.9802 499.5963 2228.0061 505.13132 2222.4712 C 510.66632 2216.936 519.64032 2216.936 525.1753 2222.4712" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/></g><g id="id471_Graphic"><path d="M 525.1753 2268.4714 C 530.71033 2274.0063 530.71033 2282.9805 525.1753 2288.5154 C 519.64032 2294.0505 510.66632 2294.0505 505.13132 2288.5154 C 499.5963 2282.9805 499.5963 2274.0063 505.13132 2268.4714 C 510.66632 2262.9363 519.64032 2262.9363 525.1753 2268.4714" fill="black"/><path d="M 525.1753 2268.4714 C 530.71033 2274.0063 530.71033 2282.9805 525.1753 2288.5154 C 519.64032 2294.0505 510.66632 2294.0505 505.13132 2288.5154 C 499.5963 2282.9805 499.5963 2274.0063 505.13132 2268.4714 C 510.66632 2262.9363 519.64032 2262.9363 525.1753 2268.4714" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/></g><line x1="515.15332" y1="2247.1665" x2="515.15332" y2="2263.8201" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><line x1="515.15332" y1="2217.8198" x2="515.15332" y2="2168.1504" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="3"/><g id="id475_Graphic"><path d="M 535.19727 2119.26 C 546.26727 2130.3301 546.26727 2148.2778 535.19727 2159.3479 C 524.12732 2170.418 506.17935 2170.418 495.10938 2159.3479 C 484.03934 2148.2778 484.03934 2130.3301 495.10938 2119.26 C 506.17935 2108.19 524.12732 2108.19 535.19727 2119.26" fill="yellow" fill-opacity=".5"/><path d="M 535.19727 2119.26 C 546.26727 2130.3301 546.26727 2148.2778 535.19727 2159.3479 C 524.12732 2170.418 506.17935 2170.418 495.10938 2159.3479 C 484.03934 2148.2778 484.03934 2130.3301 495.10938 2119.26 C 506.17935 2108.19 524.12732 2108.19 535.19727 2119.26" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="8,5"/><text transform="translate(497.47614 2123.804)" fill="black"><tspan font-family="Helvetica" font-size="26" font-weight="500" x="9.006262" y="25" textLength="17.341797">A</tspan></text></g><line x1="463.31088" y1="2096.9441" x2="482.01135" y2="2112.2239" marker-end="url(#FilledDiamond_Marker)" stroke="blue" stroke-linecap="round" stroke-linejoin="round" stroke-width="3" stroke-dasharray="1,4"/><g id="id718_Graphic"><path d="M 618.86163 2058.0142 C 629.93164 2069.0842 629.93164 2087.032 618.86163 2098.102 C 607.7917 2109.1721 589.8437 2109.1721 578.77374 2098.102 C 567.70374 2087.032 567.70374 2069.0842 578.77374 2058.0142 C 589.8437 2046.9441 607.7917 2046.9441 618.86163 2058.0142" fill="#ff6"/><path d="M 618.86163 2058.0142 C 629.93164 2069.0842 629.93164 2087.032 618.86163 2098.102 C 607.7917 2109.1721 589.8437 2109.1721 578.77374 2098.102 C 567.70374 2087.032 567.70374 2069.0842 578.77374 2058.0142 C 589.8437 2046.9441 607.7917 2046.9441 618.86163 2058.0142" stroke="red" stroke-linecap="round" stroke-linejoin="round" stroke-width="3"/><text transform="translate(581.14056 2062.5581)" fill="maroon"><tspan font-family="Helvetica" font-size="26" font-weight="500" fill="maroon" x="3.2298946" y="25" textLength="28.894531">A’’</tspan></text></g><line x1="522.14453" y1="2219.5881" x2="584.5973" y2="2104.3074" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="3"/><line x1="574.73132" y1="2095.6902" x2="549.68903" y2="2114.0222" marker-end="url(#FilledDiamond_Marker)" stroke="blue" stroke-linecap="round" stroke-linejoin="round" stroke-width="3" stroke-dasharray="1,4"/><line x1="470.04346" y1="2078.1025" x2="568.97186" y2="2078.2505" stroke="red" stroke-linecap="round" stroke-linejoin="round" stroke-width="7"/><g id="id722_Graphic"><path d="M 419.06274 2052.1411 C 459.63675 2034.4656 555.9978 2028.5737 616.85608 2041.7183 C 677.71497 2054.8606 641.8429 2119.8875 608.87634 2121.7026 C 575.90967 2123.5149 553.70416 2096.4143 553.70416 2096.4148 L 498.5315 2097.185 C 498.5315 2097.185 500.21146 2125.1155 452.02893 2119.2227 C 403.84808 2113.3301 378.49112 2069.8181 419.06274 2052.1411 Z" fill="red" fill-opacity=".15000001"/><path d="M 419.06274 2052.1411 C 459.63675 2034.4656 555.9978 2028.5737 616.85608 2041.7183 C 677.71497 2054.8606 641.8429 2119.8875 608.87634 2121.7026 C 575.90967 2123.5149 553.70416 2096.4143 553.70416 2096.4148 L 498.5315 2097.185 C 498.5315 2097.185 500.21146 2125.1155 452.02893 2119.2227 C 403.84808 2113.3301 378.49112 2069.8181 419.06274 2052.1411 Z" stroke="red" stroke-linecap="round" stroke-linejoin="round" stroke-width="4" stroke-dasharray="4,9,1,5"/></g><text transform="translate(481.40805 1999.724)" fill="red"><tspan font-family="Helvetica" font-size="17" font-weight="bold" fill="red" x=".11987305" y="16" textLength="43.429688">Confl</tspan><tspan font-family="Helvetica" font-size="17" font-weight="bold" fill="red" x="43.54956" y="16" textLength="45.330566">icting</tspan></text></g></g></svg>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/figures/error-extinct.svg	Mon Jul 30 14:45:42 2012 +0200
@@ -0,0 +1,3 @@
+<?xml version="1.0"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xl="http://www.w3.org/1999/xlink" version="1.1" viewBox="286 1306 644 435" width="644pt" height="435pt"><metadata xmlns:dc="http://purl.org/dc/elements/1.1/"><dc:date>2012-03-18 23:47Z</dc:date><!-- Produced by OmniGraffle Professional 5.3.6 --></metadata><defs><filter id="Shadow" filterUnits="userSpaceOnUse"><feGaussianBlur in="SourceAlpha" result="blur" stdDeviation="3.488"/><feOffset in="blur" result="offset" dx="0" dy="4"/><feFlood flood-color="black" flood-opacity=".75" result="flood"/><feComposite in="flood" in2="offset" operator="in"/></filter><font-face font-family="Helvetica" font-size="26" units-per-em="1000" underline-position="-75.683594" underline-thickness="49.31641" slope="0" x-height="522.94922" cap-height="717.2852" ascent="770.0196" descent="-229.98048" font-weight="500"><font-face-src><font-face-name name="Helvetica"/></font-face-src></font-face><marker orient="auto" overflow="visible" markerUnits="strokeWidth" id="FilledDiamond_Marker" viewBox="-1 -2 4 4" markerWidth="4" markerHeight="4" color="blue"><g><path d="M 1.6 0 L .8 -.60000002 L 0 0 L .8 .60000002 Z" fill="currentColor" stroke="currentColor" stroke-width="1"/></g></marker><font-face font-family="Helvetica" font-size="17" units-per-em="1000" underline-position="-75.683594" underline-thickness="49.316406" slope="0" x-height="532.22656" cap-height="719.72656" ascent="770.01953" descent="-229.98047" font-weight="bold"><font-face-src><font-face-name name="Helvetica-Bold"/></font-face-src></font-face></defs><g stroke="none" stroke-opacity="1" stroke-dasharray="none" fill="none" fill-opacity="1"><title>Canvas 1</title><g><title>Layer 1</title><g><use xl:href="#id581_Graphic" filter="url(#Shadow)"/><use xl:href="#id423_Graphic" filter="url(#Shadow)"/><use xl:href="#id422_Graphic" filter="url(#Shadow)"/><use xl:href="#id415_Graphic" filter="url(#Shadow)"/><use xl:href="#id567_Graphic" filter="url(#Shadow)"/><use xl:href="#id588_Graphic" filter="url(#Shadow)"/><use xl:href="#id565_Graphic" filter="url(#Shadow)"/><use xl:href="#id592_Graphic" filter="url(#Shadow)"/><use xl:href="#id595_Graphic" filter="url(#Shadow)"/><use xl:href="#id597_Graphic" filter="url(#Shadow)"/><use xl:href="#id599_Graphic" filter="url(#Shadow)"/><use xl:href="#id604_Graphic" filter="url(#Shadow)"/></g><g id="id581_Graphic"><rect x="538.9198" y="1322.60815" width="371" height="394" fill="white" fill-opacity=".5"/><rect x="538.9198" y="1322.60815" width="371" height="394" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/></g><g id="id423_Graphic"><rect x="306.04608" y="1379.2305" width="160" height="280.84644" fill="white" fill-opacity=".5"/><rect x="306.04608" y="1379.2305" width="160" height="280.84644" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/></g><g id="id422_Graphic"><circle cx="384.04633" cy="1522.2869" r="28.346533" fill="red"/><circle cx="384.04633" cy="1522.2869" r="28.346533" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(366.36917 1506.7869)" fill="black"><tspan font-family="Helvetica" font-size="26" font-weight="500" x="9.006262" y="25" textLength="17.341797">A</tspan></text></g><line x1="371.27045" y1="1496.4152" x2="353.8865" y2="1461.212" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="3"/><path d="M 394.06836 1589.8818 C 399.60336 1595.4169 399.60336 1604.3907 394.06836 1609.9258 C 388.53336 1615.4608 379.55936 1615.4608 374.02435 1609.9258 C 368.48935 1604.3907 368.48935 1595.4169 374.02435 1589.8818 C 379.55936 1584.3468 388.53336 1584.3468 394.06836 1589.8818" fill="black"/><path d="M 394.06836 1589.8818 C 399.60336 1595.4169 399.60336 1604.3907 394.06836 1609.9258 C 388.53336 1615.4608 379.55936 1615.4608 374.02435 1609.9258 C 368.48935 1604.3907 368.48935 1595.4169 374.02435 1589.8818 C 379.55936 1584.3468 388.53336 1584.3468 394.06836 1589.8818" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><path d="M 394.06836 1635.8817 C 399.60336 1641.4167 399.60336 1650.3906 394.06836 1655.9257 C 388.53336 1661.4607 379.55936 1661.4607 374.02435 1655.9257 C 368.48935 1650.3906 368.48935 1641.4167 374.02435 1635.8817 C 379.55936 1630.3467 388.53336 1630.3467 394.06836 1635.8817" fill="black"/><path d="M 394.06836 1635.8817 C 399.60336 1641.4167 399.60336 1650.3906 394.06836 1655.9257 C 388.53336 1661.4607 379.55936 1661.4607 374.02435 1655.9257 C 368.48935 1650.3906 368.48935 1641.4167 374.02435 1635.8817 C 379.55936 1630.3467 388.53336 1630.3467 394.06836 1635.8817" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><line x1="384.05234" y1="1614.577" x2="384.05911" y2="1631.2305" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><g id="id415_Graphic"><circle cx="341.11066" cy="1435.3403" r="28.346533" fill="yellow"/><circle cx="341.11066" cy="1435.3403" r="28.346533" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(323.43347 1419.8403)" fill="black"><tspan font-family="Helvetica" font-size="26" font-weight="500" x="9.006262" y="25" textLength="17.341797">B</tspan></text></g><line x1="384.04633" y1="1551.1333" x2="384.04636" y2="1585.2306" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="3"/><g id="id567_Graphic"><path d="M 649.09058 1405.5331 C 665.0907 1366.53296 703.09058 1353.5331 727.09058 1382.5331 C 751.0899 1411.5319 743.091 1543.5331 730.0907 1547.5327 C 717.09076 1551.5322 681.09106 1566.5331 662.09082 1553.533 C 643.0905 1540.5328 633.091 1444.5325 649.09058 1405.5331 Z" fill="blue" fill-opacity=".15000001"/><path d="M 649.09058 1405.5331 C 665.0907 1366.53296 703.09058 1353.5331 727.09058 1382.5331 C 751.0899 1411.5319 743.091 1543.5331 730.0907 1547.5327 C 717.09076 1551.5322 681.09106 1566.5331 662.09082 1553.533 C 643.0905 1540.5328 633.091 1444.5325 649.09058 1405.5331 Z" stroke="blue" stroke-linecap="round" stroke-linejoin="round" stroke-width="4" stroke-dasharray="4,9,1,5"/></g><line x1="670.96875" y1="1499.7141" x2="608.7143" y2="1440.1655" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="3"/><path d="M 763.18304 1609.1852 C 768.7181 1614.7202 768.7181 1623.6941 763.18304 1629.2291 C 757.64807 1634.7642 748.67407 1634.7642 743.1391 1629.2291 C 737.60406 1623.6941 737.60406 1614.7202 743.1391 1609.1852 C 748.67407 1603.6501 757.64807 1603.6501 763.18304 1609.1852" fill="black"/><path d="M 763.18304 1609.1852 C 768.7181 1614.7202 768.7181 1623.6941 763.18304 1629.2291 C 757.64807 1634.7642 748.67407 1634.7642 743.1391 1629.2291 C 737.60406 1623.6941 737.60406 1614.7202 743.1391 1609.1852 C 748.67407 1603.6501 757.64807 1603.6501 763.18304 1609.1852" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><path d="M 763.18304 1655.1852 C 768.7181 1660.7202 768.7181 1669.6941 763.18304 1675.2291 C 757.64807 1680.7642 748.67407 1680.7642 743.1391 1675.2291 C 737.60406 1669.6941 737.60406 1660.7202 743.1391 1655.1852 C 748.67407 1649.6501 757.64807 1649.6501 763.18304 1655.1852" fill="black"/><path d="M 763.18304 1655.1852 C 768.7181 1660.7202 768.7181 1669.6941 763.18304 1675.2291 C 757.64807 1680.7642 748.67407 1680.7642 743.1391 1675.2291 C 737.60406 1669.6941 737.60406 1660.7202 743.1391 1655.1852 C 748.67407 1649.6501 757.64807 1649.6501 763.18304 1655.1852" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><line x1="753.16107" y1="1633.8804" x2="753.16107" y2="1650.5339" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><g id="id588_Graphic"><circle cx="587.5074" cy="1419.88025" r="28.346518" fill="yellow"/><path d="M 607.55133 1399.8363 C 618.62134 1410.90625 618.62134 1428.85425 607.55133 1439.9242 C 596.4814 1450.9943 578.5334 1450.9943 567.46344 1439.9242 C 556.39343 1428.85425 556.39343 1410.90625 567.46344 1399.8363 C 578.5334 1388.7662 596.4814 1388.7662 607.55133 1399.8363" stroke="red" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" stroke-dasharray="16,9"/><text transform="translate(569.83026 1404.38025)" fill="red"><tspan font-family="Helvetica" font-size="26" font-weight="500" fill="red" x="9.006262" y="25" textLength="17.341797">B</tspan></text></g><g id="id565_Graphic"><circle cx="691.8144" cy="1519.6537" r="28.346518" fill="red" fill-opacity=".5"/><path d="M 711.85834 1499.6097 C 722.92834 1510.6797 722.92834 1528.6277 711.85834 1539.6976 C 700.7884 1550.7677 682.8404 1550.7677 671.77045 1539.6976 C 660.70044 1528.6277 660.70044 1510.6797 671.77045 1499.6097 C 682.8404 1488.5397 700.7884 1488.5397 711.85834 1499.6097" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="8,5"/><text transform="translate(674.13727 1504.1537)" fill="black"><tspan font-family="Helvetica" font-size="26" font-weight="500" x="9.006262" y="25" textLength="17.341797">A</tspan></text></g><line x1="706.95093" y1="1544.2173" x2="745.4616" y2="1606.7125" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="3"/><line x1="784.66077" y1="1519.52405" x2="730.5608" y2="1519.5996" marker-end="url(#FilledDiamond_Marker)" stroke="blue" stroke-linecap="round" stroke-linejoin="round" stroke-width="4" stroke-dasharray="1,3"/><g id="id592_Graphic"><circle cx="813.5072" cy="1519.4838" r="28.346518" fill="red"/><circle cx="813.5072" cy="1519.4838" r="28.346518" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(795.8301 1503.9838)" fill="black"><tspan font-family="Helvetica" font-size="26" font-weight="500" x="6.5243282" y="25" textLength="22.305664">A'</tspan></text></g><line x1="798.56934" y1="1544.1689" x2="760.75946" y2="1606.6506" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="3"/><g id="id595_Graphic"><circle cx="426.39252" cy="1435.3403" r="28.346533" fill="#ff8000"/><circle cx="426.39252" cy="1435.3403" r="28.346533" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(408.71533 1419.8403)" fill="black"><tspan font-family="Helvetica" font-size="26" font-weight="500" x="8.2889767" y="25" textLength="18.776367">C</tspan></text></g><line x1="396.6806" y1="1496.3458" x2="413.75824" y2="1461.2814" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="3"/><g id="id597_Graphic"><circle cx="817.5072" cy="1419.88025" r="28.346518" fill="#ff8000"/><circle cx="817.5072" cy="1419.88025" r="28.346518" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(799.8301 1404.38025)" fill="black"><tspan font-family="Helvetica" font-size="26" font-weight="500" x="5.807043" y="25" textLength="23.740234">C'</tspan></text></g><line x1="816.34967" y1="1448.7037" x2="814.66473" y2="1490.6603" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="3"/><g id="id599_Graphic"><circle cx="691.8144" cy="1419.88025" r="28.346518" fill="#ff8000" fill-opacity=".5"/><path d="M 711.85834 1399.8363 C 722.92834 1410.90625 722.92834 1428.85425 711.85834 1439.9242 C 700.7884 1450.9943 682.8404 1450.9943 671.77045 1439.9242 C 660.70044 1428.85425 660.70044 1410.90625 671.77045 1399.8363 C 682.8404 1388.7662 700.7884 1388.7662 711.85834 1399.8363" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="8,5"/><text transform="translate(674.13727 1404.38025)" fill="black"><tspan font-family="Helvetica" font-size="26" font-weight="500" x="8.2889767" y="25" textLength="18.776367">C</tspan></text></g><line x1="691.8196" y1="1448.7267" x2="691.82715" y2="1490.80725" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="3"/><line x1="788.66077" y1="1419.88025" x2="730.56085" y2="1419.88025" marker-end="url(#FilledDiamond_Marker)" stroke="blue" stroke-linecap="round" stroke-linejoin="round" stroke-width="4" stroke-dasharray="1,3"/><text transform="translate(557.16095 1491.30725)" fill="blue"><tspan font-family="Helvetica" font-size="17" font-weight="bold" fill="blue" x=".13012695" y="16" textLength="72.739746">Obsolete</tspan></text><g id="id604_Graphic"><path d="M 548.05884 1393.9442 C 562.8297 1376.26746 597.9101 1370.3754 620.06622 1383.5195 C 642.22168 1396.6632 634.83734 1456.4924 622.8358 1458.3053 C 610.83466 1460.118 577.60083 1466.9171 560.06024 1461.0248 C 542.51978 1455.1326 533.28845 1411.6206 548.05884 1393.9442 Z" fill="#ff8000" fill-opacity=".15000001"/><path d="M 548.05884 1393.9442 C 562.8297 1376.26746 597.9101 1370.3754 620.06622 1383.5195 C 642.22168 1396.6632 634.83734 1456.4924 622.8358 1458.3053 C 610.83466 1460.118 577.60083 1466.9171 560.06024 1461.0248 C 542.51978 1455.1326 533.28845 1411.6206 548.05884 1393.9442 Z" stroke="#ff8000" stroke-linecap="round" stroke-linejoin="round" stroke-width="4" stroke-dasharray="4,9,1,5"/></g><text transform="translate(551.5074 1339.08044)" fill="#ff8000"><tspan font-family="Helvetica" font-size="17" font-weight="bold" fill="#ff8000" x=".103271484" y="16" textLength="71.793457">Unstable</tspan></text><line x1="631.31445" y1="1469.267" x2="752.31433" y2="1469.267" stroke="red" stroke-linecap="round" stroke-linejoin="round" stroke-width="3" stroke-dasharray="4,3,1,3"/><text transform="translate(712.3343 1373.30725)" fill="maroon"><tspan font-family="Helvetica" font-size="17" font-weight="bold" fill="maroon" x=".103271484" y="16" textLength="54.793457">extinct</tspan></text><text transform="translate(580.1609 1539.234)" fill="#008040"><tspan font-family="Helvetica" font-size="17" font-weight="bold" fill="#008040" x=".13012695" y="16" textLength="89.739746">suspended</tspan></text></g></g></svg>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/figures/error-obsolete.svg	Mon Jul 30 14:45:42 2012 +0200
@@ -0,0 +1,3 @@
+<?xml version="1.0"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xl="http://www.w3.org/1999/xlink" version="1.1" viewBox="125 130 278 413" width="278pt" height="413pt"><metadata xmlns:dc="http://purl.org/dc/elements/1.1/"><dc:date>2012-03-18 23:47Z</dc:date><!-- Produced by OmniGraffle Professional 5.3.6 --></metadata><defs><filter id="Shadow" filterUnits="userSpaceOnUse"><feGaussianBlur in="SourceAlpha" result="blur" stdDeviation="3.488"/><feOffset in="blur" result="offset" dx="0" dy="4"/><feFlood flood-color="black" flood-opacity=".75" result="flood"/><feComposite in="flood" in2="offset" operator="in"/></filter><font-face font-family="Helvetica" font-size="26" units-per-em="1000" underline-position="-75.683594" underline-thickness="49.31641" slope="0" x-height="522.94922" cap-height="717.2852" ascent="770.0196" descent="-229.98048" font-weight="500"><font-face-src><font-face-name name="Helvetica"/></font-face-src></font-face><marker orient="auto" overflow="visible" markerUnits="strokeWidth" id="FilledDiamond_Marker" viewBox="-1 -2 4 4" markerWidth="4" markerHeight="4" color="blue"><g><path d="M 1.6 0 L .8 -.60000002 L 0 0 L .8 .60000002 Z" fill="currentColor" stroke="currentColor" stroke-width="1"/></g></marker><font-face font-family="Helvetica" font-size="17" units-per-em="1000" underline-position="-75.683594" underline-thickness="49.316406" slope="0" x-height="532.22656" cap-height="719.72656" ascent="770.01953" descent="-229.98047" font-weight="bold"><font-face-src><font-face-name name="Helvetica-Bold"/></font-face-src></font-face></defs><g stroke="none" stroke-opacity="1" stroke-dasharray="none" fill="none" fill-opacity="1"><title>Canvas 1</title><g><title>Layer 1</title><g><use xl:href="#id411_Graphic" filter="url(#Shadow)"/><use xl:href="#id410_Graphic" filter="url(#Shadow)"/><use xl:href="#id408_Graphic" filter="url(#Shadow)"/><use xl:href="#id407_Graphic" filter="url(#Shadow)"/><use xl:href="#id395_Graphic" filter="url(#Shadow)"/><use xl:href="#id394_Graphic" filter="url(#Shadow)"/><use xl:href="#id391_Graphic" filter="url(#Shadow)"/><use xl:href="#id390_Graphic" filter="url(#Shadow)"/><use xl:href="#id724_Graphic" filter="url(#Shadow)"/></g><g id="id411_Graphic"><rect x="145" y="146" width="238" height="373" fill="white" fill-opacity=".5"/><rect x="145" y="146" width="238" height="373" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/></g><g id="id410_Graphic"><circle cx="290.34644" cy="351.50146" r="28.34651" fill="red"/><circle cx="290.34644" cy="351.50146" r="28.34651" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(272.66928 336.0015)" fill="black"><tspan font-family="Helvetica" font-size="26" font-weight="500" x="9.006262" y="25" textLength="17.341797">A</tspan></text></g><line x1="216.72533" y1="434.68497" x2="271.22772" y2="373.10342" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="3"/><g id="id408_Graphic"><circle cx="207.00024" cy="445.67322" r="14.173263" fill="black"/><circle cx="207.00024" cy="445.67322" r="14.173263" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/></g><g id="id407_Graphic"><circle cx="206.30316" cy="488.67325" r="14.173263" fill="black"/><circle cx="206.30316" cy="488.67325" r="14.173263" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/></g><line x1="206.7624" y1="460.34457" x2="206.541" y2="474.0019" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><g id="id395_Graphic"><circle cx="206.30315" cy="266.11322" r="24.803194" fill="#ff8000" fill-opacity=".5"/><path d="M 223.8416 248.57477 C 233.52788 258.26099 233.52788 273.96545 223.8416 283.65167 C 214.15538 293.33795 198.45091 293.33795 188.76469 283.65167 C 179.07841 273.96545 179.07841 258.26099 188.76469 248.57477 C 198.45091 238.88849 214.15538 238.88849 223.8416 248.57477" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="8,5"/><text transform="translate(191.46063 250.61322)" fill="black"><tspan font-family="Helvetica" font-size="26" font-weight="500" x="5.4543362" y="25" textLength="18.776367">C</tspan></text></g><g id="id394_Graphic"><circle cx="206.30315" cy="351.50146" r="24.803194" fill="yellow" fill-opacity=".5"/><path d="M 223.8416 333.96301 C 233.52788 343.64923 233.52788 359.3537 223.8416 369.03992 C 214.15538 378.7262 198.45091 378.7262 188.76469 369.03992 C 179.07841 359.3537 179.07841 343.64923 188.76469 333.96301 C 198.45091 324.27673 214.15538 324.27673 223.8416 333.96301" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="8,5"/><text transform="translate(191.46063 336.00146)" fill="black"><tspan font-family="Helvetica" font-size="26" font-weight="500" x="6.1716213" y="25" textLength="17.341797">B</tspan></text></g><line x1="206.89162" y1="431.00034" x2="206.49045" y2="376.80396" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><line x1="206.30315" y1="326.1983" x2="206.30315" y2="291.41638" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><g id="id391_Graphic"><circle cx="290.34644" cy="269.6565" r="28.346504" fill="yellow"/><circle cx="290.34644" cy="269.6565" r="28.346504" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(272.66928 254.15652)" fill="black"><tspan font-family="Helvetica" font-size="26" font-weight="500" x="6.1180782" y="25" textLength="23.118164">B’</tspan></text></g><g id="id390_Graphic"><circle cx="290.34644" cy="187.80643" r="28.3465" fill="#ff8000"/><circle cx="290.34644" cy="187.80643" r="28.3465" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(272.66928 172.30643)" fill="black"><tspan font-family="Helvetica" font-size="26" font-weight="500" x="5.400793" y="25" textLength="24.552734">C’</tspan></text></g><line x1="290.34644" y1="322.655" x2="290.34644" y2="298.50296" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="3"/><line x1="290.3464" y1="240.81003" x2="290.3464" y2="216.65289" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="3"/><line x1="269.24112" y1="207.47118" x2="232.0592" y2="242.11519" marker-end="url(#FilledDiamond_Marker)" stroke="blue" stroke-linecap="round" stroke-linejoin="round" stroke-width="4" stroke-dasharray="1,3"/><line x1="269.68039" y1="289.78198" x2="231.52318" y2="326.9411" marker-end="url(#FilledDiamond_Marker)" stroke="blue" stroke-linecap="round" stroke-linejoin="round" stroke-width="4" stroke-dasharray="1,3"/><text transform="translate(154.02574 177.80643)" fill="blue"><tspan font-family="Helvetica" font-size="17" font-weight="bold" fill="blue" x=".13012695" y="16" textLength="72.739746">Obsolete</tspan></text><g id="id724_Graphic"><path d="M 167.93994 250.66768 C 182.56549 215.87003 217.30066 204.27097 239.23875 230.14594 C 261.17621 256.02005 253.8645 373.79715 241.98106 377.36597 C 230.09801 380.93442 197.19122 394.31873 179.82327 382.71951 C 162.45541 371.1203 153.31494 285.46457 167.93994 250.66768 Z" fill="blue" fill-opacity=".15000001"/><path d="M 167.93994 250.66768 C 182.56549 215.87003 217.30066 204.27097 239.23875 230.14594 C 261.17621 256.02005 253.8645 373.79715 241.98106 377.36597 C 230.09801 380.93442 197.19122 394.31873 179.82327 382.71951 C 162.45541 371.1203 153.31494 285.46457 167.93994 250.66768 Z" stroke="blue" stroke-linecap="round" stroke-linejoin="round" stroke-width="4" stroke-dasharray="4,9,1,5"/></g></g></g></svg>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/figures/error-unstable.svg	Mon Jul 30 14:45:42 2012 +0200
@@ -0,0 +1,3 @@
+<?xml version="1.0"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xl="http://www.w3.org/1999/xlink" version="1.1" viewBox="305 746 658 435" width="658pt" height="435pt"><metadata xmlns:dc="http://purl.org/dc/elements/1.1/"><dc:date>2012-03-18 23:47Z</dc:date><!-- Produced by OmniGraffle Professional 5.3.6 --></metadata><defs><filter id="Shadow" filterUnits="userSpaceOnUse"><feGaussianBlur in="SourceAlpha" result="blur" stdDeviation="3.488"/><feOffset in="blur" result="offset" dx="0" dy="4"/><feFlood flood-color="black" flood-opacity=".75" result="flood"/><feComposite in="flood" in2="offset" operator="in"/></filter><font-face font-family="Helvetica" font-size="26" units-per-em="1000" underline-position="-75.683594" underline-thickness="49.31641" slope="0" x-height="522.94922" cap-height="717.2852" ascent="770.0196" descent="-229.98048" font-weight="500"><font-face-src><font-face-name name="Helvetica"/></font-face-src></font-face><marker orient="auto" overflow="visible" markerUnits="strokeWidth" id="FilledDiamond_Marker" viewBox="-1 -2 4 4" markerWidth="4" markerHeight="4" color="blue"><g><path d="M 1.6 0 L .8 -.60000002 L 0 0 L .8 .60000002 Z" fill="currentColor" stroke="currentColor" stroke-width="1"/></g></marker><font-face font-family="Helvetica" font-size="17" units-per-em="1000" underline-position="-75.683594" underline-thickness="49.316406" slope="0" x-height="532.22656" cap-height="719.72656" ascent="770.01953" descent="-229.98047" font-weight="bold"><font-face-src><font-face-name name="Helvetica-Bold"/></font-face-src></font-face></defs><g stroke="none" stroke-opacity="1" stroke-dasharray="none" fill="none" fill-opacity="1"><title>Canvas 1</title><g><title>Layer 1</title><g><use xl:href="#id672_Graphic" filter="url(#Shadow)"/><use xl:href="#id673_Graphic" filter="url(#Shadow)"/><use xl:href="#id674_Graphic" filter="url(#Shadow)"/><use xl:href="#id680_Graphic" filter="url(#Shadow)"/><use xl:href="#id689_Graphic" filter="url(#Shadow)"/><use xl:href="#id692_Graphic" filter="url(#Shadow)"/><use xl:href="#id702_Graphic" filter="url(#Shadow)"/><use xl:href="#id714_Graphic" filter="url(#Shadow)"/><use xl:href="#id688_Graphic" filter="url(#Shadow)"/></g><g id="id672_Graphic"><rect x="571.2267" y="762.23743" width="371" height="394" fill="white" fill-opacity=".5"/><rect x="571.2267" y="762.23743" width="371" height="394" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/></g><g id="id673_Graphic"><rect x="325.28735" y="817.17517" width="160" height="280.84595" fill="white" fill-opacity=".5"/><rect x="325.28735" y="817.17517" width="160" height="280.84595" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/></g><g id="id674_Graphic"><circle cx="403.28784" cy="960.2316" r="28.346495" fill="red"/><circle cx="403.28784" cy="960.2316" r="28.346495" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(385.61066 944.73157)" fill="black"><tspan font-family="Helvetica" font-size="26" font-weight="500" x="9.006262" y="25" textLength="17.341797">A</tspan></text></g><line x1="390.51233" y1="934.3598" x2="373.12732" y2="899.1533" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="3"/><path d="M 413.30957 1027.8263 C 418.84457 1033.3613 418.84457 1042.3352 413.30957 1047.87024 C 407.77457 1053.4053 398.80057 1053.4053 393.26556 1047.87024 C 387.73056 1042.3352 387.73056 1033.3613 393.26556 1027.8263 C 398.80057 1022.29126 407.77457 1022.29126 413.30957 1027.8263" fill="black"/><path d="M 413.30957 1027.8263 C 418.84457 1033.3613 418.84457 1042.3352 413.30957 1047.87024 C 407.77457 1053.4053 398.80057 1053.4053 393.26556 1047.87024 C 387.73056 1042.3352 387.73056 1033.3613 393.26556 1027.8263 C 398.80057 1022.29126 407.77457 1022.29126 413.30957 1027.8263" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><path d="M 413.30957 1073.8263 C 418.84457 1079.3613 418.84457 1088.3352 413.30957 1093.87024 C 407.77457 1099.4053 398.80057 1099.4053 393.26556 1093.87024 C 387.73056 1088.3352 387.73056 1079.3613 393.26556 1073.8263 C 398.80057 1068.29126 407.77457 1068.29126 413.30957 1073.8263" fill="black"/><path d="M 413.30957 1073.8263 C 418.84457 1079.3613 418.84457 1088.3352 413.30957 1093.87024 C 407.77457 1099.4053 398.80057 1099.4053 393.26556 1093.87024 C 387.73056 1088.3352 387.73056 1079.3613 393.26556 1073.8263 C 398.80057 1068.29126 407.77457 1068.29126 413.30957 1073.8263" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><line x1="403.1737" y1="1052.5211" x2="403.04446" y2="1069.1771" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><g id="id680_Graphic"><circle cx="360.3518" cy="873.2816" r="28.346495" fill="yellow"/><circle cx="360.3518" cy="873.2816" r="28.346495" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(342.67462 857.7816)" fill="black"><tspan font-family="Helvetica" font-size="26" font-weight="500" x="9.006262" y="25" textLength="17.341797">B</tspan></text></g><line x1="403.28772" y1="989.078" x2="403.2876" y2="1023.17505" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="3"/><line x1="718.2204" y1="930.3955" x2="718.53784" y2="874.86414" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="3"/><path d="M 789.4242 1048.76624 C 794.95923 1054.3013 794.95923 1063.27515 789.4242 1068.8102 C 783.8892 1074.3452 774.9152 1074.3452 769.38025 1068.8102 C 763.8452 1063.27515 763.8452 1054.3013 769.38025 1048.76624 C 774.9152 1043.2312 783.8892 1043.2312 789.4242 1048.76624" fill="black"/><path d="M 789.4242 1048.76624 C 794.95923 1054.3013 794.95923 1063.27515 789.4242 1068.8102 C 783.8892 1074.3452 774.9152 1074.3452 769.38025 1068.8102 C 763.8452 1063.27515 763.8452 1054.3013 769.38025 1048.76624 C 774.9152 1043.2312 783.8892 1043.2312 789.4242 1048.76624" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><path d="M 789.4242 1094.7661 C 794.95923 1100.30115 794.95923 1109.275 789.4242 1114.81006 C 783.8892 1120.3451 774.9152 1120.3451 769.38025 1114.81006 C 763.8452 1109.275 763.8452 1100.30115 769.38025 1094.7661 C 774.9152 1089.2311 783.8892 1089.2311 789.4242 1094.7661" fill="black"/><path d="M 789.4242 1094.7661 C 794.95923 1100.30115 794.95923 1109.275 789.4242 1114.81006 C 783.8892 1120.3451 774.9152 1120.3451 769.38025 1114.81006 C 763.8452 1109.275 763.8452 1100.30115 769.38025 1094.7661 C 774.9152 1089.2311 783.8892 1089.2311 789.4242 1094.7661" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><line x1="779.4223" y1="1073.4614" x2="779.44507" y2="1090.1149" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><g id="id689_Graphic"><circle cx="718.0555" cy="959.24146" r="28.34648" fill="red" fill-opacity=".5"/><path d="M 738.09943 939.19757 C 749.16943 950.2675 749.16943 968.2155 738.09943 979.28546 C 727.0295 990.35547 709.0815 990.35547 698.01154 979.28546 C 686.94153 968.2155 686.94153 950.2675 698.01154 939.19757 C 709.0815 928.12756 727.0295 928.12756 738.09943 939.19757" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="8,5"/><text transform="translate(700.37836 943.7415)" fill="black"><tspan font-family="Helvetica" font-size="26" font-weight="500" x="9.006262" y="25" textLength="17.341797">A</tspan></text></g><line x1="733.1928" y1="983.8047" x2="771.7024" y2="1046.2938" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="3"/><line x1="800.166" y1="959.2338" x2="756.80194" y2="959.2374" marker-end="url(#FilledDiamond_Marker)" stroke="blue" stroke-linecap="round" stroke-linejoin="round" stroke-width="4" stroke-dasharray="1,3"/><g id="id692_Graphic"><circle cx="829.01245" cy="959.23145" r="28.34648" fill="red"/><circle cx="829.01245" cy="959.23145" r="28.34648" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(811.33533 943.73145)" fill="black"><tspan font-family="Helvetica" font-size="26" font-weight="500" x="6.5243282" y="25" textLength="22.305664">A'</tspan></text></g><line x1="816.1477" y1="985.059" x2="785.96265" y2="1045.65894" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="3"/><text transform="translate(687.9198 889.0216)" fill="blue"><tspan font-family="Helvetica" font-size="17" font-weight="bold" fill="blue" x=".13012695" y="16" textLength="72.739746">Obsolete</tspan></text><g id="id702_Graphic"><path d="M 678.60693 819.58557 C 693.37787 801.90887 728.45825 796.0167 750.6144 809.16083 C 772.7699 822.3045 765.3855 882.13373 753.38397 883.94666 C 741.3828 885.75934 708.149 892.5584 690.6084 886.66614 C 673.06793 880.7739 663.8366 837.26196 678.60693 819.58557 Z" fill="#ff8000" fill-opacity=".15000001"/><path d="M 678.60693 819.58557 C 693.37787 801.90887 728.45825 796.0167 750.6144 809.16083 C 772.7699 822.3045 765.3855 882.13373 753.38397 883.94666 C 741.3828 885.75934 708.149 892.5584 690.6084 886.66614 C 673.06793 880.7739 663.8366 837.26196 678.60693 819.58557 Z" stroke="#ff8000" stroke-linecap="round" stroke-linejoin="round" stroke-width="4" stroke-dasharray="4,9,1,5"/></g><text transform="translate(674.40198 774.87)" fill="#ff8000"><tspan font-family="Helvetica" font-size="17" font-weight="bold" fill="#ff8000" x=".103271484" y="16" textLength="71.793457">Unstable</tspan></text><g id="id714_Graphic"><path d="M 679.4558 933.30554 C 694.08136 915.6288 728.81653 909.73657 750.75464 922.88074 C 772.6921 936.0244 765.38037 995.85364 753.49695 997.66656 C 741.6139 999.47925 708.7071 1006.2783 691.33917 1000.3861 C 673.9713 994.49384 664.8308 950.9819 679.4558 933.30554 Z" fill="blue" fill-opacity=".15000001"/><path d="M 679.4558 933.30554 C 694.08136 915.6288 728.81653 909.73657 750.75464 922.88074 C 772.6921 936.0244 765.38037 995.85364 753.49695 997.66656 C 741.6139 999.47925 708.7071 1006.2783 691.33917 1000.3861 C 673.9713 994.49384 664.8308 950.9819 679.4558 933.30554 Z" stroke="blue" stroke-linecap="round" stroke-linejoin="round" stroke-width="4" stroke-dasharray="4,9,1,5"/></g><g id="id688_Graphic"><circle cx="718.0555" cy="845.52167" r="28.34648" fill="yellow"/><path d="M 738.09943 825.47766 C 749.16943 836.5476 749.16943 854.4956 738.09943 865.56555 C 727.0295 876.63556 709.0815 876.63556 698.01154 865.56555 C 686.94153 854.4956 686.94153 836.5476 698.01154 825.47766 C 709.0815 814.40765 727.0295 814.40765 738.09943 825.47766" stroke="red" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" stroke-dasharray="16,9"/><text transform="translate(700.37836 830.0216)" fill="red"><tspan font-family="Helvetica" font-size="26" font-weight="500" fill="red" x="9.006262" y="25" textLength="17.341797">B</tspan></text></g></g></g></svg>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/figures/example-1-update.svg	Mon Jul 30 14:45:42 2012 +0200
@@ -0,0 +1,3 @@
+<?xml version="1.0"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xl="http://www.w3.org/1999/xlink" version="1.1" viewBox="47 621 426 249" width="426pt" height="249pt"><metadata xmlns:dc="http://purl.org/dc/elements/1.1/"><dc:date>2012-03-21 08:32Z</dc:date><!-- Produced by OmniGraffle Professional 5.3.6 --></metadata><defs><filter id="Shadow" filterUnits="userSpaceOnUse"><feGaussianBlur in="SourceAlpha" result="blur" stdDeviation="3.488"/><feOffset in="blur" result="offset" dx="0" dy="4"/><feFlood flood-color="black" flood-opacity=".75" result="flood"/><feComposite in="flood" in2="offset" operator="in"/></filter><font-face font-family="Helvetica" font-size="26" units-per-em="1000" underline-position="-75.683594" underline-thickness="49.31641" slope="0" x-height="522.94922" cap-height="717.2852" ascent="770.0196" descent="-229.98048" font-weight="500"><font-face-src><font-face-name name="Helvetica"/></font-face-src></font-face><marker orient="auto" overflow="visible" markerUnits="strokeWidth" id="FilledDiamond_Marker" viewBox="-1 -3 6 6" markerWidth="6" markerHeight="6" color="red"><g><path d="M 3.7333333 0 L 1.8666667 -1.4 L 0 0 L 1.8666667 1.4 Z" fill="currentColor" stroke="currentColor" stroke-width="1"/></g></marker></defs><g stroke="none" stroke-opacity="1" stroke-dasharray="none" fill="none" fill-opacity="1"><title>Canvas 1</title><g><title>Layer 1</title><g><use xl:href="#id464_Graphic" filter="url(#Shadow)"/><use xl:href="#id463_Graphic" filter="url(#Shadow)"/><use xl:href="#id465_Graphic" filter="url(#Shadow)"/><use xl:href="#id466_Graphic" filter="url(#Shadow)"/><use xl:href="#id472_Graphic" filter="url(#Shadow)"/><use xl:href="#id471_Graphic" filter="url(#Shadow)"/><use xl:href="#id475_Graphic" filter="url(#Shadow)"/></g><g id="id464_Graphic"><rect x="67" y="637.9972" width="141" height="207.84595" fill="white" fill-opacity=".5"/><rect x="67" y="637.9972" width="141" height="207.84595" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/></g><g id="id463_Graphic"><circle cx="128.71246" cy="718.34644" r="28.346489" fill="red"/><circle cx="128.71246" cy="718.34644" r="28.346489" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(111.035286 702.84644)" fill="black"><tspan font-family="Helvetica" font-size="26" font-weight="500" x="9.006262" y="25" textLength="17.341797">A</tspan></text></g><line x1="128.93794" y1="770.9975" x2="128.83687" y2="747.19263" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="3"/><circle cx="129.000244" cy="785.6706" r="14.173275" fill="black"/><circle cx="129.000244" cy="785.6706" r="14.173275" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><circle cx="129.000244" cy="831.6706" r="14.173275" fill="black"/><circle cx="129.000244" cy="831.6706" r="14.173275" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><line x1="128.96199" y1="800.3438" x2="128.91855" y2="816.99756" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><g id="id465_Graphic"><rect x="277" y="637.99725" width="176" height="207.84595" fill="white" fill-opacity=".5"/><rect x="277" y="637.99725" width="176" height="207.84595" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/></g><g id="id466_Graphic"><circle cx="407.34644" cy="703.95215" r="28.346495" fill="red"/><circle cx="407.34644" cy="703.95215" r="28.346495" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(389.66928 688.45215)" fill="black"><tspan font-family="Helvetica" font-size="26" font-weight="500" x="6.1180782" y="25" textLength="23.118164">A’</tspan></text></g><line x1="378.9428" y1="772.1349" x2="396.34451" y2="730.6261" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="3"/><g id="id472_Graphic"><circle cx="373.26825" cy="785.67053" r="14.173267" fill="black"/><circle cx="373.26825" cy="785.67053" r="14.173267" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/></g><g id="id471_Graphic"><circle cx="373.26825" cy="831.67053" r="14.173267" fill="black"/><circle cx="373.26825" cy="831.67053" r="14.173267" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/></g><line x1="373.26837" y1="800.34375" x2="373.26852" y2="816.9973" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><line x1="365.1879" y1="773.41925" x2="335.23688" y2="728.00824" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="3"/><g id="id475_Graphic"><circle cx="319.30762" cy="703.95215" r="28.346495" fill="red" fill-opacity=".5"/><path d="M 339.35156 683.9082 C 350.4216 694.97815 350.4216 712.92615 339.35156 723.9961 C 328.28159 735.0661 310.33365 735.0661 299.26367 723.9961 C 288.19363 712.92615 288.19363 694.97815 299.26367 683.9082 C 310.33365 672.8382 328.28159 672.8382 339.35156 683.9082" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="8,5"/><text transform="translate(301.63046 688.45215)" fill="black"><tspan font-family="Helvetica" font-size="26" font-weight="500" x="9.006262" y="25" textLength="17.341797">A</tspan></text></g><line x1="378.49997" y1="703.95215" x2="362.1041" y2="703.95215" marker-end="url(#FilledDiamond_Marker)" stroke="red" stroke-linecap="round" stroke-linejoin="round" stroke-width="3" stroke-dasharray="1,4"/></g></g></svg>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/figures/example-2-split.svg	Mon Jul 30 14:45:42 2012 +0200
@@ -0,0 +1,3 @@
+<?xml version="1.0"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xl="http://www.w3.org/1999/xlink" version="1.1" viewBox="27 1004 488 344" width="488pt" height="344pt"><metadata xmlns:dc="http://purl.org/dc/elements/1.1/"><dc:date>2012-03-21 08:32Z</dc:date><!-- Produced by OmniGraffle Professional 5.3.6 --></metadata><defs><filter id="Shadow" filterUnits="userSpaceOnUse"><feGaussianBlur in="SourceAlpha" result="blur" stdDeviation="3.488"/><feOffset in="blur" result="offset" dx="0" dy="4"/><feFlood flood-color="black" flood-opacity=".75" result="flood"/><feComposite in="flood" in2="offset" operator="in"/></filter><font-face font-family="Helvetica" font-size="26" units-per-em="1000" underline-position="-75.683594" underline-thickness="49.31641" slope="0" x-height="522.94922" cap-height="717.2852" ascent="770.0196" descent="-229.98048" font-weight="500"><font-face-src><font-face-name name="Helvetica"/></font-face-src></font-face><font-face font-family="Helvetica" font-size="15" units-per-em="1000" underline-position="-75.683594" underline-thickness="49.316406" slope="0" x-height="522.94922" cap-height="717.28516" ascent="770.01953" descent="-229.98045" font-weight="500"><font-face-src><font-face-name name="Helvetica"/></font-face-src></font-face><marker orient="auto" overflow="visible" markerUnits="strokeWidth" id="FilledDiamond_Marker" viewBox="-1 -3 6 6" markerWidth="6" markerHeight="6" color="red"><g><path d="M 3.7333333 0 L 1.8666667 -1.4 L 0 0 L 1.8666667 1.4 Z" fill="currentColor" stroke="currentColor" stroke-width="1"/></g></marker></defs><g stroke="none" stroke-opacity="1" stroke-dasharray="none" fill="none" fill-opacity="1"><title>Canvas 1</title><g><title>Layer 1</title><g><use xl:href="#id478_Graphic" filter="url(#Shadow)"/><use xl:href="#id479_Graphic" filter="url(#Shadow)"/><use xl:href="#id481_Graphic" filter="url(#Shadow)"/><use xl:href="#id482_Graphic" filter="url(#Shadow)"/><use xl:href="#id485_Graphic" filter="url(#Shadow)"/><use xl:href="#id488_Graphic" filter="url(#Shadow)"/><use xl:href="#id489_Graphic" filter="url(#Shadow)"/><use xl:href="#id491_Graphic" filter="url(#Shadow)"/><use xl:href="#id492_Graphic" filter="url(#Shadow)"/><use xl:href="#id495_Graphic" filter="url(#Shadow)"/></g><g id="id478_Graphic"><rect x="47" y="1087.16406" width="143" height="202.84595" fill="white" fill-opacity=".5"/><rect x="47" y="1087.16406" width="143" height="202.84595" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/></g><g id="id479_Graphic"><circle cx="118.50046" cy="1155.5105" r="28.346527" fill="#ff8000"/><circle cx="118.50046" cy="1155.5105" r="28.346527" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(100.823296 1140.0105)" fill="black"><tspan font-family="Helvetica" font-size="26" font-weight="500" x="9.006262" y="25" textLength="17.341797">A</tspan></text></g><line x1="118.50029" y1="1215.66406" x2="118.50038" y2="1184.3569" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="3"/><g id="id481_Graphic"><path d="M 128.52223 1220.3153 C 134.05725 1225.85034 134.05725 1234.8242 128.52223 1240.35925 C 122.98724 1245.8943 114.013245 1245.8943 108.478256 1240.35925 C 102.94323 1234.8242 102.94323 1225.85034 108.478256 1220.3153 C 114.013245 1214.7803 122.98724 1214.7803 128.52223 1220.3153" fill="black"/><path d="M 128.52223 1220.3153 C 134.05725 1225.85034 134.05725 1234.8242 128.52223 1240.35925 C 122.98724 1245.8943 114.013245 1245.8943 108.478256 1240.35925 C 102.94323 1234.8242 102.94323 1225.85034 108.478256 1220.3153 C 114.013245 1214.7803 122.98724 1214.7803 128.52223 1220.3153" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/></g><g id="id482_Graphic"><path d="M 128.52223 1265.8153 C 134.05725 1271.35034 134.05725 1280.3242 128.52223 1285.85925 C 122.98724 1291.3943 114.013245 1291.3943 108.478256 1285.85925 C 102.94323 1280.3242 102.94323 1271.35034 108.478256 1265.8153 C 114.013245 1260.2803 122.98724 1260.2803 128.52223 1265.8153" fill="black"/><path d="M 128.52223 1265.8153 C 134.05725 1271.35034 134.05725 1280.3242 128.52223 1285.85925 C 122.98724 1291.3943 114.013245 1291.3943 108.478256 1285.85925 C 102.94323 1280.3242 102.94323 1271.35034 108.478256 1265.8153 C 114.013245 1260.2803 122.98724 1260.2803 128.52223 1265.8153" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/></g><line x1="118.500244" y1="1245.0105" x2="118.500244" y2="1261.1641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><g id="id485_Graphic"><rect x="277" y="1020" width="218" height="303.08691" fill="white" fill-opacity=".5"/><rect x="277" y="1020" width="218" height="303.08691" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/></g><line x1="384.58325" y1="1251.5149" x2="356.10718" y2="1212.0704" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="3"/><g id="id488_Graphic"><path d="M 403.19525 1253.3916 C 408.73026 1258.9266 408.73026 1267.9005 403.19525 1273.43555 C 397.66025 1278.9706 388.68625 1278.9706 383.15125 1273.43555 C 377.61624 1267.9005 377.61624 1258.9266 383.15125 1253.3916 C 388.68625 1247.8566 397.66025 1247.8566 403.19525 1253.3916" fill="black"/><path d="M 403.19525 1253.3916 C 408.73026 1258.9266 408.73026 1267.9005 403.19525 1273.43555 C 397.66025 1278.9706 388.68625 1278.9706 383.15125 1273.43555 C 377.61624 1267.9005 377.61624 1258.9266 383.15125 1253.3916 C 388.68625 1247.8566 397.66025 1247.8566 403.19525 1253.3916" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/></g><g id="id489_Graphic"><path d="M 403.19525 1298.8916 C 408.73026 1304.4266 408.73026 1313.4005 403.19525 1318.93555 C 397.66025 1324.4706 388.68625 1324.4706 383.15125 1318.93555 C 377.61624 1313.4005 377.61624 1304.4266 383.15125 1298.8916 C 388.68625 1293.3566 397.66025 1293.3566 403.19525 1298.8916" fill="black"/><path d="M 403.19525 1298.8916 C 408.73026 1304.4266 408.73026 1313.4005 403.19525 1318.93555 C 397.66025 1324.4706 388.68625 1324.4706 383.15125 1318.93555 C 377.61624 1313.4005 377.61624 1304.4266 383.15125 1298.8916 C 388.68625 1293.3566 397.66025 1293.3566 403.19525 1298.8916" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/></g><line x1="393.17325" y1="1278.0868" x2="393.17325" y2="1294.24036" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><g id="id491_Graphic"><circle cx="437.34644" cy="1188.5874" r="28.346533" fill="red"/><circle cx="437.34644" cy="1188.5874" r="28.346533" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(419.66928 1171.0874)" fill="black"><tspan font-family="Helvetica" font-size="26" font-weight="500" x="4.8351192" y="25" textLength="17.341797">A</tspan><tspan font-family="Helvetica" font-size="15" font-weight="500" x="22.176916" y="32" textLength="8.342285">1</tspan></text></g><g id="id492_Graphic"><circle cx="437.34644" cy="1105.34644" r="28.346533" fill="yellow"/><circle cx="437.34644" cy="1105.34644" r="28.346533" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(419.66928 1087.84644)" fill="black"><tspan font-family="Helvetica" font-size="26" font-weight="500" x="4.8351192" y="25" textLength="17.341797">A</tspan><tspan font-family="Helvetica" font-size="15" font-weight="500" x="22.176916" y="32" textLength="8.342285">2</tspan></text></g><line x1="400.63443" y1="1250.7749" x2="422.67834" y2="1213.4341" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="3"/><line x1="437.3464" y1="1159.741" x2="437.3464" y2="1134.1929" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="3"/><g id="id495_Graphic"><circle cx="339.34644" cy="1188.5874" r="28.346533" fill="#ff8000"/><path d="M 359.39038 1168.54346 C 370.46042 1179.6134 370.46042 1197.5614 359.39038 1208.63135 C 348.3204 1219.7014 330.37247 1219.7014 319.30249 1208.63135 C 308.23245 1197.5614 308.23245 1179.6134 319.30249 1168.54346 C 330.37247 1157.4734 348.3204 1157.4734 359.39038 1168.54346" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="8,5"/><text transform="translate(321.66928 1173.0874)" fill="black"><tspan font-family="Helvetica" font-size="26" font-weight="500" x="9.006262" y="25" textLength="17.341797">A</tspan></text></g><line x1="393.50046" y1="1152" x2="374.81244" y2="1164.626" marker-end="url(#FilledDiamond_Marker)" stroke="red" stroke-linecap="round" stroke-linejoin="round" stroke-width="3" stroke-dasharray="1,4"/><line x1="417.8144" y1="1126.5746" x2="393.50046" y2="1153" stroke="red" stroke-linecap="round" stroke-linejoin="round" stroke-width="3" stroke-dasharray="1,4"/><line x1="416.74606" y1="1168.3948" x2="399" y2="1151" stroke="red" stroke-linecap="round" stroke-linejoin="round" stroke-width="3" stroke-dasharray="1,4"/></g></g></svg>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/figures/simple-3-merge.svg	Mon Jul 30 14:45:42 2012 +0200
@@ -0,0 +1,3 @@
+<?xml version="1.0"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xl="http://www.w3.org/1999/xlink" version="1.1" viewBox="27 1677 497 345" width="497pt" height="345pt"><metadata xmlns:dc="http://purl.org/dc/elements/1.1/"><dc:date>2012-03-21 08:32Z</dc:date><!-- Produced by OmniGraffle Professional 5.3.6 --></metadata><defs><filter id="Shadow" filterUnits="userSpaceOnUse"><feGaussianBlur in="SourceAlpha" result="blur" stdDeviation="3.488"/><feOffset in="blur" result="offset" dx="0" dy="4"/><feFlood flood-color="black" flood-opacity=".75" result="flood"/><feComposite in="flood" in2="offset" operator="in"/></filter><font-face font-family="Helvetica" font-size="26" units-per-em="1000" underline-position="-75.683594" underline-thickness="49.31641" slope="0" x-height="522.94922" cap-height="717.2852" ascent="770.0196" descent="-229.98048" font-weight="500"><font-face-src><font-face-name name="Helvetica"/></font-face-src></font-face><marker orient="auto" overflow="visible" markerUnits="strokeWidth" id="FilledDiamond_Marker" viewBox="-1 -3 6 6" markerWidth="6" markerHeight="6" color="red"><g><path d="M 3.7333333 0 L 1.8666667 -1.4 L 0 0 L 1.8666667 1.4 Z" fill="currentColor" stroke="currentColor" stroke-width="1"/></g></marker></defs><g stroke="none" stroke-opacity="1" stroke-dasharray="none" fill="none" fill-opacity="1"><title>Canvas 1</title><g><title>Layer 1</title><g><use xl:href="#id500_Graphic" filter="url(#Shadow)"/><use xl:href="#id501_Graphic" filter="url(#Shadow)"/><use xl:href="#id503_Graphic" filter="url(#Shadow)"/><use xl:href="#id504_Graphic" filter="url(#Shadow)"/><use xl:href="#id507_Graphic" filter="url(#Shadow)"/><use xl:href="#id509_Graphic" filter="url(#Shadow)"/><use xl:href="#id510_Graphic" filter="url(#Shadow)"/><use xl:href="#id516_Graphic" filter="url(#Shadow)"/><use xl:href="#id284_Graphic" filter="url(#Shadow)"/><use xl:href="#id520_Graphic" filter="url(#Shadow)"/><use xl:href="#id523_Graphic" filter="url(#Shadow)"/></g><g id="id500_Graphic"><rect x="47" y="1726.5562" width="143" height="260.96387" fill="white" fill-opacity=".5"/><rect x="47" y="1726.5562" width="143" height="260.96387" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/></g><g id="id501_Graphic"><circle cx="118.50044" cy="1853.0206" r="28.346527" fill="red"/><circle cx="118.50044" cy="1853.0206" r="28.346527" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(100.82328 1837.5206)" fill="black"><tspan font-family="Helvetica" font-size="26" font-weight="500" x="9.006262" y="25" textLength="17.341797">A</tspan></text></g><line x1="118.50028" y1="1913.1742" x2="118.500366" y2="1881.8672" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="3"/><g id="id503_Graphic"><path d="M 128.52223 1917.8254 C 134.05725 1923.3605 134.05725 1932.3344 128.52223 1937.8694 C 122.98724 1943.4044 114.013245 1943.4044 108.478256 1937.8694 C 102.94323 1932.3344 102.94323 1923.3605 108.478256 1917.8254 C 114.013245 1912.2904 122.98724 1912.2904 128.52223 1917.8254" fill="black"/><path d="M 128.52223 1917.8254 C 134.05725 1923.3605 134.05725 1932.3344 128.52223 1937.8694 C 122.98724 1943.4044 114.013245 1943.4044 108.478256 1937.8694 C 102.94323 1932.3344 102.94323 1923.3605 108.478256 1917.8254 C 114.013245 1912.2904 122.98724 1912.2904 128.52223 1917.8254" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/></g><g id="id504_Graphic"><path d="M 128.52223 1963.3254 C 134.05725 1968.8605 134.05725 1977.8344 128.52223 1983.3694 C 122.98724 1988.9044 114.013245 1988.9044 108.478256 1983.3694 C 102.94323 1977.8344 102.94323 1968.8605 108.478256 1963.3254 C 114.013245 1957.7904 122.98724 1957.7904 128.52223 1963.3254" fill="black"/><path d="M 128.52223 1963.3254 C 134.05725 1968.8605 134.05725 1977.8344 128.52223 1983.3694 C 122.98724 1988.9044 114.013245 1988.9044 108.478256 1983.3694 C 102.94323 1977.8344 102.94323 1968.8605 108.478256 1963.3254 C 114.013245 1957.7904 122.98724 1957.7904 128.52223 1963.3254" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/></g><line x1="118.500244" y1="1942.5206" x2="118.500244" y2="1958.6742" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><g id="id507_Graphic"><rect x="285.5" y="1693.958" width="218" height="303.08691" fill="white" fill-opacity=".5"/><rect x="285.5" y="1693.958" width="218" height="303.08691" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/></g><line x1="393.10345" y1="1925.458" x2="364.69406" y2="1885.965" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="3"/><g id="id509_Graphic"><path d="M 411.69525 1927.3492 C 417.23026 1932.8843 417.23026 1941.8582 411.69525 1947.3932 C 406.16025 1952.9282 397.18625 1952.9282 391.65125 1947.3932 C 386.11624 1941.8582 386.11624 1932.8843 391.65125 1927.3492 C 397.18625 1921.8142 406.16025 1921.8142 411.69525 1927.3492" fill="black"/><path d="M 411.69525 1927.3492 C 417.23026 1932.8843 417.23026 1941.8582 411.69525 1947.3932 C 406.16025 1952.9282 397.18625 1952.9282 391.65125 1947.3932 C 386.11624 1941.8582 386.11624 1932.8843 391.65125 1927.3492 C 397.18625 1921.8142 406.16025 1921.8142 411.69525 1927.3492" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/></g><g id="id510_Graphic"><path d="M 411.69525 1972.8492 C 417.23026 1978.3843 417.23026 1987.3582 411.69525 1992.8932 C 406.16025 1998.4282 397.18625 1998.4282 391.65125 1992.8932 C 386.11624 1987.3582 386.11624 1978.3843 391.65125 1972.8492 C 397.18625 1967.3142 406.16025 1967.3142 411.69525 1972.8492" fill="black"/><path d="M 411.69525 1972.8492 C 417.23026 1978.3843 417.23026 1987.3582 411.69525 1992.8932 C 406.16025 1998.4282 397.18625 1998.4282 391.65125 1992.8932 C 386.11624 1987.3582 386.11624 1978.3843 391.65125 1972.8492 C 397.18625 1967.3142 406.16025 1967.3142 411.69525 1972.8492" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/></g><line x1="401.67789" y1="1952.0444" x2="401.68301" y2="1968.198" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><g id="id516_Graphic"><circle cx="347.84644" cy="1862.5444" r="28.346533" fill="red" fill-opacity=".5"/><path d="M 367.89038 1842.5005 C 378.96042 1853.5704 378.96042 1871.5184 367.89038 1882.5884 C 356.8204 1893.6584 338.87247 1893.6584 327.8025 1882.5884 C 316.73245 1871.5184 316.73245 1853.5704 327.8025 1842.5005 C 338.87247 1831.4304 356.8204 1831.4304 367.89038 1842.5005" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="8,5"/><text transform="translate(330.16928 1847.0444)" fill="black"><tspan font-family="Helvetica" font-size="26" font-weight="500" x="9.006262" y="25" textLength="17.341797">A</tspan></text></g><line x1="427.0911" y1="1818.7612" x2="385.31204" y2="1841.8445" marker-end="url(#FilledDiamond_Marker)" stroke="red" stroke-linecap="round" stroke-linejoin="round" stroke-width="3" stroke-dasharray="1,4"/><line x1="118.50028" y1="1824.1741" x2="118.50015" y2="1800.5271" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="3"/><g id="id284_Graphic"><circle cx="118.5" cy="1771.6807" r="28.346527" fill="yellow"/><circle cx="118.5" cy="1771.6807" r="28.346527" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(100.82284 1756.1807)" fill="black"><tspan font-family="Helvetica" font-size="26" font-weight="500" x="9.006262" y="25" textLength="17.341797">B</tspan></text></g><g id="id520_Graphic"><circle cx="347.84644" cy="1754.9026" r="28.346533" fill="yellow" fill-opacity=".5"/><path d="M 367.89038 1734.8586 C 378.96042 1745.9286 378.96042 1763.8766 367.89038 1774.9465 C 356.8204 1786.0166 338.87247 1786.0166 327.8025 1774.9465 C 316.73245 1763.8766 316.73245 1745.9286 327.8025 1734.8586 C 338.87247 1723.7886 356.8204 1723.7886 367.89038 1734.8586" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="8,5"/><text transform="translate(330.16928 1739.4026)" fill="black"><tspan font-family="Helvetica" font-size="26" font-weight="500" x="9.006262" y="25" textLength="17.341797">B</tspan></text></g><line x1="347.84897" y1="1833.698" x2="347.85342" y2="1783.749" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="3"/><line x1="406.91367" y1="1923.662" x2="442.04407" y2="1831.7589" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="3"/><g id="id523_Graphic"><circle cx="452.34644" cy="1804.8074" r="28.346533" fill="#ff8000"/><circle cx="452.34644" cy="1804.8074" r="28.346533" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(434.66928 1789.3074)" fill="black"><tspan font-family="Helvetica" font-size="26" font-weight="500" x="8.2889767" y="25" textLength="18.776367">C</tspan></text></g><line x1="426.309" y1="1792.373" x2="386.4721" y2="1773.3486" marker-end="url(#FilledDiamond_Marker)" stroke="red" stroke-linecap="round" stroke-linejoin="round" stroke-width="3" stroke-dasharray="1,4"/></g></g></svg>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/figures/simple-4-reorder.svg	Mon Jul 30 14:45:42 2012 +0200
@@ -0,0 +1,3 @@
+<?xml version="1.0"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xl="http://www.w3.org/1999/xlink" version="1.1" viewBox="27 2166 497 345" width="497pt" height="345pt"><metadata xmlns:dc="http://purl.org/dc/elements/1.1/"><dc:date>2012-03-21 08:32Z</dc:date><!-- Produced by OmniGraffle Professional 5.3.6 --></metadata><defs><filter id="Shadow" filterUnits="userSpaceOnUse"><feGaussianBlur in="SourceAlpha" result="blur" stdDeviation="3.488"/><feOffset in="blur" result="offset" dx="0" dy="4"/><feFlood flood-color="black" flood-opacity=".75" result="flood"/><feComposite in="flood" in2="offset" operator="in"/></filter><marker orient="auto" overflow="visible" markerUnits="strokeWidth" id="FilledDiamond_Marker" viewBox="-1 -3 6 6" markerWidth="6" markerHeight="6" color="red"><g><path d="M 3.7333333 0 L 1.8666667 -1.4 L 0 0 L 1.8666667 1.4 Z" fill="currentColor" stroke="currentColor" stroke-width="1"/></g></marker><font-face font-family="Helvetica" font-size="26" units-per-em="1000" underline-position="-75.683594" underline-thickness="49.31641" slope="0" x-height="522.94922" cap-height="717.2852" ascent="770.0196" descent="-229.98048" font-weight="500"><font-face-src><font-face-name name="Helvetica"/></font-face-src></font-face></defs><g stroke="none" stroke-opacity="1" stroke-dasharray="none" fill="none" fill-opacity="1"><title>Canvas 1</title><g><title>Layer 1</title><g><use xl:href="#id532_Graphic" filter="url(#Shadow)"/><use xl:href="#id525_Graphic" filter="url(#Shadow)"/><use xl:href="#id526_Graphic" filter="url(#Shadow)"/><use xl:href="#id528_Graphic" filter="url(#Shadow)"/><use xl:href="#id529_Graphic" filter="url(#Shadow)"/><use xl:href="#id534_Graphic" filter="url(#Shadow)"/><use xl:href="#id535_Graphic" filter="url(#Shadow)"/><use xl:href="#id537_Graphic" filter="url(#Shadow)"/><use xl:href="#id540_Graphic" filter="url(#Shadow)"/><use xl:href="#id541_Graphic" filter="url(#Shadow)"/><use xl:href="#id546_Graphic" filter="url(#Shadow)"/><use xl:href="#id549_Graphic" filter="url(#Shadow)"/></g><g id="id532_Graphic"><rect x="285.5" y="2182.9602" width="218" height="303.08691" fill="white" fill-opacity=".5"/><rect x="285.5" y="2182.9602" width="218" height="303.08691" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/></g><line x1="427.7653" y1="2330.3635" x2="376.8968" y2="2275.3335" marker-end="url(#FilledDiamond_Marker)" stroke="red" stroke-linecap="round" stroke-linejoin="round" stroke-width="3" stroke-dasharray="1,4"/><g id="id525_Graphic"><rect x="47" y="2215.56" width="143" height="260.96387" fill="white" fill-opacity=".5"/><rect x="47" y="2215.56" width="143" height="260.96387" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/></g><g id="id526_Graphic"><path d="M 138.5444 2321.9729 C 149.61443 2333.043 149.61443 2350.9907 138.5444 2362.0608 C 127.47443 2373.1309 109.526474 2373.1309 98.456505 2362.0608 C 87.386475 2350.9907 87.386475 2333.043 98.456505 2321.9729 C 109.526474 2310.9028 127.47443 2310.9028 138.5444 2321.9729" fill="red"/><path d="M 138.5444 2321.9729 C 149.61443 2333.043 149.61443 2350.9907 138.5444 2362.0608 C 127.47443 2373.1309 109.526474 2373.1309 98.456505 2362.0608 C 87.386475 2350.9907 87.386475 2333.043 98.456505 2321.9729 C 109.526474 2310.9028 127.47443 2310.9028 138.5444 2321.9729" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(100.82329 2326.5168)" fill="black"><tspan font-family="Helvetica" font-size="26" font-weight="500" x="9.006262" y="25" textLength="17.341797">A</tspan></text></g><line x1="118.50029" y1="2402.1704" x2="118.500374" y2="2370.8633" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="3"/><g id="id528_Graphic"><path d="M 128.52225 2406.8218 C 134.05727 2412.3567 134.05727 2421.3308 128.52225 2426.8657 C 122.98724 2432.4009 114.01326 2432.4009 108.478264 2426.8657 C 102.94324 2421.3308 102.94324 2412.3567 108.478264 2406.8218 C 114.01326 2401.2866 122.98724 2401.2866 128.52225 2406.8218" fill="black"/><path d="M 128.52225 2406.8218 C 134.05727 2412.3567 134.05727 2421.3308 128.52225 2426.8657 C 122.98724 2432.4009 114.01326 2432.4009 108.478264 2426.8657 C 102.94324 2421.3308 102.94324 2412.3567 108.478264 2406.8218 C 114.01326 2401.2866 122.98724 2401.2866 128.52225 2406.8218" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/></g><g id="id529_Graphic"><path d="M 128.52225 2452.3218 C 134.05727 2457.8567 134.05727 2466.8308 128.52225 2472.3657 C 122.98724 2477.9009 114.01326 2477.9009 108.478264 2472.3657 C 102.94324 2466.8308 102.94324 2457.8567 108.478264 2452.3218 C 114.01326 2446.7866 122.98724 2446.7866 128.52225 2452.3218" fill="black"/><path d="M 128.52225 2452.3218 C 134.05727 2457.8567 134.05727 2466.8308 128.52225 2472.3657 C 122.98724 2477.9009 114.01326 2477.9009 108.478264 2472.3657 C 102.94324 2466.8308 102.94324 2457.8567 108.478264 2452.3218 C 114.01326 2446.7866 122.98724 2446.7866 128.52225 2452.3218" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/></g><line x1="118.50025" y1="2431.5171" x2="118.50025" y2="2447.6704" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><line x1="393.10349" y1="2414.4607" x2="364.69394" y2="2374.9673" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="3"/><g id="id534_Graphic"><path d="M 411.69525 2416.352 C 417.23026 2421.887 417.23026 2430.8611 411.69525 2436.396 C 406.16025 2441.9312 397.18625 2441.9312 391.65125 2436.396 C 386.11624 2430.8611 386.11624 2421.887 391.65125 2416.352 C 397.18625 2410.8169 406.16025 2410.8169 411.69525 2416.352" fill="black"/><path d="M 411.69525 2416.352 C 417.23026 2421.887 417.23026 2430.8611 411.69525 2436.396 C 406.16025 2441.9312 397.18625 2441.9312 391.65125 2436.396 C 386.11624 2430.8611 386.11624 2421.887 391.65125 2416.352 C 397.18625 2410.8169 406.16025 2410.8169 411.69525 2416.352" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/></g><g id="id535_Graphic"><path d="M 411.69525 2461.852 C 417.23026 2467.387 417.23026 2476.3611 411.69525 2481.896 C 406.16025 2487.4312 397.18625 2487.4312 391.65125 2481.896 C 386.11624 2476.3611 386.11624 2467.387 391.65125 2461.852 C 397.18625 2456.3169 406.16025 2456.3169 411.69525 2461.852" fill="black"/><path d="M 411.69525 2461.852 C 417.23026 2467.387 417.23026 2476.3611 411.69525 2481.896 C 406.16025 2487.4312 397.18625 2487.4312 391.65125 2481.896 C 386.11624 2476.3611 386.11624 2467.387 391.65125 2461.852 C 397.18625 2456.3169 406.16025 2456.3169 411.69525 2461.852" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/></g><line x1="401.67325" y1="2441.0474" x2="401.67325" y2="2457.2007" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><g id="id537_Graphic"><path d="M 367.89038 2331.5027 C 378.96042 2342.5728 378.96042 2360.5205 367.89038 2371.5906 C 356.8204 2382.6606 338.87247 2382.6606 327.8025 2371.5906 C 316.73245 2360.5205 316.73245 2342.5728 327.8025 2331.5027 C 338.87247 2320.4326 356.8204 2320.4326 367.89038 2331.5027" fill="red" fill-opacity=".5"/><path d="M 367.89038 2331.5027 C 378.96042 2342.5728 378.96042 2360.5205 367.89038 2371.5906 C 356.8204 2382.6606 338.87247 2382.6606 327.8025 2371.5906 C 316.73245 2360.5205 316.73245 2342.5728 327.8025 2331.5027 C 338.87247 2320.4326 356.8204 2320.4326 367.89038 2331.5027" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="8,5"/><text transform="translate(330.16928 2336.0466)" fill="black"><tspan font-family="Helvetica" font-size="26" font-weight="500" x="9.006262" y="25" textLength="17.341797">A</tspan></text></g><line x1="118.500275" y1="2313.1704" x2="118.50013" y2="2289.523" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="3"/><g id="id540_Graphic"><path d="M 138.5439 2240.6326 C 149.61392 2251.7026 149.61392 2269.6504 138.5439 2280.7205 C 127.47393 2291.7905 109.52598 2291.7905 98.45601 2280.7205 C 87.38598 2269.6504 87.38598 2251.7026 98.45601 2240.6326 C 109.52598 2229.5625 127.47393 2229.5625 138.5439 2240.6326" fill="yellow"/><path d="M 138.5439 2240.6326 C 149.61392 2251.7026 149.61392 2269.6504 138.5439 2280.7205 C 127.47393 2291.7905 109.52598 2291.7905 98.45601 2280.7205 C 87.38598 2269.6504 87.38598 2251.7026 98.45601 2240.6326 C 109.52598 2229.5625 127.47393 2229.5625 138.5439 2240.6326" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(100.82279 2245.1765)" fill="black"><tspan font-family="Helvetica" font-size="26" font-weight="500" x="9.006262" y="25" textLength="17.341797">B</tspan></text></g><g id="id541_Graphic"><path d="M 367.89038 2223.8625 C 378.96042 2234.9326 378.96042 2252.8804 367.89038 2263.9504 C 356.8204 2275.0205 338.87247 2275.0205 327.8025 2263.9504 C 316.73245 2252.8804 316.73245 2234.9326 327.8025 2223.8625 C 338.87247 2212.7925 356.8204 2212.7925 367.89038 2223.8625" fill="yellow" fill-opacity=".5"/><path d="M 367.89038 2223.8625 C 378.96042 2234.9326 378.96042 2252.8804 367.89038 2263.9504 C 356.8204 2275.0205 338.87247 2275.0205 327.8025 2263.9504 C 316.73245 2252.8804 316.73245 2234.9326 327.8025 2223.8625 C 338.87247 2212.7925 356.8204 2212.7925 367.89038 2223.8625" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="8,5"/><text transform="translate(330.16928 2228.4065)" fill="black"><tspan font-family="Helvetica" font-size="26" font-weight="500" x="9.006262" y="25" textLength="17.341797">B</tspan></text></g><line x1="347.84644" y1="2322.7002" x2="347.84644" y2="2272.7529" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="3"/><g id="id546_Graphic"><path d="M 467.39038 2218.6619 C 478.46042 2229.732 478.46042 2247.6797 467.39038 2258.7498 C 456.3204 2269.8198 438.37247 2269.8198 427.3025 2258.7498 C 416.23245 2247.6797 416.23245 2229.732 427.3025 2218.6619 C 438.37247 2207.5918 456.3204 2207.5918 467.39038 2218.6619" fill="red"/><path d="M 467.39038 2218.6619 C 478.46042 2229.732 478.46042 2247.6797 467.39038 2258.7498 C 456.3204 2269.8198 438.37247 2269.8198 427.3025 2258.7498 C 416.23245 2247.6797 416.23245 2229.732 427.3025 2218.6619 C 438.37247 2207.5918 456.3204 2207.5918 467.39038 2218.6619" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(429.66928 2223.2058)" fill="black"><tspan font-family="Helvetica" font-size="26" font-weight="500" x="6.5243282" y="25" textLength="22.305664">A'</tspan></text></g><line x1="409.35617" y1="2413.8691" x2="432.46094" y2="2376.2634" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="3"/><line x1="447.3466" y1="2267.5522" x2="447.34692" y2="2322.7002" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="3"/><g id="id549_Graphic"><path d="M 467.39038 2331.5027 C 478.46042 2342.5728 478.46042 2360.5205 467.39038 2371.5906 C 456.3204 2382.6606 438.37247 2382.6606 427.3025 2371.5906 C 416.23245 2360.5205 416.23245 2342.5728 427.3025 2331.5027 C 438.37247 2320.4326 456.3204 2320.4326 467.39038 2331.5027" fill="yellow"/><path d="M 467.39038 2331.5027 C 478.46042 2342.5728 478.46042 2360.5205 467.39038 2371.5906 C 456.3204 2382.6606 438.37247 2382.6606 427.3025 2371.5906 C 416.23245 2360.5205 416.23245 2342.5728 427.3025 2331.5027 C 438.37247 2320.4326 456.3204 2320.4326 467.39038 2331.5027" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(429.66928 2336.0466)" fill="black"><tspan font-family="Helvetica" font-size="26" font-weight="500" x="6.5243282" y="25" textLength="22.305664">B'</tspan></text></g><line x1="428.2673" y1="2260.343" x2="376.15155" y2="2319.4465" marker-end="url(#FilledDiamond_Marker)" stroke="red" stroke-linecap="round" stroke-linejoin="round" stroke-width="3" stroke-dasharray="1,4"/></g></g></svg>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/figures/simple-5-delete.svg	Mon Jul 30 14:45:42 2012 +0200
@@ -0,0 +1,3 @@
+<?xml version="1.0"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xl="http://www.w3.org/1999/xlink" version="1.1" viewBox="66 2640 420 262" width="35pc" height="262pt"><metadata xmlns:dc="http://purl.org/dc/elements/1.1/"><dc:date>2012-03-21 08:32Z</dc:date><!-- Produced by OmniGraffle Professional 5.3.6 --></metadata><defs><filter id="Shadow" filterUnits="userSpaceOnUse"><feGaussianBlur in="SourceAlpha" result="blur" stdDeviation="3.488"/><feOffset in="blur" result="offset" dx="0" dy="4"/><feFlood flood-color="black" flood-opacity=".75" result="flood"/><feComposite in="flood" in2="offset" operator="in"/></filter><font-face font-family="Helvetica" font-size="26" units-per-em="1000" underline-position="-75.683594" underline-thickness="49.31641" slope="0" x-height="522.94922" cap-height="717.2852" ascent="770.0196" descent="-229.98048" font-weight="500"><font-face-src><font-face-name name="Helvetica"/></font-face-src></font-face><marker orient="auto" overflow="visible" markerUnits="strokeWidth" id="FilledDiamond_Marker" viewBox="-1 -3 6 6" markerWidth="6" markerHeight="6" color="red"><g><path d="M 3.7333333 0 L 1.8666667 -1.4 L 0 0 L 1.8666667 1.4 Z" fill="currentColor" stroke="currentColor" stroke-width="1"/></g></marker><marker orient="auto" overflow="visible" markerUnits="strokeWidth" id="Ball_Marker" viewBox="-4 -3 5 6" markerWidth="5" markerHeight="6" color="red"><g><circle cx="-1.3999994" cy="0" r="1.3999988" fill="none" stroke="currentColor" stroke-width="1"/></g></marker></defs><g stroke="none" stroke-opacity="1" stroke-dasharray="none" fill="none" fill-opacity="1"><title>Canvas 1</title><g><title>Layer 1</title><g><use xl:href="#id575_Graphic" filter="url(#Shadow)"/><use xl:href="#id554_Graphic" filter="url(#Shadow)"/><use xl:href="#id555_Graphic" filter="url(#Shadow)"/><use xl:href="#id557_Graphic" filter="url(#Shadow)"/><use xl:href="#id558_Graphic" filter="url(#Shadow)"/><use xl:href="#id562_Graphic" filter="url(#Shadow)"/><use xl:href="#id563_Graphic" filter="url(#Shadow)"/><use xl:href="#id565_Graphic" filter="url(#Shadow)"/></g><g id="id575_Graphic"><rect x="322.92328" y="2656" width="143" height="221.08398" fill="white" fill-opacity=".5"/><rect x="322.92328" y="2656" width="143" height="221.08398" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/></g><g id="id554_Graphic"><rect x="86" y="2670.28" width="143" height="192.52393" fill="white" fill-opacity=".5"/><rect x="86" y="2670.28" width="143" height="192.52393" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/></g><g id="id555_Graphic"><path d="M 177.54439 2708.2524 C 188.61441 2719.3225 188.61441 2737.2703 177.54439 2748.3403 C 166.47443 2759.4104 148.52646 2759.4104 137.4565 2748.3403 C 126.38647 2737.2703 126.38647 2719.3225 137.4565 2708.2524 C 148.52646 2697.1824 166.47443 2697.1824 177.54439 2708.2524" fill="red"/><path d="M 177.54439 2708.2524 C 188.61441 2719.3225 188.61441 2737.2703 177.54439 2748.3403 C 166.47443 2759.4104 148.52646 2759.4104 137.4565 2748.3403 C 126.38647 2737.2703 126.38647 2719.3225 137.4565 2708.2524 C 148.52646 2697.1824 166.47443 2697.1824 177.54439 2708.2524" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(139.82329 2712.7964)" fill="black"><tspan font-family="Helvetica" font-size="26" font-weight="500" x="9.006262" y="25" textLength="17.341797">A</tspan></text></g><line x1="157.50027" y1="2788.45" x2="157.50037" y2="2757.1428" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="3"/><g id="id557_Graphic"><path d="M 167.52223 2793.1013 C 173.05725 2798.6362 173.05725 2807.6104 167.52223 2813.1453 C 161.98724 2818.6804 153.013245 2818.6804 147.47826 2813.1453 C 141.94324 2807.6104 141.94324 2798.6362 147.47826 2793.1013 C 153.013245 2787.5662 161.98724 2787.5662 167.52223 2793.1013" fill="black"/><path d="M 167.52223 2793.1013 C 173.05725 2798.6362 173.05725 2807.6104 167.52223 2813.1453 C 161.98724 2818.6804 153.013245 2818.6804 147.47826 2813.1453 C 141.94324 2807.6104 141.94324 2798.6362 147.47826 2793.1013 C 153.013245 2787.5662 161.98724 2787.5662 167.52223 2793.1013" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/></g><g id="id558_Graphic"><path d="M 167.52223 2838.6013 C 173.05725 2844.1362 173.05725 2853.1104 167.52223 2858.6453 C 161.98724 2864.1804 153.013245 2864.1804 147.47826 2858.6453 C 141.94324 2853.1104 141.94324 2844.1362 147.47826 2838.6013 C 153.013245 2833.0662 161.98724 2833.0662 167.52223 2838.6013" fill="black"/><path d="M 167.52223 2838.6013 C 173.05725 2844.1362 173.05725 2853.1104 167.52223 2858.6453 C 161.98724 2864.1804 153.013245 2864.1804 147.47826 2858.6453 C 141.94324 2853.1104 141.94324 2844.1362 147.47826 2838.6013 C 153.013245 2833.0662 161.98724 2833.0662 167.52223 2838.6013" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/></g><line x1="157.50024" y1="2817.7966" x2="157.50024" y2="2833.95" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><line x1="394.42325" y1="2798.7" x2="394.42328" y2="2764.2998" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="3"/><g id="id562_Graphic"><path d="M 404.44525 2803.3513 C 409.98026 2808.8862 409.98026 2817.8604 404.44525 2823.3953 C 398.91025 2828.9304 389.93625 2828.9304 384.40125 2823.3953 C 378.86624 2817.8604 378.86624 2808.8862 384.40125 2803.3513 C 389.93625 2797.8162 398.91025 2797.8162 404.44525 2803.3513" fill="black"/><path d="M 404.44525 2803.3513 C 409.98026 2808.8862 409.98026 2817.8604 404.44525 2823.3953 C 398.91025 2828.9304 389.93625 2828.9304 384.40125 2823.3953 C 378.86624 2817.8604 378.86624 2808.8862 384.40125 2803.3513 C 389.93625 2797.8162 398.91025 2797.8162 404.44525 2803.3513" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/></g><g id="id563_Graphic"><path d="M 404.44525 2848.8513 C 409.98026 2854.3862 409.98026 2863.3604 404.44525 2868.8953 C 398.91025 2874.4304 389.93625 2874.4304 384.40125 2868.8953 C 378.86624 2863.3604 378.86624 2854.3862 384.40125 2848.8513 C 389.93625 2843.3162 398.91025 2843.3162 404.44525 2848.8513" fill="black"/><path d="M 404.44525 2848.8513 C 409.98026 2854.3862 409.98026 2863.3604 404.44525 2868.8953 C 398.91025 2874.4304 389.93625 2874.4304 384.40125 2868.8953 C 378.86624 2863.3604 378.86624 2854.3862 384.40125 2848.8513 C 389.93625 2843.3162 398.91025 2843.3162 404.44525 2848.8513" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/></g><line x1="394.42325" y1="2828.0466" x2="394.42325" y2="2844.2" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><g id="id565_Graphic"><path d="M 414.46722 2715.4094 C 425.53726 2726.4795 425.53726 2744.4272 414.46722 2755.4973 C 403.39725 2766.5674 385.4493 2766.5674 374.37933 2755.4973 C 363.3093 2744.4272 363.3093 2726.4795 374.37933 2715.4094 C 385.4493 2704.3394 403.39725 2704.3394 414.46722 2715.4094" fill="red" fill-opacity=".5"/><path d="M 414.46722 2715.4094 C 425.53726 2726.4795 425.53726 2744.4272 414.46722 2755.4973 C 403.39725 2766.5674 385.4493 2766.5674 374.37933 2755.4973 C 363.3093 2744.4272 363.3093 2726.4795 374.37933 2715.4094 C 385.4493 2704.3394 403.39725 2704.3394 414.46722 2715.4094" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="8,5"/><text transform="translate(376.7461 2719.9534)" fill="black"><tspan font-family="Helvetica" font-size="26" font-weight="500" x="9.006262" y="25" textLength="17.341797">A</tspan></text></g><line x1="442.8926" y1="2681.3723" x2="422.98685" y2="2703.5828" marker-end="url(#FilledDiamond_Marker)" marker-start="url(#Ball_Marker)" stroke="red" stroke-linecap="round" stroke-linejoin="round" stroke-width="3" stroke-dasharray="1,4"/></g></g></svg>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/from-mq.rst	Mon Jul 30 14:45:42 2012 +0200
@@ -0,0 +1,172 @@
+-------------------------------------------
+From MQ To Evolve, The Refugee Book
+-------------------------------------------
+
+Cheat sheet
+-------------
+
+==============================  ============================================
+mq command                       new equivalent
+==============================  ============================================
+qseries                         ``log``
+qnew                            ``commit``
+qrefresh                        ``amend``
+qpop                            ``update`` or ``qdown``
+qpush                           ``update`` or ``gup`` sometimes ``stabilize``
+qrm                             ``prune``
+qfold                           ``amend -c`` (for now, ``collapse`` soon)
+qdiff                           ``odiff``
+
+qfinish                         --
+qimport                         --
+==============================  ============================================
+
+
+Replacement details
+---------------------
+
+hg qseries
+```````````
+
+All your work in progress is now in real changesets all the time.
+
+You can use the standard log command to display them. You can use the
+phase revset to display unfinished work only, and use templates to have
+the same kind of compact that the output of qseries has.
+
+This will result in something like that::
+
+  [alias]
+  wip = log -r 'not public()' --template='{rev}:{node|short} {desc|firstline}\n'
+
+hg qnew
+````````
+
+With evolve you handle standard changesets without an additional overlay.
+
+Standard changeset are created using hg commit as usual.::
+
+  $ hg commit
+
+If you want to keep the "WIP is not pushed" behavior, you want to
+set your changeset in the secret phase using the phase command.
+
+Note that you only need it for the first commit you want to be secret. Later
+commits will inherit their parents phase.
+
+If you always want your new commit to be in the secret phase, your should
+consider updating your configuration:
+
+  [phases]
+  new-commit=secret
+
+hg qref
+````````
+
+A new command from evolution will allow you to rewrite the changeset you are
+currently on. Just call:
+
+  $ hg amend
+
+This command takes the same options as commit, plus the switch '-e' (--edit)
+to edit the commit message in an editor.
+
+The amend command also has a -c switch which allow you to make an
+explicit amending commit before rewriting a changeset.::
+
+  $ hg record -m 'feature A'
+  # oups, I forget some stuff
+  $ hg record babar.py
+  $ hg amend -c .^ # .^ refer to "working directoy parent, here 'feature A'
+
+note: refresh is an alias for amend
+
+hg qref -X
+````````````
+
+To remove changes from you current commit use::
+
+  $ hg uncommit not-ready.txt
+
+
+hg qpop
+`````````
+
+The following command emulate the behavior of hg qpop:
+
+  $ hg gdown
+
+If you need to go back to an arbitrary commit you can use:
+
+  $ hg update
+
+.. note:: gdown and update allow movement with working directory changes applied
+          and gracefully merge them.
+
+hg qpush
+````````
+
+When you rewrite changesets, descendants of rewritten changesets are marked as
+"out of sync". You need to rewrite them on top of the new version of their
+ancestor.
+
+The evolution extension adds a command to rewrite the "out of sync"
+changesets:::
+
+  $ hg stabilize
+
+You can also decide to do it manually using::
+
+  $ hg graft -O <old-version>
+
+or::
+
+  $ hg rebase -r <revset for old version> -d .
+
+note: using graft allows you to pick the changeset you want next as the --move
+option of qpush do.
+
+
+hg qrm
+```````
+
+evolution introduce a new command to mark a changeset as "not wanted anymore".::
+
+  $ hg prune <revset>
+
+hg qfold
+`````````
+
+
+::
+
+  $ hg up <top changeset>
+  $ amend --edit -c <bottom changeset>
+
+
+or later::
+
+  $ hg collapse # XXX not implemented
+  $ hg rebase --collapse # XXX not tested
+
+
+hg qdiff
+`````````
+
+``odiff`` is an alias for `hg diff -r .^` it works as qdiff, but outside mq.
+
+
+
+hg qfinish and hg qimport
+````````````````````````````
+
+These are not necessary anymore. If you want to control exchange and
+mutability of changesets, see the phase feature
+
+
+
+hg qcommit
+```````````````
+
+If you really need to send patches through versioned mq patches, you should
+look at the qsync extension.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/index.rst	Mon Jul 30 14:45:42 2012 +0200
@@ -0,0 +1,166 @@
+========================================
+Safe Mutable History
+========================================
+
+
+Here are various materials on planned improvement to Mercurial regarding
+rewriting history.
+
+First read about what challenge arise while rewriting history and how we plan to
+solve them once and for all.
+
+.. toctree::
+   :maxdepth: 2
+
+   instability
+
+The effort is split in two parts:
+
+ * The **obsolete marker** concept aims to provide an alternative to ``strip``
+   to get rid of changesets.
+
+ * 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
+most users will never be directly exposed to the concept. For this reason
+this manual starts with changeset evolution.
+
+Evolve: A robust alternative to MQ
+====================================
+
+Evolve is an experimental history rewriting extension that uses obsolete
+markers. It is inspired from MQ and pbranch but have multiple advantages over
+them:
+
+* Focus on your current work.
+
+    You can focus your work on a single changeset and take care of adapting
+    descendent changeset later.
+
+* Handle **non-linear history with branches and merges**
+
+* Rely internally on **robust merge** mechanism of mercurial.
+
+  Simple conflict are handled by real merge tools using appropriate ancestor.
+  Conflict are much rarer and much more user friendly.
+
+*  Mutable history **fully available all the time**.
+
+  always use 'hg update' and forget about (un)applying patches to access the
+  mutable part of your history.
+
+
+* Use only **plain changeset** and forget about patches. Evole will create and
+  exchange real changesets. Mutable history can be used in all usual operations:
+  pull, push, log, diff, etc.
+
+* Allow **sharing and collaboration** mutable history without fear of duplicate
+  (thanks to obsolete marker).
+
+* Cover all mq usage but guard.
+
+.. warning:: The evolve extention and the obsolete marker are at an experimental
+            stage. While using obsolete you'll likely be exposed to complex
+            implication of the **obsolete marker** concept. I do not recommend
+            non-power user to test this at this stage.
+
+            XXX make sure to read the XXX section before using it.
+
+            Production ready version should hide such details to normal user.
+
+To enable the evolve extension use::
+
+    $ hg clone https://bitbucket.org/marmoute/mutable-history -u stable
+    $ mutable-history/enable.sh >> ~/.hgrc
+
+You will probably want to use the associated version of hgview (qt viewer
+recommended). ::
+
+    $ hg clone http://hg-lab.logilab.org/wip/hgview/ -u obsolete
+    $ cd hgview
+    $ python setup.py install --user
+
+Works with mercurial 2.2
+
+ ---
+
+For more information see documents below:
+
+.. toctree::
+   :maxdepth: 1
+
+   tutorials/tutorial
+   evolve-good-practice
+   evolve-faq
+   from-mq
+   evolve-collaboration
+   qsync
+
+Smart changeset deletion: Obsolete Marker
+==========================================
+
+Obsolete marker is a powerful concept that allow mercurial to safely handle
+history rewriting operations. It is a new type of relation between Mercurial
+changesets that track the result of history rewriting operations.
+
+This concept is simple to define and provides a very solid base to:
+
+- Very fast history rewriting operations,
+
+- auditable and reversible history rewriting process,
+
+- clean final history,
+
+- share and collaborate on mutable parts of the history,
+
+- gracefully handle history rewriting conflicts,
+
+- allow various history rewriting UI to collaborate with a underlying common API.
+
+ ---
+
+For more information see documents below
+
+.. toctree::
+   :maxdepth: 1
+
+   obs-concept
+   obs-terms
+   obs-implementation
+   obs-road-map
+
+
+Known limitation and bug
+=================================
+
+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.
+
+  Only ``hg log``, ``hgview`` and `hg glog` support it. ``hg head`` or other visual viewer don't.
+
+* hg heads show extinct changeset
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/instability.rst	Mon Jul 30 14:45:42 2012 +0200
@@ -0,0 +1,221 @@
+
+-----------------------------------
+The instability Principle
+-----------------------------------
+
+
+
+An intrinsic contradiction
+-----------------------------------
+
+XXX starts by talking about getting ride of changeset.
+
+DVCSes bring two new major concepts to the Version Control Scene:
+
+    * History is organized as a robust DAG,
+    * History can be rewritten.
+
+However, the two concepts are in contradiction:
+
+To achieve a robust history, three key elements are gathered in *changesets*:
+
+    * Full snapshot of the versioned content,
+    * Reference to the previous full snapshot used to build the new one,
+    * A description of the change who lead from the old content to the new old.
+
+All three elements are to compute a *unique* hash that identify the changeset
+(with various other metadata). This identification is a key part of DVCS design.
+
+This is a very useful property because Changing B parent means
+changing B content too. This requires the creation of **another**
+changeset, which is semantically good.
+
+::
+
+  Schema base,  A, B and B'
+
+To avoid duplication, the older changeset is usually discarded from accessible
+history. I'm calling them *obsolete* changesets.
+
+
+But rewriting a changeset with children does not change these
+children's parent! And because children of the rewritten changeset
+still **depend** on the older "dead" version of the changeset with
+can not get rid of this dead version.
+
+::
+
+  Schema base,  A and A' and B.
+
+I'm calling these children **unstable** because they are based on a
+dead changeset and prevent people to get rid of it.
+
+This instability is an **unavoidable consequence** of the strict dependency of
+changeset.  History Rewriting history alway  need to take it in account and
+provide a way to rewrite the descendant on the new changeset to avoid
+coexistence of the old and new version of a rewritten changeset.
+
+
+Everybody is working around the issue
+------------------------------------------------
+
+I'm not claiming that rewriting history is impossible. People are successfully
+doing for years. However they all need to work around *instability*. Several
+work around strategy exists.
+
+
+Rewriting all at once
+``````````````````````````
+
+The simplest way to avoid instability is to ensure rewriting
+operations always end in a stable situation. This is achieved by
+rewriting all affected changesets at the same time.
+
+Rewriting all descendants at the same time when rewriting a changeset.
+
+::
+
+  Schema!
+
+Several Mercurial commands apply it: rebase, collapse, histedit.
+Mercurial also refuses to amend changeset with descendant. The git
+branch design enforces such approach in git too.
+
+
+However, DVCS are **Distributed**. This means that you do not control what
+happen outside your repository. Once a changeset have been exchanged *outside*,
+there is no way to be sure it does not have descendants somewhere else.
+Therefore **if you rewrite changeset that exists elsewhere, you can't eradicate
+the risk of instability.**
+
+Do not rewrite exchanged changeset
+```````````````````````````````````
+
+To work around the issue above, Mercurial introduced phases, which
+prevent you from rewriting shared changesets and ensure others can't
+pull certain changesets from you. But this is a very frustrating
+limitation that prevents you to efficiently sharing, reviewing and
+collaborating on mutable changesets.
+
+In the Git world, they use another approach to prevent instability. By
+convention only a single developper works on a changeset contained in
+a named branch. But once again this is a huge blocker for
+collaborating. Moreover clueless people **will** mess up social
+convention soon or later.
+
+
+Loose the DAG robustness
+````````````````````````````
+
+The other approach in Mercurial is to keep the mutable part of the
+history outside the DVCS constraint. This is the MQ approach of
+sticking a quilt queue over Mercurial.
+
+This allow much more flexible workflow but two major feature are lost in the
+process:
+
+:Graceful merge: MQ use plain-patch to store changeset content and patch have
+                 trouble to apply in changing context. Applying your queue
+                 becomes very painful when context changes.
+
+:easy branching: A quilt queue is by definition a linear queue. Increasing risk
+                 of conflict
+
+It is possible to collaborate over versioned mq! But you are going to
+have a lot of troubles.
+
+Ignore conflicts
+```````````````````````````````````
+ 
+Another ignored issue is a conflicting rewrite of the same changeset.
+If a changeset is rewritten two times we have two newer versions,
+and duplicated history is complicated to merge.
+
+Mercurial work around by
+
+The "One set of mutable changset == One developer" mantra is also a way to work
+around conflicting rewriting of changeset. If two different people are able to
+
+The git branch model allow to overwrite changeset version by another
+one, but it does not care about divergent version. It is the equivalent
+of "common ftp" source management for changesets.
+
+Facing The Danger Once And For All
+------------------------------------------------
+
+Above we saw that, the more effort you put to avoid instability, the more option
+you deny. And even most restrictive work flow can't guarantee that instability
+will never show up!
+
+Obsolete marker can handle the job
+```````````````````````````````````
+
+It is time to provide a full featured solution to deal with
+instability and to stop working around the issue! This is why I
+developing a new feature for mercurial called "Obsolete markers".
+Obsolete markers have two key properties:
+
+
+* Any "old" changeset we want to get ride of is **explicitly** marked
+  as "obsolete" by history rewriting operation.
+
+  By explicitly marking the obsolete part of the history, we will be able to
+  easily detect instability situation.
+
+* Relations between old and new version of changesets are tracked by obsolete
+  markers.
+
+  By Storing a meta-history of changeset evolution we are able to easily resolve
+  instability and edition conflict [#]_ .
+
+.. [#] edition conflict is another major obstable to collaboration. See the
+       section dedicated to obsolete marker for details.
+
+Improves robustness == improves simplicity
+````````````````````````````````````````````````
+
+This proposal should **first** be seen as a safety measure.
+
+It allow to detect instability as soon as possible
+
+::
+
+    $ hg pull
+    added 3 changeset
+    +2 unstable changeset
+    (do you want "hg stabilize" ?)
+    working directory parent is obsolete!
+    $ hg push
+    outgoing unstable changesets
+    (use "hg stabilize" or force the push)
+
+And should not not encourage people to create instability
+
+::
+
+    $ hg up 42
+    $ hg commit --amend
+    changeset have descendant.
+    $ hg commit --amend -f
+    +5 unstable changeset
+
+    $ hg rebase -D --rev 40::44
+    rebasing already obsolete changeset 42:AAA will conflict with newer version 48:BBB
+
+While allowing powerful feature
+````````````````````````````````````````````````
+
+
+* Help to automatically solve instability.
+
+* "prune" changeset remotely.
+
+* track resulting changeset when submitting patch//pull request.
+
+* Focus on what you do:
+
+  I do not like the "all at once" model of history rewriting. I'm comfortable
+  with instability and obsolete marker offer all the tool to safely create and
+  handle instability locally.
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/makefile	Mon Jul 30 14:45:42 2012 +0200
@@ -0,0 +1,6 @@
+
+all: tutorial
+	sphinx-build . ../html/
+
+tutorial:
+	python test2rst.py tutorials/
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/obs-concept.rst	Mon Jul 30 14:45:42 2012 +0200
@@ -0,0 +1,393 @@
+-----------------------------------------------------------
+Why Do We Need a New Concept
+-----------------------------------------------------------
+
+Current DVCSes are great tools for forging a series of flawless
+changesets on your own. But they perform poorly when it comes to
+**sharing** some work in progress and **collaborating** on such work
+in progress.
+
+When people forge a new version of a changeset they actually create a
+new changeset and get rid of the original changeset. Difficulties to
+collaborate mostly came from the way old content is *removed* from
+a repository.
+
+Mercurial Approach: Strip
+-----------------------------------------------------
+
+With the current version of mercurial, every changeset that exists in
+your repository is *visible* and *meaningful*. To delete old
+(rewritten) changesets, mercurial removes them from the repository
+storage with an operation called *strip*. After the *stripping*, the
+repository looks as if the changeset never existed.
+
+This approach is simple and effective except for one big
+drawback: you can remove changesets from **your repository only**. If
+a stripped changeset exists in another repository it touches, it will
+show up again. This is because a shared changeset becomes
+part of a shared global history. Stripping a changeset from all
+repositories is at best impractical and in most case impossible.
+
+As consequence, **you can not rewrite something once you exchange it with
+others**. The old version will still exist along side the new one [#]_.
+
+Moreover stripping changesets creates backup bundles. This allows
+restoration of the deleted changesets, but the process is painful.
+
+Finally, as the repository format is not optimized for deletion. stripping a
+changeset may be slow in some situations.
+
+To sum up, the strip approach is very simple but does not handle
+interaction with the outer world, which is very unfortunate for a
+*Distributed* VCS.
+
+.. [#] various work around exists but they require their own workflows
+   which are distinct from the very elegant basic workflow of
+   Mercurial.
+
+Git Approach: Overwrite Reference
+-----------------------------------------------------
+
+The Git approach to repository structure is a bit more complex: there
+can be any amount of unrelated changesets in a repository, and **only
+changesets referenced by a git branch** are *visible* and
+*meaningful*.
+
+
+.. warning:: add a schema::
+
+        C
+        | B---<foo>
+        |/
+        |
+        A
+
+    Only B and A are visible.
+
+This simplifies the process of getting rid of old changesets. You can
+just leave them in place and move the reference on the new one. You
+can then propagate this change by moving the git-branch on remote host
+with the newer version of the marker overwriting the older one.
+
+This approach goes a bit further but still has a major drawback:
+
+Because you **overwrite** the git-branch, you have no conflict
+resolution. The last to act wins. This makes collaboration on multiple
+changesets difficult because you can't merge concurrent updates on a
+changeset.
+
+Every overwrite is a forced operation where the operator says, "yes I
+want this to replace that". In highly distributed environments, a user
+may end up with conflicting references and no proper way to choose.
+
+Because of this way to visualize a repository, git-branches are a core
+part of git, which makes the user interface more complicated and
+constrains moving through history.
+
+Finally, even if all older changesets still exist in the repository,
+accesing them is still painful.
+
+
+-----------------------------------------------------
+The Obsolete Marker Concept
+-----------------------------------------------------
+
+
+As none of the concepts was powerful enough to fulfill the need of
+safely rewriting history, including easy sharing and collaboration on
+mutable history, we needed another one.
+
+Basic concept
+-----------------------------------------------------
+
+
+Every history rewriting operation stores the information that old rewritten
+changeset is replaced by newer version in a given set of changesets.
+
+All basic history rewriting operation can create an appropriate obsolete marker.
+
+
+.. figure:: ./figures/example-1-update.*
+
+    *Updating* a changeset
+
+    Create one obsolete marker: ``([A'] obsolete A)``
+
+
+
+.. figure:: ./figures/example-2-split.*
+
+    *Splitting* a changeset in multiple one
+
+    Create one obsolete marker ``([B1, B2] obsolete B)]``
+
+
+.. figure:: ./figures/simple-3-merge.*
+
+    *Merging* multiple changeset in a single one
+
+    Create two obsolete markers ``([C] obsolete A), ([C] obsolete B)``
+
+.. figure:: ./figures/simple-4-reorder.*
+
+    *Moving* changeset around
+
+    Reordering those two changesets need two obsolete markers:
+    ``([A'] obsolete A), ([B'] obsolete B)``
+
+
+
+.. figure:: ./figures/simple-5-delete.*
+
+    *Removing* a changeset:
+
+    One obselete marker ``([] obsolete B)``
+
+
+To conclude, a single obsolete marker express a relation from **0..n** new
+changesets to **1** old changeset.
+
+Basic Usage
+-----------------------------------------------------
+
+Obsolete markers create a perpendicular history: **a versioned
+changeset graph**. This means that offers the same features we have
+for versioned files but applied to changeset:
+
+First: we can display a **coherent view** of the history graph in which only a
+single version of your changesets is displayed by the UI.
+
+Second, because obsolete changeset content is still **available**. You can 
+you can
+
+    * **browse** the content of your obsolete commits,
+
+    * **compare** newer and older versions of a changeset,
+
+    * **restore** content of previously obsolete changesets.
+
+Finally, the obsolete marker can be **exchanged between
+repositories**. You are able to share the result on your history
+rewriting operations with other prople and **collaborate on the
+mutable part of the history**.
+
+Conflicting history rewriting operation can be detected and
+**resolved** as easily as conflicting changes on a file.
+
+
+Detecting and solving tricky situations
+-----------------------------------------------------
+
+History rewriting can lead to complex situations. The obsolete marker
+introduces a simple representation for this complex reality. But
+people using complex workflows will one day or another have to face
+the intrinsic complexity of some real-world situation.
+
+This section describes possible situations, defines precise sets of
+changesets involved in such situations and explains how the error
+cases can be resolved automatically using the available information.
+
+
+Obsolete changesets
+````````````````````
+
+Old changesets left behind by obsolete operation are called **obsolete**.
+
+With the current version of mercurial, this *obsolete* part is stripped from the
+repository before the end of every rewriting operation.
+
+.. figure:: ./figures/error-obsolete.*
+
+    Rebasing `B` and `C` on `A` (as `B'`, `C'`)
+
+    This rebase operation added two obsolete markers from new
+    changesets to old changesets. These two old changesets are now
+    part of the *obsolete* part of the history.
+
+In most cases, the obsolete set will be fully hidden to both the UI and
+discovery, hence users do not have to care about them unless they want to
+audit history rewriting operations.
+
+Unstable changesets
+```````````````````
+
+While exploring the possibilities of the obsolete marker a bit
+further, you may end up with *obsolete* changesets which have
+*non-obsolete* children. There is two common ways to achieve this:
+
+* Pull a changeset based of an old version of a changeset [#]_.
+
+* Use a partial rewriting operation. For example amend on a changeset with
+  children.
+
+*Non-obsolete* changeset based on *obsolete* one are called **unstable**
+
+.. figure:: ./figures/error-unstable.*
+
+    Amend `A` into `A'` leaving `B` behind.
+
+    In this situation we cannot consider `B` as *obsolete*. But we
+    have all the necessary data to detect `B` as an *unstable* branch
+    of the history because its parent `A` is *obsolete*. In addition,
+    we have enough data to automatically resolve this instability: we
+    know that the new version of `B` parent (`A`) is `A'`. We can
+    deduce that we should rebase `B` on `A'` to get a stable history
+    again.
+
+Proper warnings should be issued when part of the history becomes
+unstable. The UI will be able to use the obsolete marker to
+automatically suggest a resolution to the user of even carry them out
+for them.
+
+
+XXX details on automatic resolution for
+
+* movement
+
+* handling deletion
+
+* handling split on multiple head
+
+
+.. [#] For this to happen one needs to explicitly enable exchange of draft
+       changesets. See phase help for details.
+
+The two parts of the obsolete set
+``````````````````````````````````````
+
+The previous section shows that there could be two kinds of *obsolete*
+changesets:
+
+* an *obsolete* changeset with no or *obsolete* only descendants is called **extinct**.
+
+* an *obsolete* changeset with *unstable* descendants is called **suspended**.
+
+
+.. figure:: ./figures/error-extinct.*
+
+    Amend `A` and `C` leaving `B` behind.
+
+    In this example we have two *obsolete* changesets: `C` with no *unstable*
+    children is *extinct*. `A` with *unstable* descendant (`B`) is *suspended*.
+    `B` is *unstable* as before.
+
+
+Because nothing outside the obsolete set default on *extinct*
+changesets, they can be safely hidden in the UI and even garbage
+collected. *Suspended* changesets have to stay visible and available
+until their unstable descendant are rewritten into stable version.
+
+
+Conflicting rewrites
+````````````````````
+
+If people start to concurrently edit the same part of the history they will
+likely meet conflicting situations when a changeset has been rewritten in two
+different ways.
+
+
+.. figure:: ./figures/error-conflicting.*
+
+    Conflicting rewrite of `A` into `A'` and `A''`
+
+This kind of conflict is easy to detect with an obsolete marker
+because an obsolete changeset can have more than one new version. It
+may be seen as the multiple heads case. Mercurial warns you about this
+on pull. It is resolved the same way by a merge of A' and A'' that
+will keep the same parent than `A'` and `A''` with two obsolete
+markers pointing to both `A` and `A'`
+
+.. warning::  TODO: Add a schema of the resolution. (merge A' and A'' with A as
+              ancestor and graft the result of A^)
+
+Allowing multiple new changesets to obsolete a single one allows to
+distinguish a split changeset from a history rewriting conflict.
+
+Reliable history
+``````````````````````
+
+Obsolete markers help to smooth rewriting operation process. However
+they do not change the fact that **you should only rewrite the mutable
+part of the history**. The phase concept enforces this rule by
+explicitly defining a public immutable set of changesets. Rewriting
+operations refuse to work on public changesets, but there are still
+some corner cases where previously rewritten changesets are made
+public.
+
+Special rules apply for obsolete markers pointing to public changesets:
+
+* Public changesets are excluded from the obsolete set (public
+  changesets are never hidden or candidate to garbage collection)
+
+* *newer* version of a public changeset are called **latecomer** and
+  highlighted as an error case.
+
+Solving such an error is easy. Because we know what changeset a
+*latecomer* tries to rewrite, we can easily compute a smaller
+changeset containing only the change from the old *public* to the new
+*latecomer*.
+
+.. warning:: add a schema
+
+
+Conclusion
+----------------
+
+The obsolete marker is a powerful concept that allows mercurial to safely handle
+history rewriting operations. It is a new type of relation between Mercurial
+changesets which tracks the result of history rewriting operations.
+
+This concept is simple to define and provides a very solid base for:
+
+
+- Very fast history rewriting operations,
+
+- auditable and reversible history rewriting process,
+
+- clean final history,
+
+- sharing and collaborating on the mutable part of the history,
+
+- gracefully handling history rewriting conflicts,
+
+- various history rewriting UI's collaborating with an underlying common API.
+
+.. list-table:: Comparison on solution [#]_
+   :header-rows: 1
+
+   * - Solution
+     - Remove changeset locally
+     - Works on any point of your history
+     - Propagation
+     - Collaboration
+     - Speed
+     - Access to older version
+
+   * - Strip
+     - `+`
+     - `+`
+     - \
+     - \ 
+     - \ 
+     - `- -`
+
+   * - Reference
+     - `+`
+     - \ 
+     - `+`
+     - \ 
+     - `+`
+     - `-`
+
+   * - Obsolete
+     - `+`
+     - `+`
+     - `++`
+     - `++`
+     - `+`
+     - `+`
+
+
+
+.. [#] To preserve good tradition in comparison table, an overwhelming advantage
+       goes to the defended solution.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/obs-implementation.rst	Mon Jul 30 14:45:42 2012 +0200
@@ -0,0 +1,213 @@
+
+-----------------------------------------------------
+Implementation of Obsolete Marker
+-----------------------------------------------------
+.. warning:: This document is still in heavy work in progress
+
+Main questions about Obsolete Marker Implementation
+-----------------------------------------------------
+
+
+What data should be contained in a Marker ?
+````````````````````````````````````````````````````
+
+There are two critical pieces of information that **must** be stored
+in an obsolete Marker.
+
+:object:
+    the old obsoleted changeset
+
+:replacements:
+    list of new changeset. list size can be anything, including 0 (0..N)
+
+Everybody agreed on this point.
+
+ ---
+
+This is probably a good idea to have an unique Identifier, for UI, transfer and
+access.
+
+    :id: same as changeset but for marker.
+
+The field below will depend on the way we exchange obsolete marker between
+changesets.
+
+ ---
+
+Having audit data will be very useful. When it gets messy you need all the
+information available to understand the situation.
+
+I have the feeling that we are versioning history. Therefor we will probably
+need the same kind of information than when versioning Files.
+
+:date: date of the marker creation
+
+:user: ui.username
+
+To go further:
+
+:description: "Optional reason for the rewrite (generated by the user)"
+
+:tool: the automated tool that made this
+
+:operation: Kind of rewritting operation that created the marker (delete,
+            update, split, fold, reordering), to help conflict resolution.
+
+Matt said this is "too complicated". I'll wait for him to meet a very hairy
+situation to agree that they are needed.
+
+Leaving the door open to any addition data is an option too.
+
+How shall we store Marker on disk
+`````````````````````````````````````````````````````````
+
+Requirement
+.............
+
+We need to quickly load the 'object' to know the "obsolete" set.
+We need quick access by object and replacements to travels along the graph.
+
+Common Part
+.............
+
+The file is store in `.hg/store/obsmarkers`. It is a binary files:
+
+The files starts with a Format Version string
+
+
+Minimalistic proposal
+.........................
+
+The core of a Marker will we stored as:
+
+* number of replacement (8-Bytes integer)
+* node id of the obsolete changeset (20-Bytes hash)
+* node id of replacement changeset (20-Bytes hash x number of remplacement)
+
+Version with ID
+.........................
+
+This version add a node id computed from the marker content. It will be present
+*before* other data:
+
+* node id of the maker (20-Bytes hash)
+
+
+Version with Metadata proposal
+...............................
+
+An extra files is used to old metadata (date, user, etc) `.hg/store/obs-extra`:.
+
+The format of this field is undefined yet. This will add the following
+field at the end of a marker
+
+* offset of the metadata in obs-extra (8-Bytes integer)
+
+
+How shall we exchange Marker over the Wire ?
+`````````````````````````````````````````````````````````
+
+We can have a lot of markers. We do not want to exchange data for the one we
+already know. Listkey() is not very appropriate there as you get everything.
+
+Moreover, we might want to only hear about Marker that impact changeset we are
+pulling.
+
+pushkey is not batchable yet (could be fixed)
+
+A dedicated discovery and exchange protocol seems mandatory here.
+
+
+Various technical details
+-----------------------------------------------------
+
+Some stuff that worse to note. some may deserve their own section later.
+
+storing old changeset
+``````````````````````
+
+The new general delta format allows a very efficient storage of two very similar
+changesets. Storing obsolete children using general delta takes no more place
+than storing the obsolete diff. Reverted file will even we reused. The whole
+operation will take much less space the strip backup.
+
+
+Abstraction from history rewriting UI
+```````````````````````````````````````````
+
+How Mercurial handles obsolete marker is independent from what decides
+to create them and what actual operation solves the error case. Any of
+the existing history rewriting UI (rebase, mq, histedit) can lay
+obsolete markers and resolve situation created by others. To go
+further, a hook system of obsolete marker creation would allow each
+mechanism to collaborate with other though a standard and central
+mechanism.
+
+
+Obsolete marker storage
+```````````````````````````
+
+The Obsolete marker will most likely be stored outside standard
+history. They are multiple reasons for this:
+
+First, obsolete markers are really perpendicular to standard history
+there is no strong reason to include it here other than convenience.
+
+Second, storing obsolete marker inside standard history means:
+
+* A changeset must be created every time an obsolete relation is added. Very
+  inconvenient for delete operation.
+
+* Obsolete marker must be forged at the creation of the new changeset. This
+  is very inconvenient for split operation. And in general it becomes
+  complicated to fix history afterward in particular when working with older
+  clients.
+
+Storing obsolete marker outside history have several pros:
+
+* It eases Exchange of obsolete markers without unnecessary obsolete
+  changeset contents.
+
+* It allows tuning the actual storage and protocol exchange while maintaining
+  compatibility with older clients through the wire (as we do the repository
+  format).
+
+* It eases the exchange of obsolete related information during
+  discovery to exchange obsolete changeset relevant to conflict
+  resolution. Exchanging such information deserves a dedicated
+  protocol.
+
+Persistent
+```````````````````````
+
+*Extinct* changeset and obsolete marker will most likely be garbage collected as
+some point. However, archive server may decide to keep them forever in order to
+keep a fully auditable history in its finest conception.
+
+
+Current status
+-----------------------------------------------------
+
+An experimental implementatione exists. What have been done so far.
+
+
+* 1-1 obsolete marker stored outside history,
+
+* compute obsolete-tip
+
+* obsolete marker exchange through pushkey,
+
+* compute obsolete, unstable, extinct and suspended set.
+
+* hidden extinct changesets for UI.
+
+* Use secret phase to remove from discovery obsolete and unstable changesets (to
+  be improved soon)
+
+* alter rebase to use obsolete markers instead of stripping.
+
+* Have an experimental mq-like extension to rewrite history (more on that later)
+
+* Have an extension to update and mq repository according evolution of
+  standard (more on that later)
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/obs-terms.rst	Mon Jul 30 14:45:42 2012 +0200
@@ -0,0 +1,239 @@
+-----------------------------------------------------------
+Terminology of the obsolete concept
+-----------------------------------------------------------
+
+Obsolete markers
+---------------------------------
+
+The mutable concept is based on **obsolete markers**. Creating an obsolete
+marker registers a relation between an old obsoleted changeset and its newer
+version.
+
+Old changesets are called **precursors** while their new versions are called
+**successors**. A marker always registers a single *precursor* and:
+
+- no *successor*: the *precursor* is just discarded.
+- one *successor*: the *precursor* has been rewritten
+- multiple *successors*: the *precursor* were splits in multiple
+  changesets.
+
+.. The *precursors* and *successors* terms can be used on changeset directy:
+
+.. :precursors: of a changeset `A` are changesets used as *precursors* by
+..              obsolete marker using changeset `A` as *successors*
+
+.. :successors: of a changeset `B` are changesets used as *successors* by
+..              obsolete marker using changeset `B` as *precursors*
+
+Chaining obsolete markers is allowed to rewrite a changeset that is already a
+*successor*. This is a kind of *second order version control*.
+To clarify ambiguous situations one can use **direct precursors** or
+**direct successors** to name changesets that are directly related.
+
+The set of all *obsolete markers* forms a direct acyclic graph the same way
+standard *parents*/*children* relation does. In this graph we have:
+
+:any precursors: are transitive precursors of a changeset: *direct precursors*
+                 and *precursors* of *precursors*.
+
+:any successors: are transitive successors of a changeset: *direct successors*
+                 and *successors*  of *successors*)
+
+Obsolete markers may refer changesets that are not known locally.
+So, *direct precursors* of a changeset may be unknown locally.
+This is why we usually focus on the **first known precursors**  of the rewritten
+changeset. The same apply for *successors*.
+
+Changeset in *any successors* which are not **obsolete** are called
+**newest successors**..
+
+.. note:: I'm not very happy with this naming scheme and I'm looking for a
+          better distinction between *direct successors* and **any successors*.
+
+Possible changesets "type"
+---------------------------------
+
+The following table describes names and behaviors of changesets affected by
+obsolete markers. The left column describes generic categories and the right
+columns are about sub-categories.
+
+
++---------------------+--------------------------+-----------------------------+
+| **mutable**         | **obsolete**             | **extinct**                 |
+|                     |                          |                             |
+| Changeset in either | Obsolete changeset is    | *extinct* changeset is      |
+| *draft* or *secret* | *mutable* used as a      | *obsolete* which has only   |
+| phase.              | *precursor*.             | *obsolete* descendants.     |
+|                     |                          |                             |
+|                     | A changeset is used as   | They can safely be:         |
+|                     | a *precursor* when at    |                             |
+|                     | least one obsolete       | - hidden in the UI,         |
+|                     | marker refers to it      | - silently excluded from    |
+|                     | as precursors.           |   pull and push operations  |
+|                     |                          | - mostly ignored            |
+|                     |                          | - garbage collected         |
+|                     |                          |                             |
+|                     |                          +-----------------------------+
+|                     |                          |                             |
+|                     |                          | **suspended**               |
+|                     |                          |                             |
+|                     |                          | *suspended* changeset is    |
+|                     |                          | *obsolete* with at least    |
+|                     |                          | one non-obsolete descendant |
+|                     |                          |                             |
+|                     |                          | Thoses descendants prevent  |
+|                     |                          | properties of extincts      |
+|                     |                          | changesets to apply. But    |
+|                     |                          | they will refuse to be      |
+|                     |                          | pushed without --force.     |
+|                     |                          |                             |
+|                     +--------------------------+-----------------------------+
+|                     |                          |                             |
+|                     | **troublesome**          | **unstable**                |
+|                     |                          |                             |
+|                     | *troublesome* 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   |
+|                     | column. It is possible   | solve the problem.          |
+|                     | for *troublesome*        |                             |
+|                     | changeset to combine     | (possible alternative name: |
+|                     | multiple issue at once.  | precarious)                 |
+|                     | (a.k.a. conflicting and  |                             |
+|                     | unstable)                +-----------------------------+
+|                     |                          |                             |
+|                     | (possible alternative    | **latecomer**               |
+|                     | names: unsettled,        |                             |
+|                     | troubled)                | *latecomer* is a changeset  |
+|                     |                          | that tries to be successor  |
+|                     |                          | of  public changesets.      |
+|                     |                          |                             |
+|                     |                          | Public changeset can't      |
+|                     |                          | be deleted and replace      |
+|                     |                          | *latecomer*                 |
+|                     |                          | need to be converted into   |
+|                     |                          | an overlay to this public   |
+|                     |                          | changeset.                  |
+|                     |                          |                             |
+|                     |                          | (possible alternative names:|
+|                     |                          | mislead, naive, unaware,    |
+|                     |                          | mindless, disenchanting)    |
+|                     |                          |                             |
+|                     |                          +-----------------------------+
+|                     |                          | **conflicting**             |
+|                     |                          |                             |
+|                     |                          | *conflicting* is changeset  |
+|                     |                          | that appears when multiple  |
+|                     |                          | changesets are successors   |
+|                     |                          | of the same precursor.      |
+|                     |                          |                             |
+|                     |                          | *conflicting* are solved    |
+|                     |                          | through a three ways merge  |
+|                     |                          | between the two             |
+|                     |                          | *conflictings*,             |
+|                     |                          | using the last "obsolete-   |
+|                     |                          | -common-ancestor" as the    |
+|                     |                          | base.                       |
+|                     |                          |                             |
+|                     |                          | (*splitting* is             |
+|                     |                          | properly not detected as a  |
+|                     |                          | conflict)                   |
+|                     |                          |                             |
+|                     +--------------------------+-----------------------------+
+|                     |                                                        |
+|                     | Mutable changesets which are neither *obsolete* or     |
+|                     | *troublesome* are *"ok"*.                              |
+|                     |                                                        |
+|                     | Do we really need a name for it ? *"ok"* is a pretty   |
+|                     | crappy name :-/ other possibilities are:               |
+|                     |                                                        |
+|                     | - stable (confusing with stable branch)                |
+|                     | - sane                                                 |
+|                     | - healthy                                              |
+|                     |                                                        |
++---------------------+--------------------------------------------------------+
+|                                                                              |
+|     **immutable**                                                            |
+|                                                                              |
+| Changesets in the *public* phases.                                           |
+|                                                                              |
+| Rewriting operation refuse to work on immutable changeset.                   |
+|                                                                              |
+| Obsolete markers that refer an immutable changeset as precursors have        |
+| no effect on the precussors but may have effect on the successors.           |
+|                                                                              |
+| When a *mutable* changeset becomes *immutable* (changing its phase from draft|
+| to public) it is just *immutable* and loose any property of it's former      |
+| state.                                                                       |
+|                                                                              |
+| The phase properties says that public changesets stay as *immutable* forever.|
+|                                                                              |
++------------------------------------------------------------------------------+
+
+.. note:: I'm not very happy with the naming of:
+
+          - "ok" changeset
+          - latecomer
+          - troublesome
+
+          Any better idea are welcome.
+
+
+Command and operation name
+---------------------------------
+
+
+Existing terms
+``````````````
+
+Mercurial core already uses the following terms:
+
+:amend: to rewrite a changeset
+:graft: to copy a changeset
+:rebase: to move a changeset
+
+
+Uncommit
+`````````````
+
+Remove files from a commit (and leave them as dirty in the working directory)
+
+The *evolve* extension have an `uncommit` command that aims to replace most
+`rollback` usage.
+
+Fold
+``````````
+
+Collapse multiple changesets into a unique one.
+
+The *evolve* extension will have a `fold` command.
+
+Prune
+``````````
+
+Make a changeset obsolete without successors.
+
+This an important operation as it should mostly replace *strip*.
+
+Alternative names:
+
+- kill: shall has funny effects when you forget "hg" in front of ``hg kill``.
+- obsolete: too vague, too long and too generic.
+
+Stabilize
+```````````````
+
+Automatically resolve *troublesome* changesets
+(*unstable*, *latecomer* and *conflicting*)
+
+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)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/qsync.rst	Mon Jul 30 14:45:42 2012 +0200
@@ -0,0 +1,16 @@
+---------------------------------------------------------------------
+Qsync: Mercurial to MQ exporter
+---------------------------------------------------------------------
+
+
+People may have tools or co-workers that export to receive mutable history using
+a versioned mq repository.
+
+For this purpose you can use the ``qsync`` extension:
+
+
+To enable the evolve extension use::
+
+    $ hg clone http://hg-dev.octopoid.net/hgwebdir.cgi/mutable-history/
+    $ mutable-history/iqsync-enable.sh >> ~/.hgrc
+    $ hg help qsync
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/test2rst.py	Mon Jul 30 14:45:42 2012 +0200
@@ -0,0 +1,64 @@
+#!/usr/bin/env python
+
+import os, os.path as op, re, sys
+
+# line starts with two chars one of which is not a space (and both are not
+# newlines obviously) and ends with one or more newlines followed by two spaces
+# on a next line (indented text)
+CODEBLOCK = re.compile(r'()\n(([^ \n][^\n]|[^\n][^ \n])[^\n]*)\n+  ')
+
+INDEX = '''
+Mercurial tests
+===============
+
+.. toctree::
+   :maxdepth: 1
+'''
+
+
+def rstify(orig, name):
+    header = '%s\n%s\n\n' % (name, '=' * len(name))
+    content = header + orig
+    content = CODEBLOCK.sub(r'\n\1\n\n::\n\n  ', content)
+    return content
+
+
+def main(base):
+    if os.path.isdir(base):
+        one_dir(base)
+    else:
+        print one_file(base)
+
+
+def one_dir(base):
+    index = INDEX
+    #doc = lambda x: op.join(op.dirname(__file__), 'docs', x)
+
+    for fn in sorted(os.listdir(base)):
+        if not fn.endswith('.t'):
+            continue
+        print fn
+        name = os.path.splitext(fn)[0]
+        content = one_file(op.join(base, fn))
+        target = op.join(base, name + '.rst')
+        #with file(doc(name + '.rst'), 'w') as f:
+        with file(target, 'w') as f:
+            f.write(content)
+        print f
+
+        index += '\n   ' + name
+
+    #with file(doc('index.rst'), 'w') as f:
+    #    f.write(index)
+
+
+def one_file(path):
+    name = os.path.basename(path)[:-2]
+    return rstify(file(path).read(), name)
+
+
+if __name__ == '__main__':
+    if len(sys.argv) != 2:
+        print 'Please supply a path to tests dir as parameter'
+        sys.exit()
+    main(sys.argv[1])
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/tutorials/tutorial.t	Mon Jul 30 14:45:42 2012 +0200
@@ -0,0 +1,775 @@
+
+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 unstables 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 stabilize' 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 stabilize" command will make this for you.
+
+It has a --dry-run option to only suggest the next move.
+
+  $ hg stabilize --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 unstables 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
+
+# XXX make prune stabilization works
+#  $ hg stabilize --any
+#  merging shopping
+
+  $ hg graft -O ae45c0c3092a
+  grafting revision 17
+  merging shopping
+
+  $ hg log -G
+  @  20de1fb1cec5 (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.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/enable.sh	Mon Jul 30 14:45:42 2012 +0200
@@ -0,0 +1,52 @@
+#!/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/evolve.py	Mon Jul 30 14:45:42 2012 +0200
@@ -0,0 +1,674 @@
+# states.py - introduce the state concept for mercurial changeset
+#
+# Copyright 2011 Peter Arrenbrecht <peter.arrenbrecht@gmail.com>
+#                Logilab SA        <contact@logilab.fr>
+#                Pierre-Yves David <pierre-yves.david@ens-lyon.org>
+#
+# 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'''
+
+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.i18n import _
+from mercurial.commands import walkopts, commitopts, commitopts2, logopts
+from mercurial import hg
+
+### util function
+#############################
+
+def noderange(repo, revsets):
+    """The same as revrange but return node"""
+    return map(repo.changelog.node,
+               scmutil.revrange(repo, revsets))
+
+### changeset rewriting logic
+#############################
+
+def rewrite(repo, old, updates, head, newbases, commitopts):
+    """Return (nodeid, created) where nodeid is the identifier of the
+    changeset generated by the rewrite process, and created is True if
+    nodeid was actually created. If created is False, nodeid
+    references a changeset existing before the rewrite call.
+    """
+    if len(old.parents()) > 1: #XXX remove this unecessary limitation.
+        raise error.Abort(_('cannot amend merge changesets'))
+    base = old.p1()
+    updatebookmarks = _bookmarksupdater(repo, old.node())
+
+    wlock = repo.wlock()
+    try:
+
+        # commit a new version of the old changeset, including the update
+        # collect all files which might be affected
+        files = set(old.files())
+        for u in updates:
+            files.update(u.files())
+
+        # Recompute copies (avoid recording a -> b -> a)
+        copied = copies.pathcopies(base, head)
+
+
+        # prune files which were reverted by the updates
+        def samefile(f):
+            if f in head.manifest():
+                a = head.filectx(f)
+                if f in base.manifest():
+                    b = base.filectx(f)
+                    return (a.data() == b.data()
+                            and a.flags() == b.flags())
+                else:
+                    return False
+            else:
+                return f not in base.manifest()
+        files = [f for f in files if not samefile(f)]
+        # commit version of these files as defined by head
+        headmf = head.manifest()
+        def filectxfn(repo, ctx, path):
+            if path in headmf:
+                fctx = head[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()
+        if commitopts.get('message') and commitopts.get('logfile'):
+            raise util.Abort(_('options --message and --logfile are mutually'
+                               ' exclusive'))
+        if commitopts.get('logfile'):
+            message= open(commitopts['logfile']).read()
+        elif commitopts.get('message'):
+            message = commitopts['message']
+        else:
+            message = old.description()
+
+        user = commitopts.get('user') or old.user()
+        date = commitopts.get('date') or None # old.date()
+        extra = dict(commitopts.get('extra', {}))
+        extra['branch'] = head.branch()
+
+        new = context.memctx(repo,
+                             parents=newbases,
+                             text=message,
+                             files=files,
+                             filectxfn=filectxfn,
+                             user=user,
+                             date=date,
+                             extra=extra)
+
+        if commitopts.get('edit'):
+            new._text = cmdutil.commitforceeditor(repo, new, [])
+        revcount = len(repo)
+        newid = repo.commitctx(new)
+        new = repo[newid]
+        created = len(repo) != revcount
+        if created:
+            updatebookmarks(newid)
+            # add evolution metadata
+            collapsed = set([u.node() for u in updates] + [old.node()])
+            repo.addcollapsedobsolete(collapsed, new.node())
+        else:
+            # newid is an existing revision. It could make sense to
+            # replace revisions with existing ones but probably not by
+            # default.
+            pass
+    finally:
+        wlock.release()
+
+    return newid, created
+
+def relocate(repo, orig, dest):
+    """rewrite <rev> on dest"""
+    try:
+        rebase = extensions.find('rebase')
+        # dummy state to trick rebase node
+        assert orig.p2().rev() == node.nullrev, 'no support yet'
+        destbookmarks = repo.nodebookmarks(dest.node())
+        cmdutil.duplicatecopies(repo, orig.node(), dest.node())
+        nodesrc = orig.node()
+        destphase = repo[nodesrc].phase()
+        if rebase.rebasenode.func_code.co_argcount == 5:
+            # rebasenode collapse argument was introduced by
+            # d1afbf03e69a (2.3)
+            rebase.rebasenode(repo, orig.node(), dest.node(),
+                              {node.nullrev: node.nullrev}, False)
+        else:
+            rebase.rebasenode(repo, orig.node(), dest.node(),
+                              {node.nullrev: node.nullrev})
+        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'))
+            raise
+        oldbookmarks = repo.nodebookmarks(nodesrc)
+        if nodenew is not None:
+            phases.retractboundary(repo, destphase, [nodenew])
+            repo.addobsolete(nodenew, nodesrc)
+            for book in oldbookmarks:
+                repo._bookmarks[book] = nodenew
+        else:
+            repo.addobsolete(node.nullid, nodesrc)
+            # Behave like rebase, move bookmarks to dest
+            for book in oldbookmarks:
+                repo._bookmarks[book] = dest.node()
+        for book in destbookmarks: # restore bookmark that rebase move
+            repo._bookmarks[book] = dest.node()
+        if oldbookmarks or destbookmarks:
+            bookmarks.write(repo)
+    except util.Abort:
+        # Invalidate the previous setparents
+        repo.dirstate.invalidate()
+        raise
+
+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.
+    """
+    def selfanddescendants(repo, pctx):
+        yield pctx
+        for ctx in pctx.descendants():
+            yield ctx
+
+    # 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))',
+                                  ctx.rev()))
+        if unstables:
+            return unstables[0]
+    return None
+
+def _bookmarksupdater(repo, oldid):
+    """Return a callable update(newid) updating the current bookmark
+    and bookmarks bound to oldid to newid.
+    """
+    bm = bookmarks.readcurrent(repo)
+    def updatebookmarks(newid):
+        dirty = False
+        if bm:
+            repo._bookmarks[bm] = newid
+            dirty = True
+        oldbookmarks = repo.nodebookmarks(oldid)
+        if oldbookmarks:
+            for b in oldbookmarks:
+                repo._bookmarks[b] = newid
+            dirty = True
+        if dirty:
+            bookmarks.write(repo)
+    return updatebookmarks
+
+### new command
+#############################
+cmdtable = {}
+command = cmdutil.command(cmdtable)
+
+@command('^stabilize|evolve|solve',
+    [('n', 'dry-run', False, 'do not perform actions, print what to be done'),
+    ('A', 'any', False, 'stabilize any unstable changeset'),],
+    _('[OPTIONS]...'))
+def stabilize(ui, repo, **opts):
+    """rebase an unstable changeset to make it stable again
+
+    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.
+
+    With --any, stabilize any unstable changeset.
+
+    The working directory is updated to the rebased revision.
+    """
+
+    obsolete = extensions.find('obsolete')
+
+    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]
+
+    obs = node.parents()[0]
+    if not obs.obsolete():
+        obs = node.parents()[1]
+    assert obs.obsolete()
+    newer = obsolete.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
+    if len(targets) > 1:
+        ui.write_err(_("does not handle splitted parent yet\n"))
+        return 2
+    target = targets[0]
+    displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate})
+    target = repo[target]
+    repo.ui.status(_('move:'))
+    if not ui.quiet:
+        displayer.show(node)
+    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']:
+        repo.ui.write(todo)
+    else:
+        repo.ui.note(todo)
+        lock = repo.lock()
+        try:
+            relocate(repo, node, target)
+        finally:
+            lock.release()
+
+shorttemplate = '[{rev}] {desc|firstline}\n'
+
+@command('^gdown',
+         [],
+         '')
+def cmdgdown(ui, repo):
+    """update to parent an display summary lines"""
+    wkctx = repo[None]
+    wparents = wkctx.parents()
+    if len(wparents) != 1:
+        raise util.Abort('merge in progress')
+
+    parents = wparents[0].parents()
+    displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate})
+    if len(parents) == 1:
+        p = parents[0]
+        hg.update(repo, p.rev())
+        displayer.show(p)
+        return 0
+    else:
+        for p in parents:
+            displayer.show(p)
+        ui.warn(_('multiple parents, explicitly update to one\n'))
+        return 1
+
+@command('^gup',
+         [],
+         '')
+def cmdup(ui, repo):
+    """update to child an display summary lines"""
+    wkctx = repo[None]
+    wparents = wkctx.parents()
+    if len(wparents) != 1:
+        raise util.Abort('merge in progress')
+
+    children = [ctx for ctx in wparents[0].children() if not ctx.obsolete()]
+    displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate})
+    if not children:
+        ui.warn(_('No non-obsolete children\n'))
+        return 1
+    if len(children) == 1:
+        c = children[0]
+        hg.update(repo, c.rev())
+        displayer.show(c)
+        return 0
+    else:
+        for c in children:
+            displayer.show(c)
+        ui.warn(_('Multiple non-obsolete children, explicitly update to one\n'))
+        return 1
+
+@command('^prune|obsolete|kill',
+    [('n', 'new', [], _("successor changeset"))],
+    _('[OPTION] REV...'))
+def kill(ui, repo, *revs, **opts):
+    """mark a changeset as obsolete
+
+    This update the parent directory to a not-killed parent if the current
+    working directory parent are killed.
+
+    XXX bookmark support
+    XXX handle merge
+    XXX check immutable first
+    """
+    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(_("Can't kill immutable changeset %s") % repo[n])
+            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)
+
+    finally:
+        wlock.release()
+
+@command('^amend|refresh',
+    [('A', 'addremove', None,
+     _('mark new/missing files as added/removed before committing')),
+    ('n', 'note', '', _('use text as commit message for this update')),
+    ('c', 'change', '', _('specifies the changesets to amend'), _('REV')),
+    ('e', 'edit', False, _('invoke editor on commit messages')),
+    ] + walkopts + commitopts + commitopts2,
+    _('[OPTION]... [FILE]...'))
+def amend(ui, repo, *pats, **opts):
+    """combine a changeset with updates and replace it with a new one
+
+    Commits a new changeset incorporating both the changes to the given files
+    and all the changes from the current parent changeset into the repository.
+
+    See :hg:`commit` for details about committing changes.
+
+    If you don't specify -m, the parent's message will be reused.
+
+    If you specify --change, amend additionally considers all
+    changesets between the indicated changeset and the working copy
+    parent as updates to be subsumed.
+
+    Behind the scenes, Mercurial first commits the update as a regular child
+    of the current parent. Then it creates a new commit on the parent's parents
+    with the updated contents. Then it changes the working copy parent to this
+    new combined changeset. Finally, the old changeset and its update are hidden
+    from :hg:`log` (unless you use --hidden with log).
+
+    Returns 0 on success, 1 if nothing changed.
+    """
+
+    # determine updates to subsume
+    old = scmutil.revsingle(repo, opts.get('change') or '.')
+
+    lock = repo.lock()
+    try:
+        wlock = repo.wlock()
+        try:
+            if old.phase() == phases.public:
+                raise util.Abort(_("can not rewrite immutable changeset %s")
+                                 % old)
+            oldphase = old.phase()
+            # commit current changes as update
+            # code copied from commands.commit to avoid noisy messages
+            ciopts = dict(opts)
+            ciopts.pop('message', None)
+            ciopts.pop('logfile', None)
+            ciopts['message'] = opts.get('note') or ('amends %s' % old.hex())
+            e = cmdutil.commiteditor
+            def commitfunc(ui, repo, message, match, opts):
+                return repo.commit(message, opts.get('user'), opts.get('date'),
+                                   match, editor=e)
+            revcount = len(repo)
+            tempid = cmdutil.commit(ui, repo, commitfunc, pats, ciopts)
+            if len(repo) == revcount:
+                # No revision created
+                tempid = None
+
+            # find all changesets to be considered updates
+            head = repo['.']
+            updatenodes = set(repo.changelog.nodesbetween(
+                    roots=[old.node()], heads=[head.node()])[0])
+            updatenodes.remove(old.node())
+            okoptions = ['message', 'logfile', 'edit', 'user']
+            if not updatenodes:
+                for o in okoptions:
+                    if opts.get(o):
+                        break
+                else:
+                    raise error.Abort(_('no updates found'))
+            updates = [repo[n] for n in updatenodes]
+
+            # perform amend
+            if opts.get('edit'):
+                opts['force_editor'] = True
+            newid, created = rewrite(repo, old, updates, head,
+                                     [old.p1().node(), old.p2().node()], opts)
+            if created:
+                # reroute the working copy parent to the new changeset
+                phases.retractboundary(repo, oldphase, [newid])
+                repo.dirstate.setparents(newid, node.nullid)
+            else:
+                # rewrite() recreated an existing revision, discard
+                # the intermediate revision if any. No need to update
+                # phases or parents.
+                if tempid is not None:
+                    repo.addobsolete(node.nullid, tempid)
+                # XXX: need another message in collapse case.
+                raise error.Abort(_('no updates found'))
+        finally:
+            wlock.release()
+    finally:
+        lock.release()
+
+def _commitfiltered(repo, ctx, match):
+    """Recommit ctx with changed files not in match. Return the new
+    node identifier, or None if nothing changed.
+    """
+    base = ctx.p1()
+    m, a, r = repo.status(base, ctx)[:3]
+    allfiles = set(m + a + r)
+    files = set(f for f in allfiles if not match(f))
+    if files == allfiles:
+        return None
+
+    # Filter copies
+    copied = copies.pathcopies(base, ctx)
+    copied = dict((src, dst) for src, dst in copied.iteritems()
+                  if dst in files)
+    def filectxfn(repo, memctx, path):
+        if path not in ctx:
+            raise IOError()
+        fctx = ctx[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
+
+    new = context.memctx(repo,
+                         parents=[base.node(), node.nullid],
+                         text=ctx.description(),
+                         files=files,
+                         filectxfn=filectxfn,
+                         user=ctx.user(),
+                         date=ctx.date(),
+                         extra=ctx.extra())
+    # commitctx always create a new revision, no need to check
+    newid = repo.commitctx(new)
+    return newid
+
+def _uncommitdirstate(repo, oldctx, match):
+    """Fix the dirstate after switching the working directory from
+    oldctx to a copy of oldctx not containing changed files matched by
+    match.
+    """
+    ctx = repo['.']
+    ds = repo.dirstate
+    copies = dict(ds.copies())
+    m, a, r = repo.status(oldctx.p1(), oldctx, match=match)[:3]
+    for f in m:
+        if ds[f] == 'r':
+            # modified + removed -> removed
+            continue
+        ds.normallookup(f)
+
+    for f in a:
+        if ds[f] == 'r':
+            # added + removed -> unknown
+            ds.drop(f)
+        elif ds[f] != 'a':
+            ds.add(f)
+
+    for f in r:
+        if ds[f] == 'a':
+            # removed + added -> normal
+            ds.normallookup(f)
+        elif ds[f] != 'r':
+            ds.remove(f)
+
+    # Merge old parent and old working dir copies
+    oldcopies = {}
+    for f in (m + a):
+        src = oldctx[f].renamed()
+        if src:
+            oldcopies[f] = src[0]
+    oldcopies.update(copies)
+    copies = dict((dst, oldcopies.get(src, src))
+                  for dst, src in oldcopies.iteritems())
+    # Adjust the dirstate copies
+    for dst, src in copies.iteritems():
+        if (src not in ctx or dst in ctx or ds[dst] != 'a'):
+            src = None
+        ds.copy(src, dst)
+
+@command('^uncommit',
+    [('a', 'all', None, _('uncommit all changes when no arguments given')),
+     ] + commands.walkopts,
+    _('[OPTION]... [NAME]'))
+def uncommit(ui, repo, *pats, **opts):
+    """move changes from parent revision to working directory
+
+    Changes to selected files in parent revision appear again as
+    uncommitted changed in the working directory. A new revision
+    without selected changes is created, becomes the new parent and
+    obsoletes the previous one.
+
+    The --include option specify pattern to uncommit
+    The --exclude option specify pattern to keep in the commit
+
+    Return 0 if changed files are uncommitted.
+    """
+    lock = repo.lock()
+    try:
+        wlock = repo.wlock()
+        try:
+            wctx = repo[None]
+            if len(wctx.parents()) <= 0:
+                raise util.Abort(_("cannot uncommit null changeset"))
+            if len(wctx.parents()) > 1:
+                raise util.Abort(_("cannot uncommit while merging"))
+            old = repo['.']
+            if old.phase() == phases.public:
+                raise util.Abort(_("cannot rewrite immutable changeset"))
+            if len(old.parents()) > 1:
+                raise util.Abort(_("cannot uncommit merge changeset"))
+            oldphase = old.phase()
+            updatebookmarks = _bookmarksupdater(repo, old.node())
+            # Recommit the filtered changeset
+            newid = None
+            if (pats or opts.get('include') or opts.get('exclude')
+                or opts.get('all')):
+                match = scmutil.match(old, pats, opts)
+                newid = _commitfiltered(repo, old, match)
+            if newid is None:
+                raise util.Abort(_('nothing to uncommit'))
+            # Move local changes on filtered changeset
+            repo.addobsolete(newid, old.node())
+            phases.retractboundary(repo, oldphase, [newid])
+            repo.dirstate.setparents(newid, node.nullid)
+            _uncommitdirstate(repo, old, match)
+            updatebookmarks(newid)
+            if not repo[newid].files():
+                ui.warn(_("new changeset is empty\n"))
+                ui.status(_('(use "hg kill ." to remove it)\n'))
+        finally:
+            wlock.release()
+    finally:
+        lock.release()
+
+def commitwrapper(orig, ui, repo, *arg, **kwargs):
+    lock = repo.lock()
+    try:
+        obsoleted = kwargs.get('obsolete', [])
+        if obsoleted:
+            obsoleted = repo.set('%lr', obsoleted)
+        result = orig(ui, repo, *arg, **kwargs)
+        if not result: # commit successed
+            new = repo['-1']
+            oldbookmarks = []
+            for old in obsoleted:
+                oldbookmarks.extend(repo.nodebookmarks(old.node()))
+                repo.addobsolete(new.node(), old.node())
+            for book in oldbookmarks:
+                repo._bookmarks[book] = new.node()
+            if oldbookmarks:
+                bookmarks.write(repo)
+        return result
+    finally:
+        lock.release()
+
+def graftwrapper(orig, ui, repo, *revs, **kwargs):
+    lock = repo.lock()
+    try:
+        if kwargs.get('old_obsolete'):
+            obsoleted = kwargs.setdefault('obsolete', [])
+            if kwargs['continue']:
+                obsoleted.extend(repo.opener.read('graftstate').splitlines())
+            else:
+                obsoleted.extend(revs)
+        # convert obsolete target into revs to avoid alias joke
+        obsoleted = kwargs.setdefault('obsolete', [])
+        obsoleted[:] = [str(i) for i in repo.revs('%lr', obsoleted)]
+        if obsoleted and len(revs) > 1:
+
+            raise error.Abort(_('Can not graft multiple revision while '
+                                'obsoleting (for now).'))
+
+        return commitwrapper(orig, ui, repo,*revs, **kwargs)
+    finally:
+        lock.release()
+
+def extsetup(ui):
+    try:
+        obsolete = extensions.find('obsolete')
+    except KeyError:
+        raise error.Abort(_('evolution extension require obsolete extension.'))
+    try:
+        rebase = extensions.find('rebase')
+    except KeyError:
+        rebase = None
+        raise error.Abort(_('evolution extension require rebase extension.'))
+
+    for cmd in ['amend', 'kill', 'uncommit']:
+        entry = extensions.wrapcommand(cmdtable, cmd,
+                                       obsolete.warnobserrors)
+
+    entry = extensions.wrapcommand(commands.table, 'commit', commitwrapper)
+    entry[1].append(('o', 'obsolete', [],
+                     _("make commit obsolete this revision")))
+    entry = extensions.wrapcommand(commands.table, 'graft', graftwrapper)
+    entry[1].append(('o', 'obsolete', [],
+                     _("make graft obsoletes this revision")))
+    entry[1].append(('O', 'old-obsolete', False,
+                     _("make graft obsoletes its source")))
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/obsolete.py	Mon Jul 30 14:45:42 2012 +0200
@@ -0,0 +1,849 @@
+# obsolete.py - introduce the obsolete concept in mercurial.
+#
+# Copyright 2011 Pierre-Yves David <pierre-yves.david@ens-lyon.org>
+#                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.
+"""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
+=================
+
+
+New commands
+------------
+
+Note that rebased changesets are not marked obsolete rather than being stripped
+In this experimental extensions, this is done forcing the --keep option. Trying
+to use the --keep option of rebase with this extensionn this experimental
+extension will cause such a call to abort. Until better releasen please use
+graft command to rebase and copy changesets.
+
+"""
+
+import os, sys
+try:
+    from cStringIO import StringIO
+except ImportError:
+    from StringIO import StringIO
+
+from mercurial.i18n import _
+
+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 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()``
+    Changeset is hidden.
+    """
+    args = revset.getargs(x, 0, 0, 'hidden takes no argument')
+    return [r for r in subset if r in repo.hiddenrevs]
+
+def revsetobsolete(repo, subset, x):
+    """``obsolete()``
+    Changeset is obsolete.
+    """
+    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()``
+        Changeset is obsolete.
+        """
+        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):
+    """``unstable()``
+    Unstable changesets are non-obsolete with obsolete ancestors.
+    """
+    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):
+    """``suspended()``
+    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):
+    """``extinct()``
+    Obsolete changesets with obsolete descendants only.
+    """
+    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()``
+    Changesets marked as successors of public changesets.
+    """
+    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()``
+    Changesets marked as successors of a same changeset.
+    """
+    args = revset.getargs(x, 0, 0, 'conflicting takes no arguments')
+    return [r for r in subset if r in repo._conflictingset]
+
+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(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]
+
+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):
+    """``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]
+
+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(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]
+
+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 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):
+    """``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
+#####################
+
+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
+############################
+
+
+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
+
+    # 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')
+
+
+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
+
+### Discovery wrapping
+#############################
+
+
+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 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)
+
+def wrapclearcache(orig, repo, *args, **kwargs):
+    try:
+        return orig(repo, *args, **kwargs)
+    finally:
+        repo._clearobsoletecache()
+
+
+### New commands
+#############################
+
+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:
+                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(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)
+
+@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 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()
+                repo._clearobsoletecache()
+            finally:
+                tr.release()
+        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, 'checkheads', wrapcheckheads)
+    extensions.wrapfunction(phases, 'advanceboundary', wrapclearcache)
+
+### 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)))
+
+def _obsdeserialise(flike):
+    """read a file like object serialised with _obsserialise
+
+    this desierialize into a {subject -> objects} mapping"""
+    rels = {}
+    for line in flike:
+        subhex, objhex = line.split()
+        subnode = bin(subhex)
+        if subnode == nullid:
+            subnode = None
+        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)
+
+### repo subclassing
+#############################
+
+def reposetup(ui, repo):
+    if not repo.local():
+        return
+
+    if not util.safehasattr(repo.opener, 'tryread'):
+        raise util.Abort('Obsolete extension require Mercurial 2.2 (or later)')
+    opush = repo.push
+    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
+
+
+    class obsoletingrepo(repo.__class__):
+
+        ### 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, []))
+
+        @util.propertycache
+        def _obsoleteset(self):
+            """the set of obsolete revision"""
+            obs = set()
+            nm = self.changelog.nodemap
+            for prec in self.obsstore.precursors:
+                rev = nm.get(prec)
+                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:
+                tr = self.transaction('add-obsolete')
+                try:
+                    meta = {
+                        'date':  '%i %i' % util.makedate(),
+                        'user': ui.username(),
+                        }
+                    subs = (sub == nullid) and [] or [sub]
+                    mid = self.obsstore.create(tr, obj, subs, 0, meta)
+                    tr.close()
+                    self._clearobsoletecache()
+                    return mid
+                finally:
+                    tr.release()
+            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)
+
+        ### pull // push support
+
+        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 stabilize' 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__ = obsoletingrepo
+    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:
+            raise util.Abort('old format of obsolete marker detected!\n'
+                             'run `hg debugconvertobsolete` once.')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/qsync.py	Mon Jul 30 14:45:42 2012 +0200
@@ -0,0 +1,261 @@
+"""synchronize patches queues and evolving changesets"""
+
+import re
+from cStringIO import StringIO
+import json
+
+from mercurial.i18n import _
+from mercurial import commands
+from mercurial import patch
+from mercurial import util
+from mercurial.node import nullid, hex, short, bin
+from mercurial import cmdutil
+from mercurial import hg
+from mercurial import scmutil
+from mercurial import error
+from mercurial import extensions
+from mercurial import phases
+
+### old compat code
+#############################
+
+BRANCHNAME="qsubmit2"
+
+### new command
+#############################
+cmdtable = {}
+command = cmdutil.command(cmdtable)
+
+@command('^qsync|sync',
+    [
+     ('a', 'review-all', False, _('mark all touched patches ready for review (no editor)')),
+    ],
+    '')
+def cmdsync(ui, repo, **opts):
+    '''Export draft changeset as mq patch in a mq patches repository commit.
+
+    This command get all changesets in draft phase and create an mq changeset:
+
+        * on a "qsubmit2" branch (based on the last changeset)
+
+        * one patch per draft changeset
+
+        * a series files listing all generated patch
+
+        * qsubmitdata holding useful information
+
+    It does use obsolete relation to update patches that already existing in the qsubmit2 branch.
+
+    Already existing patch which became public, draft or got killed are remove from the mq repo.
+
+    Patch name are generated using the summary line for changeset description.
+
+    .. warning:: Series files is ordered topologically. So two series with
+                 interleaved changeset will appear interleaved.
+    '''
+
+    review = 'edit'
+    if opts['review_all']:
+        review = 'all'
+    mqrepo = repo.mq.qrepo()
+    if mqrepo is None:
+        raise util.Abort('No patches repository')
+
+    try:
+        parent = mqrepo[BRANCHNAME]
+    except error.RepoLookupError:
+        parent = initqsubmit(mqrepo)
+    store, data, touched = fillstore(repo, parent)
+    if not touched:
+        raise util.Abort('Nothing changed')
+    files = ['qsubmitdata', 'series'] + touched
+    # mark some as ready for review
+    message = 'qsubmit commit\n\n'
+    review_list = []
+    applied_list = []
+    if review:
+        olddata = get_old_data(parent)
+        oldfiles = dict([(name, bin(ctxhex)) for ctxhex, name in olddata])
+
+        for patch_name in touched:
+            try:
+                store.getfile(patch_name)
+                review_list.append(patch_name)
+            except IOError:
+                oldnode = oldfiles[patch_name]
+                obsolete = extensions.find('obsolete')
+                newnodes = obsolete.newerversion(repo, oldnode)
+                if newnodes:
+                    newnodes = [n for n in newnodes if n and n[0] in repo] # remove killing
+                if not newnodes:
+                    # changeset has been killed (eg. reject)
+                    pass
+                else:
+                    assert len(newnodes) == 1 # conflict!!!
+                    newnode = newnodes[0]
+                    assert len(newnode) == 1 # split unsupported for now
+                    newnode = list(newnode)[0]
+                    # XXX unmanaged case where a cs is obsoleted by an unavailable one
+                    #if newnode.node() not in repo.changelog.nodemap:
+                    #    raise util.Abort('%s is obsoleted by an unknown node %s'% (oldnode, newnode))
+                    ctx = repo[newnode]
+                    if ctx.phase() == phases.public:
+                        # applied
+                        applied_list.append(patch_name)
+                    elif ctx.phase() == phases.secret:
+                        # already exported changeset is now secret
+                        repo.ui.warn("An already exported changeset is now secret!!!")
+                    else:
+                        # draft
+                        assert False, "Should be exported"
+
+    if review:
+        if applied_list:
+            message += '\n'.join('* applied %s' % x for x in applied_list) + '\n'
+        if review_list:
+            message += '\n'.join('* %s ready for review' % x for x in review_list) + '\n'
+    memctx = patch.makememctx(mqrepo, (parent.node(), nullid),
+                              message,
+                              None,
+                              None,
+                              parent.branch(), files, store,
+                              editor=None)
+    if review == 'edit':
+        memctx._text = cmdutil.commitforceeditor(mqrepo, memctx, [])
+    mqrepo.savecommitmessage(memctx.description())
+    n = memctx.commit()
+    return 0
+
+
+def makename(ctx):
+    """create a patch name form a changeset"""
+    descsummary = ctx.description().splitlines()[0]
+    descsummary = re.sub(r'\s+', '_', descsummary)
+    descsummary = re.sub(r'\W+', '', descsummary)
+    if len(descsummary) > 45:
+        descsummary = descsummary[:42] + '.'
+    return '%s-%s.diff' % (ctx.branch().upper(), descsummary)
+
+
+def get_old_data(mqctx):
+    """read qsubmit data to fetch previous export data
+
+    get old data from the content of an mq commit"""
+    try:
+        old_data = mqctx['qsubmitdata']
+        return json.loads(old_data.data())
+    except error.LookupError:
+        return []
+
+def get_current_data(repo):
+    """Return what would be exported if no previous data exists"""
+    data = []
+    for ctx in repo.set('draft() - (obsolete() + merge())'):
+        name = makename(ctx)
+        data.append([ctx.hex(), makename(ctx)])
+    merges = repo.revs('draft() and merge()')
+    if merges:
+        repo.ui.warn('ignoring %i merge\n' % len(merges))
+    return data
+
+
+def patchmq(repo, store, olddata, newdata):
+    """export the mq patches and return all useful data to be exported"""
+    finaldata = []
+    touched = set()
+    currentdrafts = set(d[0] for d in newdata)
+    usednew = set()
+    usedold = set()
+    obsolete = extensions.find('obsolete')
+    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)
+        if newnodes:
+            newnodes = [n for n in newnodes if n and n[0] in repo] # remove killing
+            if len(newnodes) > 1:
+                newnodes = [short(nodes[0]) for nodes in newnodes]
+                raise util.Abort('%s have more than one newer version: %s'% (oldname, newnodes))
+            if newnodes:
+                # else, changeset have been killed
+                newnode = list(newnodes)[0][0]
+                ctx = repo[newnode]
+                if ctx.hex() != oldhex and ctx.phase():
+                    fp = StringIO()
+                    cmdutil.export(repo, [ctx.rev()], fp=fp)
+                    data = fp.getvalue()
+                    store.setfile(oldname, data, (None, None))
+                    finaldata.append([ctx.hex(), oldname])
+                    usednew.add(ctx.hex())
+                    touched.add(oldname)
+                    continue
+        if oldhex in currentdrafts:
+            # else changeset is now public or secret
+            finaldata.append([oldhex, oldname])
+            usednew.add(ctx.hex())
+            continue
+        touched.add(oldname)
+
+    for newhex, newname in newdata:
+        if newhex in usednew:
+            continue
+        newnode = bin(newhex)
+        ctx = repo[newnode]
+        fp = StringIO()
+        cmdutil.export(repo, [ctx.rev()], fp=fp)
+        data = fp.getvalue()
+        store.setfile(newname, data, (None, None))
+        finaldata.append([ctx.hex(), newname])
+        touched.add(newname)
+    # sort by branchrev number
+    finaldata.sort(key=lambda x: sort_key(repo[x[0]]))
+    # sort touched too (ease review list)
+    stouched = [f[1] for f in finaldata if f[1] in touched]
+    stouched += [x for x in touched if x not in stouched]
+    return finaldata, stouched
+
+def sort_key(ctx):
+    """ctx sort key: (branch, rev)"""
+    return (ctx.branch(), ctx.rev())
+
+
+def fillstore(repo, basemqctx):
+    """fill store with patch data"""
+    olddata = get_old_data(basemqctx)
+    newdata = get_current_data(repo)
+    store = patch.filestore()
+    try:
+        data, touched = patchmq(repo, store, olddata, newdata)
+        # put all name in the series
+        series ='\n'.join(d[1] for d in data) + '\n'
+        store.setfile('series', series, (False, False))
+
+        # export data to ease futur work
+        store.setfile('qsubmitdata', json.dumps(data, indent=True),
+                      (False, False))
+    finally:
+        store.close()
+    return store, data, touched
+
+
+def initqsubmit(mqrepo):
+    """create initial qsubmit branch"""
+    store = patch.filestore()
+    try:
+        files = set()
+        store.setfile('DO-NOT-EDIT-THIS-WORKING-COPY-BY-HAND', 'WE WARNED YOU!', (False, False))
+        store.setfile('.hgignore', '^status$\n', (False, False))
+        memctx = patch.makememctx(mqrepo, (nullid, nullid),
+                              'qsubmit init',
+                              None,
+                              None,
+                              BRANCHNAME, ('.hgignore',), store,
+                              editor=None)
+        mqrepo.savecommitmessage(memctx.description())
+        n = memctx.commit()
+    finally:
+        store.close()
+    return mqrepo[n]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/qsync-enable.sh	Mon Jul 30 14:45:42 2012 +0200
@@ -0,0 +1,20 @@
+#!/bin/sh
+
+here=`readlink -f "$0"`
+repo_root=`dirname "$here"`
+
+
+
+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 mq export
+qsync=XXXREPOPATHXXX/hgext/qsync.py
+EOF
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/killdaemons.py	Mon Jul 30 14:45:42 2012 +0200
@@ -0,0 +1,25 @@
+#!/usr/bin/env python
+
+import os, time, errno, signal
+
+# Kill off any leftover daemon processes
+try:
+    fp = open(os.environ['DAEMON_PIDS'])
+    for line in fp:
+        try:
+            pid = int(line)
+        except ValueError:
+            continue
+        try:
+            os.kill(pid, 0)
+            os.kill(pid, signal.SIGTERM)
+            for i in range(10):
+                time.sleep(0.05)
+                os.kill(pid, 0)
+            os.kill(pid, signal.SIGKILL)
+        except OSError, err:
+            if err.errno != errno.ESRCH:
+                raise
+    fp.close()
+except IOError:
+    pass
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/run-tests.py	Mon Jul 30 14:45:42 2012 +0200
@@ -0,0 +1,1128 @@
+#!/usr/bin/env python
+#
+# run-tests.py - Run a set of tests on Mercurial
+#
+# Copyright 2006 Matt Mackall <mpm@selenic.com>
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+# Modifying this script is tricky because it has many modes:
+#   - serial (default) vs parallel (-jN, N > 1)
+#   - no coverage (default) vs coverage (-c, -C, -s)
+#   - temp install (default) vs specific hg script (--with-hg, --local)
+#   - tests are a mix of shell scripts and Python scripts
+#
+# If you change this script, it is recommended that you ensure you
+# haven't broken it by running it in various modes with a representative
+# sample of test scripts.  For example:
+#
+#  1) serial, no coverage, temp install:
+#      ./run-tests.py test-s*
+#  2) serial, no coverage, local hg:
+#      ./run-tests.py --local test-s*
+#  3) serial, coverage, temp install:
+#      ./run-tests.py -c test-s*
+#  4) serial, coverage, local hg:
+#      ./run-tests.py -c --local test-s*      # unsupported
+#  5) parallel, no coverage, temp install:
+#      ./run-tests.py -j2 test-s*
+#  6) parallel, no coverage, local hg:
+#      ./run-tests.py -j2 --local test-s*
+#  7) parallel, coverage, temp install:
+#      ./run-tests.py -j2 -c test-s*          # currently broken
+#  8) parallel, coverage, local install:
+#      ./run-tests.py -j2 -c --local test-s*  # unsupported (and broken)
+#  9) parallel, custom tmp dir:
+#      ./run-tests.py -j2 --tmpdir /tmp/myhgtests
+#
+# (You could use any subset of the tests: test-s* happens to match
+# enough that it's worth doing parallel runs, few enough that it
+# completes fairly quickly, includes both shell and Python scripts, and
+# includes some scripts that run daemon processes.)
+
+from distutils import version
+import difflib
+import errno
+import optparse
+import os
+import shutil
+import subprocess
+import signal
+import sys
+import tempfile
+import time
+import re
+
+closefds = os.name == 'posix'
+def Popen4(cmd, bufsize=-1):
+    p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
+                         close_fds=closefds,
+                         stdin=subprocess.PIPE, stdout=subprocess.PIPE,
+                         stderr=subprocess.STDOUT)
+    p.fromchild = p.stdout
+    p.tochild = p.stdin
+    p.childerr = p.stderr
+    return p
+
+# reserved exit code to skip test (used by hghave)
+SKIPPED_STATUS = 80
+SKIPPED_PREFIX = 'skipped: '
+FAILED_PREFIX  = 'hghave check failed: '
+PYTHON = sys.executable
+IMPL_PATH = 'PYTHONPATH'
+if 'java' in sys.platform:
+    IMPL_PATH = 'JYTHONPATH'
+
+requiredtools = ["python", "diff", "grep", "unzip", "gunzip", "bunzip2", "sed"]
+
+defaults = {
+    'jobs': ('HGTEST_JOBS', 1),
+    'timeout': ('HGTEST_TIMEOUT', 180),
+    'port': ('HGTEST_PORT', 20059),
+}
+
+def parseargs():
+    parser = optparse.OptionParser("%prog [options] [tests]")
+
+    # keep these sorted
+    parser.add_option("--blacklist", action="append",
+        help="skip tests listed in the specified blacklist file")
+    parser.add_option("-C", "--annotate", action="store_true",
+        help="output files annotated with coverage")
+    parser.add_option("--child", type="int",
+        help="run as child process, summary to given fd")
+    parser.add_option("-c", "--cover", action="store_true",
+        help="print a test coverage report")
+    parser.add_option("-d", "--debug", action="store_true",
+        help="debug mode: write output of test scripts to console"
+             " rather than capturing and diff'ing it (disables timeout)")
+    parser.add_option("-f", "--first", action="store_true",
+        help="exit on the first test failure")
+    parser.add_option("--inotify", action="store_true",
+        help="enable inotify extension when running tests")
+    parser.add_option("-i", "--interactive", action="store_true",
+        help="prompt to accept changed output")
+    parser.add_option("-j", "--jobs", type="int",
+        help="number of jobs to run in parallel"
+             " (default: $%s or %d)" % defaults['jobs'])
+    parser.add_option("--keep-tmpdir", action="store_true",
+        help="keep temporary directory after running tests")
+    parser.add_option("-k", "--keywords",
+        help="run tests matching keywords")
+    parser.add_option("-l", "--local", action="store_true",
+        help="shortcut for --with-hg=<testdir>/../hg")
+    parser.add_option("-n", "--nodiff", action="store_true",
+        help="skip showing test changes")
+    parser.add_option("-p", "--port", type="int",
+        help="port on which servers should listen"
+             " (default: $%s or %d)" % defaults['port'])
+    parser.add_option("--pure", action="store_true",
+        help="use pure Python code instead of C extensions")
+    parser.add_option("-R", "--restart", action="store_true",
+        help="restart at last error")
+    parser.add_option("-r", "--retest", action="store_true",
+        help="retest failed tests")
+    parser.add_option("-S", "--noskips", action="store_true",
+        help="don't report skip tests verbosely")
+    parser.add_option("-t", "--timeout", type="int",
+        help="kill errant tests after TIMEOUT seconds"
+             " (default: $%s or %d)" % defaults['timeout'])
+    parser.add_option("--tmpdir", type="string",
+        help="run tests in the given temporary directory"
+             " (implies --keep-tmpdir)")
+    parser.add_option("-v", "--verbose", action="store_true",
+        help="output verbose messages")
+    parser.add_option("--view", type="string",
+        help="external diff viewer")
+    parser.add_option("--with-hg", type="string",
+        metavar="HG",
+        help="test using specified hg script rather than a "
+             "temporary installation")
+    parser.add_option("-3", "--py3k-warnings", action="store_true",
+        help="enable Py3k warnings on Python 2.6+")
+
+    for option, default in defaults.items():
+        defaults[option] = int(os.environ.get(*default))
+    parser.set_defaults(**defaults)
+    (options, args) = parser.parse_args()
+
+    # jython is always pure
+    if 'java' in sys.platform or '__pypy__' in sys.modules:
+        options.pure = True
+
+    if options.with_hg:
+        if not (os.path.isfile(options.with_hg) and
+                os.access(options.with_hg, os.X_OK)):
+            parser.error('--with-hg must specify an executable hg script')
+        if not os.path.basename(options.with_hg) == 'hg':
+            sys.stderr.write('warning: --with-hg should specify an hg script')
+    if options.local:
+        testdir = os.path.dirname(os.path.realpath(sys.argv[0]))
+        hgbin = os.path.join(os.path.dirname(testdir), 'hg')
+        if not os.access(hgbin, os.X_OK):
+            parser.error('--local specified, but %r not found or not executable'
+                         % hgbin)
+        options.with_hg = hgbin
+
+    options.anycoverage = options.cover or options.annotate
+    if options.anycoverage:
+        try:
+            import coverage
+            covver = version.StrictVersion(coverage.__version__).version
+            if covver < (3, 3):
+                parser.error('coverage options require coverage 3.3 or later')
+        except ImportError:
+            parser.error('coverage options now require the coverage package')
+
+    if options.anycoverage and options.local:
+        # this needs some path mangling somewhere, I guess
+        parser.error("sorry, coverage options do not work when --local "
+                     "is specified")
+
+    global vlog
+    if options.verbose:
+        if options.jobs > 1 or options.child is not None:
+            pid = "[%d]" % os.getpid()
+        else:
+            pid = None
+        def vlog(*msg):
+            if pid:
+                print pid,
+            for m in msg:
+                print m,
+            print
+            sys.stdout.flush()
+    else:
+        vlog = lambda *msg: None
+
+    if options.tmpdir:
+        options.tmpdir = os.path.expanduser(options.tmpdir)
+
+    if options.jobs < 1:
+        parser.error('--jobs must be positive')
+    if options.interactive and options.jobs > 1:
+        print '(--interactive overrides --jobs)'
+        options.jobs = 1
+    if options.interactive and options.debug:
+        parser.error("-i/--interactive and -d/--debug are incompatible")
+    if options.debug:
+        if options.timeout != defaults['timeout']:
+            sys.stderr.write(
+                'warning: --timeout option ignored with --debug\n')
+        options.timeout = 0
+    if options.py3k_warnings:
+        if sys.version_info[:2] < (2, 6) or sys.version_info[:2] >= (3, 0):
+            parser.error('--py3k-warnings can only be used on Python 2.6+')
+    if options.blacklist:
+        blacklist = dict()
+        for filename in options.blacklist:
+            try:
+                path = os.path.expanduser(os.path.expandvars(filename))
+                f = open(path, "r")
+            except IOError, err:
+                if err.errno != errno.ENOENT:
+                    raise
+                print "warning: no such blacklist file: %s" % filename
+                continue
+
+            for line in f.readlines():
+                line = line.strip()
+                if line and not line.startswith('#'):
+                    blacklist[line] = filename
+
+            f.close()
+
+        options.blacklist = blacklist
+
+    return (options, args)
+
+def rename(src, dst):
+    """Like os.rename(), trade atomicity and opened files friendliness
+    for existing destination support.
+    """
+    shutil.copy(src, dst)
+    os.remove(src)
+
+def splitnewlines(text):
+    '''like str.splitlines, but only split on newlines.
+    keep line endings.'''
+    i = 0
+    lines = []
+    while True:
+        n = text.find('\n', i)
+        if n == -1:
+            last = text[i:]
+            if last:
+                lines.append(last)
+            return lines
+        lines.append(text[i:n + 1])
+        i = n + 1
+
+def parsehghaveoutput(lines):
+    '''Parse hghave log lines.
+    Return tuple of lists (missing, failed):
+      * the missing/unknown features
+      * the features for which existence check failed'''
+    missing = []
+    failed = []
+    for line in lines:
+        if line.startswith(SKIPPED_PREFIX):
+            line = line.splitlines()[0]
+            missing.append(line[len(SKIPPED_PREFIX):])
+        elif line.startswith(FAILED_PREFIX):
+            line = line.splitlines()[0]
+            failed.append(line[len(FAILED_PREFIX):])
+
+    return missing, failed
+
+def showdiff(expected, output, ref, err):
+    try:
+        for line in difflib.unified_diff(expected, output, ref, err):
+            sys.stdout.write(line)
+    except IOError, ex:
+        print >>sys.stderr, 'BORKEN PIPE', ex.errno
+        pass
+
+def findprogram(program):
+    """Search PATH for a executable program"""
+    for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
+        name = os.path.join(p, program)
+        if os.access(name, os.X_OK):
+            return name
+    return None
+
+def checktools():
+    # Before we go any further, check for pre-requisite tools
+    # stuff from coreutils (cat, rm, etc) are not tested
+    for p in requiredtools:
+        if os.name == 'nt':
+            p += '.exe'
+        found = findprogram(p)
+        if found:
+            vlog("# Found prerequisite", p, "at", found)
+        else:
+            print "WARNING: Did not find prerequisite tool: "+p
+
+def killdaemons():
+    # Kill off any leftover daemon processes
+    try:
+        fp = open(DAEMON_PIDS)
+        for line in fp:
+            try:
+                pid = int(line)
+            except ValueError:
+                continue
+            try:
+                os.kill(pid, 0)
+                vlog('# Killing daemon process %d' % pid)
+                os.kill(pid, signal.SIGTERM)
+                time.sleep(0.25)
+                os.kill(pid, 0)
+                vlog('# Daemon process %d is stuck - really killing it' % pid)
+                os.kill(pid, signal.SIGKILL)
+            except OSError, err:
+                if err.errno != errno.ESRCH:
+                    raise
+        fp.close()
+        os.unlink(DAEMON_PIDS)
+    except IOError:
+        pass
+
+def cleanup(options):
+    if not options.keep_tmpdir:
+        vlog("# Cleaning up HGTMP", HGTMP)
+        shutil.rmtree(HGTMP, True)
+
+def usecorrectpython():
+    # some tests run python interpreter. they must use same
+    # interpreter we use or bad things will happen.
+    exedir, exename = os.path.split(sys.executable)
+    if exename == 'python':
+        path = findprogram('python')
+        if os.path.dirname(path) == exedir:
+            return
+    vlog('# Making python executable in test path use correct Python')
+    mypython = os.path.join(BINDIR, 'python')
+    try:
+        os.symlink(sys.executable, mypython)
+    except AttributeError:
+        # windows fallback
+        shutil.copyfile(sys.executable, mypython)
+        shutil.copymode(sys.executable, mypython)
+
+def installhg(options):
+    vlog("# Performing temporary installation of HG")
+    installerrs = os.path.join("tests", "install.err")
+    pure = options.pure and "--pure" or ""
+
+    # Run installer in hg root
+    script = os.path.realpath(sys.argv[0])
+    hgroot = os.path.dirname(os.path.dirname(script))
+    os.chdir(hgroot)
+    nohome = '--home=""'
+    if os.name == 'nt':
+        # The --home="" trick works only on OS where os.sep == '/'
+        # because of a distutils convert_path() fast-path. Avoid it at
+        # least on Windows for now, deal with .pydistutils.cfg bugs
+        # when they happen.
+        nohome = ''
+    cmd = ('%s setup.py %s clean --all'
+           ' build --build-base="%s"'
+           ' install --force --prefix="%s" --install-lib="%s"'
+           ' --install-scripts="%s" %s >%s 2>&1'
+           % (sys.executable, pure, os.path.join(HGTMP, "build"),
+              INST, PYTHONDIR, BINDIR, nohome, installerrs))
+    vlog("# Running", cmd)
+    if os.system(cmd) == 0:
+        if not options.verbose:
+            os.remove(installerrs)
+    else:
+        f = open(installerrs)
+        for line in f:
+            print line,
+        f.close()
+        sys.exit(1)
+    os.chdir(TESTDIR)
+
+    usecorrectpython()
+
+    vlog("# Installing dummy diffstat")
+    f = open(os.path.join(BINDIR, 'diffstat'), 'w')
+    f.write('#!' + sys.executable + '\n'
+            'import sys\n'
+            'files = 0\n'
+            'for line in sys.stdin:\n'
+            '    if line.startswith("diff "):\n'
+            '        files += 1\n'
+            'sys.stdout.write("files patched: %d\\n" % files)\n')
+    f.close()
+    os.chmod(os.path.join(BINDIR, 'diffstat'), 0700)
+
+    if options.py3k_warnings and not options.anycoverage:
+        vlog("# Updating hg command to enable Py3k Warnings switch")
+        f = open(os.path.join(BINDIR, 'hg'), 'r')
+        lines = [line.rstrip() for line in f]
+        lines[0] += ' -3'
+        f.close()
+        f = open(os.path.join(BINDIR, 'hg'), 'w')
+        for line in lines:
+            f.write(line + '\n')
+        f.close()
+
+    if options.anycoverage:
+        custom = os.path.join(TESTDIR, 'sitecustomize.py')
+        target = os.path.join(PYTHONDIR, 'sitecustomize.py')
+        vlog('# Installing coverage trigger to %s' % target)
+        shutil.copyfile(custom, target)
+        rc = os.path.join(TESTDIR, '.coveragerc')
+        vlog('# Installing coverage rc to %s' % rc)
+        os.environ['COVERAGE_PROCESS_START'] = rc
+        fn = os.path.join(INST, '..', '.coverage')
+        os.environ['COVERAGE_FILE'] = fn
+
+def outputcoverage(options):
+
+    vlog('# Producing coverage report')
+    os.chdir(PYTHONDIR)
+
+    def covrun(*args):
+        cmd = 'coverage %s' % ' '.join(args)
+        vlog('# Running: %s' % cmd)
+        os.system(cmd)
+
+    if options.child:
+        return
+
+    covrun('-c')
+    omit = ','.join([BINDIR, TESTDIR])
+    covrun('-i', '-r', '"--omit=%s"' % omit) # report
+    if options.annotate:
+        adir = os.path.join(TESTDIR, 'annotated')
+        if not os.path.isdir(adir):
+            os.mkdir(adir)
+        covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
+
+class Timeout(Exception):
+    pass
+
+def alarmed(signum, frame):
+    raise Timeout
+
+def pytest(test, options, replacements):
+    py3kswitch = options.py3k_warnings and ' -3' or ''
+    cmd = '%s%s "%s"' % (PYTHON, py3kswitch, test)
+    vlog("# Running", cmd)
+    return run(cmd, options, replacements)
+
+def shtest(test, options, replacements):
+    cmd = '"%s"' % test
+    vlog("# Running", cmd)
+    return run(cmd, options, replacements)
+
+needescape = re.compile(r'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
+escapesub = re.compile(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
+escapemap = dict((chr(i), r'\x%02x' % i) for i in range(256))
+escapemap.update({'\\': '\\\\', '\r': r'\r'})
+def escapef(m):
+    return escapemap[m.group(0)]
+def stringescape(s):
+    return escapesub(escapef, s)
+
+def tsttest(test, options, replacements):
+    t = open(test)
+    out = []
+    script = []
+    salt = "SALT" + str(time.time())
+
+    pos = prepos = -1
+    after = {}
+    expected = {}
+    for n, l in enumerate(t):
+        if not l.endswith('\n'):
+            l += '\n'
+        if l.startswith('  $ '): # commands
+            after.setdefault(pos, []).append(l)
+            prepos = pos
+            pos = n
+            script.append('echo %s %s $?\n' % (salt, n))
+            script.append(l[4:])
+        elif l.startswith('  > '): # continuations
+            after.setdefault(prepos, []).append(l)
+            script.append(l[4:])
+        elif l.startswith('  '): # results
+            # queue up a list of expected results
+            expected.setdefault(pos, []).append(l[2:])
+        else:
+            # non-command/result - queue up for merged output
+            after.setdefault(pos, []).append(l)
+
+    t.close()
+
+    script.append('echo %s %s $?\n' % (salt, n + 1))
+
+    fd, name = tempfile.mkstemp(suffix='hg-tst')
+
+    try:
+        for l in script:
+            os.write(fd, l)
+        os.close(fd)
+
+        cmd = '/bin/sh "%s"' % name
+        vlog("# Running", cmd)
+        exitcode, output = run(cmd, options, replacements)
+        # do not merge output if skipped, return hghave message instead
+        # similarly, with --debug, output is None
+        if exitcode == SKIPPED_STATUS or output is None:
+            return exitcode, output
+    finally:
+        os.remove(name)
+
+    def rematch(el, l):
+        try:
+            # ensure that the regex matches to the end of the string
+            return re.match(el + r'\Z', l)
+        except re.error:
+            # el is an invalid regex
+            return False
+
+    def globmatch(el, l):
+        # The only supported special characters are * and ?. Escaping is
+        # supported.
+        i, n = 0, len(el)
+        res = ''
+        while i < n:
+            c = el[i]
+            i += 1
+            if c == '\\' and el[i] in '*?\\':
+                res += el[i - 1:i + 1]
+                i += 1
+            elif c == '*':
+                res += '.*'
+            elif c == '?':
+                res += '.'
+            else:
+                res += re.escape(c)
+        return rematch(res, l)
+
+    pos = -1
+    postout = []
+    ret = 0
+    for n, l in enumerate(output):
+        lout, lcmd = l, None
+        if salt in l:
+            lout, lcmd = l.split(salt, 1)
+
+        if lout:
+            if lcmd:
+                lout += ' (no-eol)\n'
+
+            el = None
+            if pos in expected and expected[pos]:
+                el = expected[pos].pop(0)
+
+            if el == lout: # perfect match (fast)
+                postout.append("  " + lout)
+            elif (el and
+                  (el.endswith(" (re)\n") and rematch(el[:-6] + '\n', lout) or
+                   el.endswith(" (glob)\n") and globmatch(el[:-8] + '\n', lout)
+                   or el.endswith(" (esc)\n") and
+                      el.decode('string-escape') == l)):
+                postout.append("  " + el) # fallback regex/glob/esc match
+            else:
+                if needescape(lout):
+                    lout = stringescape(lout.rstrip('\n')) + " (esc)\n"
+                postout.append("  " + lout) # let diff deal with it
+
+        if lcmd:
+            # add on last return code
+            ret = int(lcmd.split()[1])
+            if ret != 0:
+                postout.append("  [%s]\n" % ret)
+            if pos in after:
+                postout += after.pop(pos)
+            pos = int(lcmd.split()[0])
+
+    if pos in after:
+        postout += after.pop(pos)
+
+    return exitcode, postout
+
+wifexited = getattr(os, "WIFEXITED", lambda x: False)
+def run(cmd, options, replacements):
+    """Run command in a sub-process, capturing the output (stdout and stderr).
+    Return a tuple (exitcode, output).  output is None in debug mode."""
+    # TODO: Use subprocess.Popen if we're running on Python 2.4
+    if options.debug:
+        proc = subprocess.Popen(cmd, shell=True)
+        ret = proc.wait()
+        return (ret, None)
+
+    if os.name == 'nt' or sys.platform.startswith('java'):
+        tochild, fromchild = os.popen4(cmd)
+        tochild.close()
+        output = fromchild.read()
+        ret = fromchild.close()
+        if ret is None:
+            ret = 0
+    else:
+        proc = Popen4(cmd)
+        def cleanup():
+            os.kill(proc.pid, signal.SIGTERM)
+            ret = proc.wait()
+            if ret == 0:
+                ret = signal.SIGTERM << 8
+            killdaemons()
+            return ret
+
+        try:
+            output = ''
+            proc.tochild.close()
+            output = proc.fromchild.read()
+            ret = proc.wait()
+            if wifexited(ret):
+                ret = os.WEXITSTATUS(ret)
+        except Timeout:
+            vlog('# Process %d timed out - killing it' % proc.pid)
+            ret = cleanup()
+            output += ("\n### Abort: timeout after %d seconds.\n"
+                       % options.timeout)
+        except KeyboardInterrupt:
+            vlog('# Handling keyboard interrupt')
+            cleanup()
+            raise
+
+    for s, r in replacements:
+        output = re.sub(s, r, output)
+    return ret, splitnewlines(output)
+
+def runone(options, test, skips, fails):
+    '''tristate output:
+    None -> skipped
+    True -> passed
+    False -> failed'''
+
+    def skip(msg):
+        if not options.verbose:
+            skips.append((test, msg))
+        else:
+            print "\nSkipping %s: %s" % (testpath, msg)
+        return None
+
+    def fail(msg):
+        fails.append((test, msg))
+        if not options.nodiff:
+            print "\nERROR: %s %s" % (testpath, msg)
+        return None
+
+    vlog("# Test", test)
+
+    # create a fresh hgrc
+    hgrc = open(HGRCPATH, 'w+')
+    hgrc.write('[ui]\n')
+    hgrc.write('slash = True\n')
+    hgrc.write('[defaults]\n')
+    hgrc.write('backout = -d "0 0"\n')
+    hgrc.write('commit = -d "0 0"\n')
+    hgrc.write('tag = -d "0 0"\n')
+    if options.inotify:
+        hgrc.write('[extensions]\n')
+        hgrc.write('inotify=\n')
+        hgrc.write('[inotify]\n')
+        hgrc.write('pidfile=%s\n' % DAEMON_PIDS)
+        hgrc.write('appendpid=True\n')
+    hgrc.close()
+
+    testpath = os.path.join(TESTDIR, test)
+    ref = os.path.join(TESTDIR, test+".out")
+    err = os.path.join(TESTDIR, test+".err")
+    if os.path.exists(err):
+        os.remove(err)       # Remove any previous output files
+    try:
+        tf = open(testpath)
+        firstline = tf.readline().rstrip()
+        tf.close()
+    except:
+        firstline = ''
+    lctest = test.lower()
+
+    if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
+        runner = pytest
+    elif lctest.endswith('.t'):
+        runner = tsttest
+        ref = testpath
+    else:
+        # do not try to run non-executable programs
+        if not os.access(testpath, os.X_OK):
+            return skip("not executable")
+        runner = shtest
+
+    # Make a tmp subdirectory to work in
+    testtmp = os.environ["TESTTMP"] = os.path.join(HGTMP, test)
+    os.mkdir(testtmp)
+    os.chdir(testtmp)
+
+    if options.timeout > 0:
+        signal.alarm(options.timeout)
+
+    ret, out = runner(testpath, options, [
+        (re.escape(testtmp), '$TESTTMP'),
+        (r':%s\b' % options.port, ':$HGPORT'),
+        (r':%s\b' % (options.port + 1), ':$HGPORT1'),
+        (r':%s\b' % (options.port + 2), ':$HGPORT2'),
+        ])
+    vlog("# Ret was:", ret)
+
+    if options.timeout > 0:
+        signal.alarm(0)
+
+    mark = '.'
+
+    skipped = (ret == SKIPPED_STATUS)
+
+    # If we're not in --debug mode and reference output file exists,
+    # check test output against it.
+    if options.debug:
+        refout = None                   # to match "out is None"
+    elif os.path.exists(ref):
+        f = open(ref, "r")
+        refout = splitnewlines(f.read())
+        f.close()
+    else:
+        refout = []
+
+    if (ret != 0 or out != refout) and not skipped and not options.debug:
+        # Save errors to a file for diagnosis
+        f = open(err, "wb")
+        for line in out:
+            f.write(line)
+        f.close()
+
+    if skipped:
+        mark = 's'
+        if out is None:                 # debug mode: nothing to parse
+            missing = ['unknown']
+            failed = None
+        else:
+            missing, failed = parsehghaveoutput(out)
+        if not missing:
+            missing = ['irrelevant']
+        if failed:
+            fail("hghave failed checking for %s" % failed[-1])
+            skipped = False
+        else:
+            skip(missing[-1])
+    elif out != refout:
+        mark = '!'
+        if ret:
+            fail("output changed and returned error code %d" % ret)
+        else:
+            fail("output changed")
+        if not options.nodiff:
+            if options.view:
+                os.system("%s %s %s" % (options.view, ref, err))
+            else:
+                showdiff(refout, out, ref, err)
+        ret = 1
+    elif ret:
+        mark = '!'
+        fail("returned error code %d" % ret)
+
+    if not options.verbose:
+        try:
+            sys.stdout.write(mark)
+            sys.stdout.flush()
+        except IOError, ex:
+            print >>sys.stderr, 'BORKEN PIPE', ex.errno
+            pass
+
+    killdaemons()
+
+    os.chdir(TESTDIR)
+    if not options.keep_tmpdir:
+        shutil.rmtree(testtmp, True)
+    if skipped:
+        return None
+    return ret == 0
+
+_hgpath = None
+
+def _gethgpath():
+    """Return the path to the mercurial package that is actually found by
+    the current Python interpreter."""
+    global _hgpath
+    if _hgpath is not None:
+        return _hgpath
+
+    cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
+    pipe = os.popen(cmd % PYTHON)
+    try:
+        _hgpath = pipe.read().strip()
+    finally:
+        pipe.close()
+    return _hgpath
+
+def _checkhglib(verb):
+    """Ensure that the 'mercurial' package imported by python is
+    the one we expect it to be.  If not, print a warning to stderr."""
+    expecthg = os.path.join(PYTHONDIR, 'mercurial')
+    actualhg = _gethgpath()
+    if actualhg != expecthg:
+        sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
+                         '         (expected %s)\n'
+                         % (verb, actualhg, expecthg))
+
+def runchildren(options, tests):
+    if INST:
+        installhg(options)
+        _checkhglib("Testing")
+
+    optcopy = dict(options.__dict__)
+    optcopy['jobs'] = 1
+    del optcopy['blacklist']
+    if optcopy['with_hg'] is None:
+        optcopy['with_hg'] = os.path.join(BINDIR, "hg")
+    optcopy.pop('anycoverage', None)
+
+    opts = []
+    for opt, value in optcopy.iteritems():
+        name = '--' + opt.replace('_', '-')
+        if value is True:
+            opts.append(name)
+        elif value is not None:
+            opts.append(name + '=' + str(value))
+
+    tests.reverse()
+    jobs = [[] for j in xrange(options.jobs)]
+    while tests:
+        for job in jobs:
+            if not tests:
+                break
+            job.append(tests.pop())
+    fps = {}
+
+    for j, job in enumerate(jobs):
+        if not job:
+            continue
+        rfd, wfd = os.pipe()
+        childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
+        childtmp = os.path.join(HGTMP, 'child%d' % j)
+        childopts += ['--tmpdir', childtmp]
+        cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
+        vlog(' '.join(cmdline))
+        fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
+        os.close(wfd)
+    signal.signal(signal.SIGINT, signal.SIG_IGN)
+    failures = 0
+    tested, skipped, failed = 0, 0, 0
+    skips = []
+    fails = []
+    while fps:
+        pid, status = os.wait()
+        fp = fps.pop(pid)
+        l = fp.read().splitlines()
+        try:
+            test, skip, fail = map(int, l[:3])
+        except ValueError:
+            test, skip, fail = 0, 0, 0
+        split = -fail or len(l)
+        for s in l[3:split]:
+            skips.append(s.split(" ", 1))
+        for s in l[split:]:
+            fails.append(s.split(" ", 1))
+        tested += test
+        skipped += skip
+        failed += fail
+        vlog('pid %d exited, status %d' % (pid, status))
+        failures |= status
+    print
+    if not options.noskips:
+        for s in skips:
+            print "Skipped %s: %s" % (s[0], s[1])
+    for s in fails:
+        print "Failed %s: %s" % (s[0], s[1])
+
+    _checkhglib("Tested")
+    print "# Ran %d tests, %d skipped, %d failed." % (
+        tested, skipped, failed)
+
+    if options.anycoverage:
+        outputcoverage(options)
+    sys.exit(failures != 0)
+
+def runtests(options, tests):
+    global DAEMON_PIDS, HGRCPATH
+    DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
+    HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
+
+    try:
+        if INST:
+            installhg(options)
+            _checkhglib("Testing")
+
+        if options.timeout > 0:
+            try:
+                signal.signal(signal.SIGALRM, alarmed)
+                vlog('# Running each test with %d second timeout' %
+                     options.timeout)
+            except AttributeError:
+                print 'WARNING: cannot run tests with timeouts'
+                options.timeout = 0
+
+        tested = 0
+        failed = 0
+        skipped = 0
+
+        if options.restart:
+            orig = list(tests)
+            while tests:
+                if os.path.exists(tests[0] + ".err"):
+                    break
+                tests.pop(0)
+            if not tests:
+                print "running all tests"
+                tests = orig
+
+        skips = []
+        fails = []
+
+        for test in tests:
+            if options.blacklist:
+                filename = options.blacklist.get(test)
+                if filename is not None:
+                    skips.append((test, "blacklisted (%s)" % filename))
+                    skipped += 1
+                    continue
+
+            if options.retest and not os.path.exists(test + ".err"):
+                skipped += 1
+                continue
+
+            if options.keywords:
+                fp = open(test)
+                t = fp.read().lower() + test.lower()
+                fp.close()
+                for k in options.keywords.lower().split():
+                    if k in t:
+                        break
+                else:
+                    skipped += 1
+                    continue
+
+            ret = runone(options, test, skips, fails)
+            if ret is None:
+                skipped += 1
+            elif not ret:
+                if options.interactive:
+                    print "Accept this change? [n] ",
+                    answer = sys.stdin.readline().strip()
+                    if answer.lower() in "y yes".split():
+                        if test.endswith(".t"):
+                            rename(test + ".err", test)
+                        else:
+                            rename(test + ".err", test + ".out")
+                        tested += 1
+                        fails.pop()
+                        continue
+                failed += 1
+                if options.first:
+                    break
+            tested += 1
+
+        if options.child:
+            fp = os.fdopen(options.child, 'w')
+            fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
+            for s in skips:
+                fp.write("%s %s\n" % s)
+            for s in fails:
+                fp.write("%s %s\n" % s)
+            fp.close()
+        else:
+            print
+            for s in skips:
+                print "Skipped %s: %s" % s
+            for s in fails:
+                print "Failed %s: %s" % s
+            _checkhglib("Tested")
+            print "# Ran %d tests, %d skipped, %d failed." % (
+                tested, skipped, failed)
+
+        if options.anycoverage:
+            outputcoverage(options)
+    except KeyboardInterrupt:
+        failed = True
+        print "\ninterrupted!"
+
+    if failed:
+        sys.exit(1)
+
+def main():
+    (options, args) = parseargs()
+    if not options.child:
+        os.umask(022)
+
+        checktools()
+
+    if len(args) == 0:
+        args = os.listdir(".")
+    args.sort()
+
+    tests = []
+    skipped = []
+    for test in args:
+        if (test.startswith("test-") and '~' not in test and
+            ('.' not in test or test.endswith('.py') or
+             test.endswith('.bat') or test.endswith('.t'))):
+            if not os.path.exists(test):
+                skipped.append(test)
+            else:
+                tests.append(test)
+    if not tests:
+        for test in skipped:
+            print 'Skipped %s: does not exist' % test
+        print "# Ran 0 tests, %d skipped, 0 failed." % len(skipped)
+        return
+    tests = tests + skipped
+
+    # Reset some environment variables to well-known values so that
+    # the tests produce repeatable output.
+    os.environ['LANG'] = os.environ['LC_ALL'] = os.environ['LANGUAGE'] = 'C'
+    os.environ['TZ'] = 'GMT'
+    os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
+    os.environ['CDPATH'] = ''
+    os.environ['COLUMNS'] = '80'
+    os.environ['GREP_OPTIONS'] = ''
+    os.environ['http_proxy'] = ''
+
+    # unset env related to hooks
+    for k in os.environ.keys():
+        if k.startswith('HG_'):
+            # can't remove on solaris
+            os.environ[k] = ''
+            del os.environ[k]
+
+    global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
+    TESTDIR = os.environ["TESTDIR"] = os.getcwd()
+    if options.tmpdir:
+        options.keep_tmpdir = True
+        tmpdir = options.tmpdir
+        if os.path.exists(tmpdir):
+            # Meaning of tmpdir has changed since 1.3: we used to create
+            # HGTMP inside tmpdir; now HGTMP is tmpdir.  So fail if
+            # tmpdir already exists.
+            sys.exit("error: temp dir %r already exists" % tmpdir)
+
+            # Automatically removing tmpdir sounds convenient, but could
+            # really annoy anyone in the habit of using "--tmpdir=/tmp"
+            # or "--tmpdir=$HOME".
+            #vlog("# Removing temp dir", tmpdir)
+            #shutil.rmtree(tmpdir)
+        os.makedirs(tmpdir)
+    else:
+        tmpdir = tempfile.mkdtemp('', 'hgtests.')
+    HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
+    DAEMON_PIDS = None
+    HGRCPATH = None
+
+    os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
+    os.environ["HGMERGE"] = "internal:merge"
+    os.environ["HGUSER"]   = "test"
+    os.environ["HGENCODING"] = "ascii"
+    os.environ["HGENCODINGMODE"] = "strict"
+    os.environ["HGPORT"] = str(options.port)
+    os.environ["HGPORT1"] = str(options.port + 1)
+    os.environ["HGPORT2"] = str(options.port + 2)
+
+    if options.with_hg:
+        INST = None
+        BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
+
+        # This looks redundant with how Python initializes sys.path from
+        # the location of the script being executed.  Needed because the
+        # "hg" specified by --with-hg is not the only Python script
+        # executed in the test suite that needs to import 'mercurial'
+        # ... which means it's not really redundant at all.
+        PYTHONDIR = BINDIR
+    else:
+        INST = os.path.join(HGTMP, "install")
+        BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
+        PYTHONDIR = os.path.join(INST, "lib", "python")
+
+    os.environ["BINDIR"] = BINDIR
+    os.environ["PYTHON"] = PYTHON
+
+    if not options.child:
+        path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
+        os.environ["PATH"] = os.pathsep.join(path)
+
+        # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
+        # can run .../tests/run-tests.py test-foo where test-foo
+        # adds an extension to HGRC
+        pypath = [PYTHONDIR, TESTDIR]
+        # We have to augment PYTHONPATH, rather than simply replacing
+        # it, in case external libraries are only available via current
+        # PYTHONPATH.  (In particular, the Subversion bindings on OS X
+        # are in /opt/subversion.)
+        oldpypath = os.environ.get(IMPL_PATH)
+        if oldpypath:
+            pypath.append(oldpypath)
+        os.environ[IMPL_PATH] = os.pathsep.join(pypath)
+
+    COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
+
+    vlog("# Using TESTDIR", TESTDIR)
+    vlog("# Using HGTMP", HGTMP)
+    vlog("# Using PATH", os.environ["PATH"])
+    vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
+
+    try:
+        if len(tests) > 1 and options.jobs > 1:
+            runchildren(options, tests)
+        else:
+            runtests(options, tests)
+    finally:
+        time.sleep(1)
+        cleanup(options)
+
+if __name__ == '__main__':
+    main()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-amend.t	Mon Jul 30 14:45:42 2012 +0200
@@ -0,0 +1,105 @@
+  $ cat >> $HGRCPATH <<EOF
+  > [defaults]
+  > amend=-d "0 0"
+  > [extensions]
+  > 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}@{branch}({phase}) {desc|firstline}\n' "$@"
+  > }
+
+  $ hg init repo
+  $ cd repo
+  $ echo a > a
+  $ hg ci -Am adda
+  adding a
+
+Test amend captures branches
+
+  $ hg branch foo
+  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 branch
+  foo
+  $ hg branches
+  foo                            2:a34b93d251e4
+  default                        0:07f494440405 (inactive)
+  $ glog
+  @  2@foo(draft) adda
+  
+Test no-op
+
+  $ hg amend
+  abort: no updates found
+  [255]
+  $ glog
+  @  2@foo(draft) adda
+  
+
+Test forcing the message to the same value, no intermediate revision.
+
+  $ hg amend -m 'adda'
+  abort: no updates found
+  [255]
+  $ glog
+  @  2@foo(draft) adda
+  
+
+Test collapsing into an existing revision, no intermediate revision.
+
+  $ echo a >> a
+  $ hg ci -m changea
+  $ echo a > a
+  $ hg ci -m reseta
+  $ hg amend --change 2
+  abort: no updates found
+  [255]
+  $ hg debugsuccessors
+  07f494440405 a34b93d251e4
+  bd19cbe78fbf a34b93d251e4
+  $ hg phase 2
+  2: draft
+  $ glog
+  @  4@foo(draft) reseta
+  |
+  o  3@foo(draft) changea
+  |
+  o  2@foo(draft) adda
+  
+
+Test collapsing into an existing rev, with an intermediate revision.
+
+  $ hg branch --force default
+  marked working directory as branch default
+  (branches are permanent and global, did you want a bookmark?)
+  $ hg ci -m resetbranch
+  created new head
+  $ hg branch --force foo
+  marked working directory as branch foo
+  (branches are permanent and global, did you want a bookmark?)
+  $ hg amend --change 2
+  abort: no updates found
+  [255]
+  $ hg debugsuccessors
+  07f494440405 a34b93d251e4
+  7384bbcba36f 000000000000
+  bd19cbe78fbf a34b93d251e4
+  $ glog
+  @  6@foo(draft) amends a34b93d251e49c93d5685ebacad785c73a7e8605
+  |
+  o  5@default(draft) resetbranch
+  |
+  o  4@foo(draft) reseta
+  |
+  o  3@foo(draft) changea
+  |
+  o  2@foo(draft) adda
+  
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-corrupt.t	Mon Jul 30 14:45:42 2012 +0200
@@ -0,0 +1,122 @@
+
+  $ cat >> $HGRCPATH <<EOF
+  > [defaults]
+  > amend=-d "0 0"
+  > [web]
+  > push_ssl = false
+  > allow_push = *
+  > [phases]
+  > publish = False
+  > [alias]
+  > qlog = log --template='{rev} - {node|short} {desc} ({phase})\n'
+  > [diff]
+  > git = 1
+  > unified = 0
+  > [extensions]
+  > 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"
+  >    hg add "$1"
+  >    hg ci -m "add $1"
+  > }
+
+  $ hg init local
+  $ hg init other
+  $ cd local
+  $ touch 1 2 3 4 5 6 7 8 9 0
+  $ hg add 1 2 3 4 5 6 7 8 9 0
+  $ mkcommit A
+  $ mkcommit B
+  $ mkcommit C
+  $ hg glog
+  @  changeset:   2:829b19580856
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     add C
+  |
+  o  changeset:   1:97b8f02ab29e
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     add B
+  |
+  o  changeset:   0:5d8dabd3961b
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     add A
+  
+  $ hg push ../other
+  pushing to ../other
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 3 changesets with 13 changes to 13 files
+
+
+  $ hg -R ../other verify
+  checking changesets
+  checking manifests
+  crosschecking files in changesets and manifests
+  checking files
+  13 files, 3 changesets, 13 total revisions
+  $ mkcommit D
+  $ mkcommit E
+  $ hg up -q .^^
+  $ hg revert -r tip -a -q
+  $ hg ci -m 'coin' -q
+  $ hg glog
+  @  changeset:   5:8313a6afebbb
+  |  tag:         tip
+  |  parent:      2:829b19580856
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     coin
+  |
+  | o  changeset:   4:076ec8ade1ac
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     add E
+  | |
+  | o  changeset:   3:824d9bb109f6
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    summary:     add D
+  |
+  o  changeset:   2:829b19580856
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     add C
+  |
+  o  changeset:   1:97b8f02ab29e
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     add B
+  |
+  o  changeset:   0:5d8dabd3961b
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     add A
+  
+
+  $ hg kill -n -1 -- -2 -3
+  $ hg push ../other
+  pushing to ../other
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 2 changes to 2 files
+  $ hg -R ../other verify
+  checking changesets
+  checking manifests
+  crosschecking files in changesets and manifests
+  checking files
+  15 files, 4 changesets, 15 total revisions
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-evolve.t	Mon Jul 30 14:45:42 2012 +0200
@@ -0,0 +1,358 @@
+  $ cat >> $HGRCPATH <<EOF
+  > [defaults]
+  > amend=-d "0 0"
+  > [web]
+  > push_ssl = false
+  > allow_push = *
+  > [phases]
+  > publish = False
+  > [alias]
+  > qlog = log --template='{rev} - {node|short} {desc} ({phase})\n'
+  > [diff]
+  > git = 1
+  > unified = 0
+  > [extensions]
+  > 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"
+  >    hg add "$1"
+  >    hg ci -m "add $1"
+  > }
+
+  $ glog() {
+  >   hg glog --template '{rev}:{node|short}@{branch}({phase}) {desc|firstline}\n' "$@"
+  > }
+
+various init
+
+  $ hg init local
+  $ cd local
+  $ mkcommit a
+  $ mkcommit b
+  $ cat >> .hg/hgrc << EOF
+  > [phases]
+  > publish = True
+  > EOF
+  $ hg pull -q . # make 1 public
+  $ rm .hg/hgrc
+  $ mkcommit c
+  $ mkcommit d
+  $ hg up 1
+  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  $ mkcommit e -q
+  created new head
+  $ mkcommit f
+  $ hg qlog
+  5 - e44648563c73 add f (draft)
+  4 - fbb94e3a0ecf add e (draft)
+  3 - 47d2a3944de8 add d (draft)
+  2 - 4538525df7e2 add c (draft)
+  1 - 7c3bad9141dc add b (public)
+  0 - 1f0dee641bb7 add a (public)
+
+test simple kill
+
+  $ hg id -n
+  5
+  $ hg kill .
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  working directory now at fbb94e3a0ecf
+  $ hg qlog
+  4 - fbb94e3a0ecf add e (draft)
+  3 - 47d2a3944de8 add d (draft)
+  2 - 4538525df7e2 add c (draft)
+  1 - 7c3bad9141dc add b (public)
+  0 - 1f0dee641bb7 add a (public)
+
+test multiple kill
+
+  $ hg kill 4 3
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  working directory now at 7c3bad9141dc
+  $ hg qlog
+  2 - 4538525df7e2 add c (draft)
+  1 - 7c3bad9141dc add b (public)
+  0 - 1f0dee641bb7 add a (public)
+
+test kill with dirty changes
+
+  $ hg up 2
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ echo 4 > g
+  $ hg add g
+  $ hg kill .
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  working directory now at 7c3bad9141dc
+  $ hg st
+  A g
+  $ cd ..
+
+##########################
+importing Parren test
+##########################
+
+  $ cat << EOF >> $HGRCPATH
+  > [ui]
+  > logtemplate = "{rev}\t{bookmarks}: {desc|firstline} - {author|user}\n"
+  > EOF
+
+Creating And Updating Changeset
+===============================
+
+Setup the Base Repo
+-------------------
+
+We start with a plain base repo::
+
+  $ hg init main; cd main
+  $ cat >main-file-1 <<-EOF
+  > One
+  > 
+  > Two
+  > 
+  > Three
+  > EOF
+  $ echo Two >main-file-2
+  $ hg add
+  adding main-file-1
+  adding main-file-2
+  $ hg commit --message base
+  $ cd ..
+
+and clone this into a new repo where we do our work::
+
+  $ hg clone main work
+  updating to branch default
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cd work
+
+
+Create First Patch
+------------------
+
+To begin with, we just do the changes that will be the initial version of the changeset::
+
+  $ echo One >file-from-A
+  $ sed -i'' -e s/One/Eins/ main-file-1
+  $ hg add file-from-A
+
+So this is what we would like our changeset to be::
+
+  $ hg diff
+  diff --git a/file-from-A b/file-from-A
+  new file mode 100644
+  --- /dev/null
+  +++ b/file-from-A
+  @@ -0,0 +1,1 @@
+  +One
+  diff --git a/main-file-1 b/main-file-1
+  --- a/main-file-1
+  +++ b/main-file-1
+  @@ -1,1 +1,1 @@
+  -One
+  +Eins
+
+To commit it we just - commit it::
+
+  $ hg commit --message "a nifty feature"
+
+and place a bookmark so we can easily refer to it again (which we could have done before the commit)::
+
+  $ hg book feature-A
+
+
+Create Second Patch
+-------------------
+
+Let's do this again for the second changeset::
+
+  $ echo Two >file-from-B
+  $ sed -i'' -e s/Two/Zwie/ main-file-1
+  $ hg add file-from-B
+
+Before committing, however, we need to switch to a new bookmark for the second
+changeset. Otherwise we would inadvertently move the bookmark for our first changeset.
+It is therefore advisable to always set the bookmark before committing::
+
+  $ hg book feature-B
+  $ hg commit --message "another feature"
+
+So here we are::
+
+  $ hg book
+     feature-A                 1:568a468b60fc
+   * feature-B                 2:7b36850622b2
+
+
+Fix The Second Patch
+--------------------
+
+There's a typo in feature-B. We spelled *Zwie* instead of *Zwei*::
+
+  $ hg diff --change tip | grep -F Zwie
+  +Zwie
+
+Fixing this is very easy. Just change::
+
+  $ sed -i'' -e s/Zwie/Zwei/ main-file-1
+
+and **amend**::
+
+  $ hg amend --note "fix spelling of Zwei"
+
+The `--note` is our commit message for the *update* only. So its only purpose
+is to document the evolution of the changeset. If we use `--message` with
+`amend`, it replaces the commit message of the changeset itself.
+
+This results in a new single changeset for our amended changeset, and the old
+changeset plus the updating changeset are hidden from view by default::
+
+  $ hg log
+  4	feature-B: another feature - test
+  1	feature-A: a nifty feature - test
+  0	: base - test
+
+  $ hg up feature-A -q
+  $ hg bookmark -i feature-A
+  $ sed -i'' -e s/Eins/Un/ main-file-1
+
+  $ hg amend --note 'french looks better'
+  1 new unstables changesets
+  $ hg log
+  6	feature-A: a nifty feature - test
+  4	feature-B: another feature - test
+  1	: a nifty feature - test
+  0	: base - test
+  $ hg up -q 0
+  $ glog --hidden
+  o  6:23409eba69a0@default(draft) a nifty feature
+  |
+  | x  5:e416e48b2742@default(draft) french looks better
+  | |
+  | | o  4:f8111a076f09@default(draft) another feature
+  | |/
+  | | x  3:524e478d4811@default(draft) fix spelling of Zwei
+  | | |
+  | | x  2:7b36850622b2@default(draft) another feature
+  | |/
+  | x  1:568a468b60fc@default(draft) a nifty feature
+  |/
+  @  0:e55e0562ee93@default(draft) base
+  
+  $ hg debugsuccessors
+  524e478d4811 f8111a076f09
+  568a468b60fc 23409eba69a0
+  7b36850622b2 f8111a076f09
+  e416e48b2742 23409eba69a0
+  $ hg stabilize
+  move:[4] another feature
+  atop:[6] a nifty feature
+  merging main-file-1
+  $ hg log
+  7	feature-B: another feature - test
+  6	feature-A: a nifty feature - test
+  0	: base - test
+
+Test commit -o options
+
+  $ hg up 6
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ hg revert -r 7 --all
+  adding file-from-B
+  reverting main-file-1
+  $ sed -i'' -e s/Zwei/deux/ main-file-1
+  $ hg commit -m 'another feature that rox' -o 7
+  created new head
+  $ hg log
+  8	feature-B: another feature that rox - test
+  6	feature-A: a nifty feature - test
+  0	: base - test
+
+phase change turning obsolete changeset public issue a latecomer warning
+
+  $ hg phase --public 7
+  1 new latecomers changesets
+
+  $ cd ..
+
+enable general delta
+
+  $ cat << EOF >> $HGRCPATH
+  > [format]
+  > generaldelta=1
+  > EOF
+
+
+
+  $ hg init alpha
+  $ cd alpha
+  $ echo 'base' > firstfile
+  $ hg add firstfile
+  $ hg ci -m 'base'
+
+  $ cd ..
+  $ hg clone -Ur 0 alpha beta
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+  $ cd alpha
+
+  $ cat << EOF > A
+  > We
+  > need
+  > some
+  > kind
+  > of 
+  > file
+  > big
+  > enough
+  > to
+  > prevent
+  > snapshot
+  > .
+  > yes
+  > new
+  > lines
+  > are
+  > useless
+  > .
+  > EOF
+  $ hg add A
+  $ hg commit -m 'adding A'
+  $ hg mv A B
+  $ echo '.' >> B
+  $ hg amend -m 'add B'
+  $ hg verify
+  checking changesets
+  checking manifests
+  crosschecking files in changesets and manifests
+  checking files
+  3 files, 4 changesets, 4 total revisions
+  $ hg --config extensions.hgext.mq= strip 'extinct()'
+  saved backup bundle to $TESTTMP/alpha/.hg/strip-backup/e87767087a57-backup.hg
+  $ hg verify
+  checking changesets
+  checking manifests
+  crosschecking files in changesets and manifests
+  checking files
+  2 files, 2 changesets, 2 total revisions
+  $ cd ..
+
+Clone just this branch
+
+  $ cd beta
+  $ hg pull -r tip ../alpha
+  pulling from ../alpha
+  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 up
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-obsolete-push.t	Mon Jul 30 14:45:42 2012 +0200
@@ -0,0 +1,47 @@
+  $ cat >> $HGRCPATH <<EOF
+  > [defaults]
+  > amend=-d "0 0"
+  > [extensions]
+  > 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'
+  $ glog() {
+  >   hg glog --template "$template" "$@"
+  > }
+
+Test outgoing, common A is suspended, B unstable and C secret, remote
+has A and B, neither A or C should be in outgoing.
+
+  $ hg init source
+  $ cd source
+  $ echo a > a
+  $ hg ci -qAm A a
+  $ echo b > b
+  $ hg ci -qAm B b
+  $ hg up 0
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ echo c > c
+  $ hg ci -qAm C c
+  $ hg phase --secret --force .
+  $ hg kill 0 1
+  1 new unstables changesets
+  $ glog --hidden
+  @  2:244232c2222a@default(unstable/secret) C
+  |
+  | x  1:6c81ed0049f8@default(extinct/draft) B
+  |/
+  x  0:1994f17a630e@default(suspended/draft) A
+  
+  $ hg init ../clone
+  $ cat >  ../clone/.hg/hgrc <<EOF
+  > [phases]
+  > publish = false
+  > EOF
+  $ hg outgoing ../clone --template "$template"
+  comparing with ../clone
+  searching for changes
+  0:1994f17a630e@default(suspended/draft) A
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-obsolete-rebase.t	Mon Jul 30 14:45:42 2012 +0200
@@ -0,0 +1,218 @@
+  $ cat >> $HGRCPATH <<EOF
+  > [defaults]
+  > amend=-d "0 0"
+  > [extensions]
+  > hgext.rebase=
+  > hgext.graphlog=
+  > EOF
+  $ echo "obsolete=$(echo $(dirname $TESTDIR))/hgext/obsolete.py" >> $HGRCPATH
+
+  $ glog() {
+  >   hg glog --template '{rev}:{node|short}@{branch}({phase}) {desc|firstline}\n'\
+  >     "$@"
+  > }
+
+  $ hg init repo
+  $ cd repo
+  $ echo a > a
+  $ hg ci -Am adda
+  adding a
+  $ echo a >> a
+  $ hg ci -m changea
+
+Test regular rebase
+
+  $ hg up 0
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ echo b > b
+  $ hg ci -Am addb
+  adding b
+  created new head
+  $ echo e > e
+  $ hg ci -Am adde e
+  $ hg rebase -d 1 -r 3 --detach --keep  
+  $ glog
+  @  4:9c5494949763@default(draft) adde
+  |
+  | o  3:98e4a024635e@default(draft) adde
+  | |
+  | o  2:102a90ea7b4a@default(draft) addb
+  | |
+  o |  1:540395c44225@default(draft) changea
+  |/
+  o  0:07f494440405@default(draft) adda
+  
+  $ glog --hidden
+  @  4:9c5494949763@default(draft) adde
+  |
+  | o  3:98e4a024635e@default(draft) adde
+  | |
+  | o  2:102a90ea7b4a@default(draft) addb
+  | |
+  o |  1:540395c44225@default(draft) changea
+  |/
+  o  0:07f494440405@default(draft) adda
+  
+  $ hg debugsuccessors
+  $ 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
+  $ hg rebase -d 1 -r 3 --detach
+  $ glog
+  @  4:9c5494949763@default(draft) adde
+  |
+  | o  2:102a90ea7b4a@default(draft) addb
+  | |
+  o |  1:540395c44225@default(draft) changea
+  |/
+  o  0:07f494440405@default(draft) adda
+  
+  $ glog --hidden
+  @  4:9c5494949763@default(draft) adde
+  |
+  | x  3:98e4a024635e@default(draft) adde
+  | |
+  | o  2:102a90ea7b4a@default(draft) addb
+  | |
+  o |  1:540395c44225@default(draft) changea
+  |/
+  o  0:07f494440405@default(draft) adda
+  
+  $ hg debugsuccessors
+  98e4a024635e 9c5494949763
+
+Test rebase with deleted empty revision
+
+  $ hg up 0
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ hg branch foo
+  marked working directory as branch foo
+  (branches are permanent and global, did you want a bookmark?)
+  $ echo a >> a
+  $ hg ci -m changea
+  $ hg rebase -d 1
+  $ glog --hidden
+  x  5:4e322f7ce8e3@foo(draft) changea
+  |
+  | o  4:9c5494949763@default(draft) adde
+  | |
+  | | x  3:98e4a024635e@default(draft) adde
+  | | |
+  +---o  2:102a90ea7b4a@default(draft) addb
+  | |
+  | @  1:540395c44225@default(draft) changea
+  |/
+  o  0:07f494440405@default(draft) adda
+  
+  $ hg debugsuccessors
+  4e322f7ce8e3 000000000000
+  98e4a024635e 9c5494949763
+
+Test rebase --collapse
+
+  $ hg up 0
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ echo c > c
+  $ hg ci -Am addc
+  adding c
+  created new head
+  $ echo c >> c
+  $ hg ci -m changec
+  $ hg rebase --collapse -d 1
+  merging c
+  $ glog --hidden
+  @  8:a7773ffa7edc@default(draft) Collapsed revision
+  |
+  | x  7:03f31481307a@default(draft) changec
+  | |
+  | x  6:076e9b2ffbe1@default(draft) addc
+  | |
+  | | x  5:4e322f7ce8e3@foo(draft) changea
+  | |/
+  +---o  4:9c5494949763@default(draft) adde
+  | |
+  | | x  3:98e4a024635e@default(draft) adde
+  | | |
+  | | o  2:102a90ea7b4a@default(draft) addb
+  | |/
+  o |  1:540395c44225@default(draft) changea
+  |/
+  o  0:07f494440405@default(draft) adda
+  
+  $ hg debugsuccessors
+  03f31481307a a7773ffa7edc
+  076e9b2ffbe1 a7773ffa7edc
+  4e322f7ce8e3 000000000000
+  98e4a024635e 9c5494949763
+
+Test rebase --abort
+
+  $ hg debugsuccessors > ../successors.old
+  $ hg up 0
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ echo d > d
+  $ hg ci -Am addd d
+  created new head
+  $ echo b >> a
+  $ hg ci -m appendab
+  $ hg rebase -d 1
+  merging a
+  warning: conflicts during merge.
+  merging a incomplete! (edit conflicts, then use 'hg resolve --mark')
+  abort: unresolved conflicts (see hg resolve, then hg rebase --continue)
+  [255]
+  $ hg rebase --abort
+  saved backup bundle to $TESTTMP/repo/.hg/strip-backup/03f165c84ea8-backup.hg
+  rebase aborted
+  $ hg debugsuccessors > ../successors.new
+  $ diff -u ../successors.old ../successors.new
+
+Test rebase --continue
+
+  $ hg rebase -d 1
+  merging a
+  warning: conflicts during merge.
+  merging a incomplete! (edit conflicts, then use 'hg resolve --mark')
+  abort: unresolved conflicts (see hg resolve, then hg rebase --continue)
+  [255]
+  $ hg resolve --tool internal:other a
+  $ hg rebase --continue
+  $ glog --hidden
+  @  12:1951ead97108@default(draft) appendab
+  |
+  o  11:03f165c84ea8@default(draft) addd
+  |
+  | x  10:4b9d80f48523@default(draft) appendab
+  | |
+  | x  9:a31943eabc43@default(draft) addd
+  | |
+  +---o  8:a7773ffa7edc@default(draft) Collapsed revision
+  | |
+  | | x  7:03f31481307a@default(draft) changec
+  | | |
+  | | x  6:076e9b2ffbe1@default(draft) addc
+  | |/
+  | | x  5:4e322f7ce8e3@foo(draft) changea
+  | |/
+  +---o  4:9c5494949763@default(draft) adde
+  | |
+  | | x  3:98e4a024635e@default(draft) adde
+  | | |
+  | | o  2:102a90ea7b4a@default(draft) addb
+  | |/
+  o |  1:540395c44225@default(draft) changea
+  |/
+  o  0:07f494440405@default(draft) adda
+  
+  $ hg debugsuccessors > ../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
+  [1]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-obsolete.t	Mon Jul 30 14:45:42 2012 +0200
@@ -0,0 +1,626 @@
+  $ cat >> $HGRCPATH <<EOF
+  > [web]
+  > push_ssl = false
+  > allow_push = *
+  > [phases]
+  > publish=False
+  > [alias]
+  > odiff=diff --rev 'limit(precursors(.),1)' --rev .
+  > [extensions]
+  > hgext.graphlog=
+  > EOF
+  $ echo "obsolete=$(echo $(dirname $TESTDIR))/hgext/obsolete.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
+  $ cd local
+  $ mkcommit a # 0
+  $ hg phase -p .
+  $ mkcommit b # 1
+  $ mkcommit c # 2
+  $ hg up 1
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ mkcommit obsol_c # 3
+  created new head
+  $ getid 2
+  4538525df7e2b9f09423636c61ef63a4cb872a2d
+  $ getid 3
+  0d3f46688ccc6e756c7e96cf64c391c411309597
+  $ hg debugobsolete 4538525df7e2b9f09423636c61ef63a4cb872a2d 0d3f46688ccc6e756c7e96cf64c391c411309597
+  $ hg debugobsolete
+  4538525df7e2b9f09423636c61ef63a4cb872a2d 0d3f46688ccc6e756c7e96cf64c391c411309597 0 {'date': '', 'user': 'test'}
+
+
+Test hidden() revset
+
+  $ qlog -r 'hidden()' --hidden
+  2
+  - 4538525df7e2
+
+Test that obsolete changeset are hidden
+
+  $ qlog
+  3
+  - 0d3f46688ccc
+  1
+  - 7c3bad9141dc
+  0
+  - 1f0dee641bb7
+  $ qlog --hidden
+  3
+  - 0d3f46688ccc
+  2
+  - 4538525df7e2
+  1
+  - 7c3bad9141dc
+  0
+  - 1f0dee641bb7
+  $ qlog -r 'obsolete()' --hidden
+  2
+  - 4538525df7e2
+
+Test that obsolete parent a properly computed
+
+  $ qlog -r 'precursors(.)' --hidden
+  2
+  - 4538525df7e2
+  $ qlog -r .
+  3
+  - 0d3f46688ccc
+  $ hg odiff
+  diff -r 4538525df7e2 -r 0d3f46688ccc c
+  --- a/c	Thu Jan 01 00:00:00 1970 +0000
+  +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,1 +0,0 @@
+  -c
+  diff -r 4538525df7e2 -r 0d3f46688ccc obsol_c
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/obsol_c	Thu Jan 01 00:00:00 1970 +0000
+  @@ -0,0 +1,1 @@
+  +obsol_c
+
+Test that obsolete successors a properly computed
+
+  $ qlog -r 'successors(2)' --hidden
+  3
+  - 0d3f46688ccc
+
+test obsolete changeset with no-obsolete descendant
+  $ hg up 1 -q
+  $ mkcommit "obsol_c'" # 4 (on 1)
+  created new head
+  $ hg debugobsolete `getid 3` `getid 4`
+  $ qlog
+  4
+  - 725c380fe99b
+  1
+  - 7c3bad9141dc
+  0
+  - 1f0dee641bb7
+  $ qlog -r 'obsolete()' --hidden
+  2
+  - 4538525df7e2
+  3
+  - 0d3f46688ccc
+  $ qlog -r 'allprecursors(4)' --hidden
+  2
+  - 4538525df7e2
+  3
+  - 0d3f46688ccc
+  $ qlog -r 'allsuccessors(2)' --hidden
+  3
+  - 0d3f46688ccc
+  4
+  - 725c380fe99b
+  $ hg up 3 -q
+  Working directory parent is obsolete
+  $ mkcommit d # 5 (on 3)
+  1 new unstables changesets
+  $ qlog -r 'obsolete()'
+  3
+  - 0d3f46688ccc
+
+  $ qlog -r 'extinct()' --hidden
+  2
+  - 4538525df7e2
+  $ qlog -r 'suspended()'
+  3
+  - 0d3f46688ccc
+  $ qlog -r 'unstable()'
+  5
+  - a7a6f2b5d8a5
+
+Test obsolete keyword
+
+  $ hg glog --template '{rev}:{node|short}@{branch}({obsolete}/{phase}) {desc|firstline}\n' \
+  >   --hidden
+  @  5:a7a6f2b5d8a5@default(unstable/draft) add d
+  |
+  | o  4:725c380fe99b@default(stable/draft) add obsol_c'
+  | |
+  x |  3:0d3f46688ccc@default(suspended/draft) add obsol_c
+  |/
+  | x  2:4538525df7e2@default(extinct/draft) add c
+  |/
+  o  1:7c3bad9141dc@default(stable/draft) add b
+  |
+  o  0:1f0dee641bb7@default(stable/public) add a
+  
+
+Test communication of obsolete relation with a compatible client
+
+  $ 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 ignore warnings)
+  [255]
+  $ hg push -f ../other-new
+  pushing to ../other-new
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 5 changesets with 5 changes to 5 files (+1 heads)
+  $ hg -R ../other-new verify
+  checking changesets
+  checking manifests
+  crosschecking files in changesets and manifests
+  checking files
+  5 files, 5 changesets, 5 total revisions
+  $ qlog -R ../other-new -r 'obsolete()'
+  2
+  - 0d3f46688ccc
+  $ qlog -R ../other-new
+  4
+  - a7a6f2b5d8a5
+  3
+  - 725c380fe99b
+  2
+  - 0d3f46688ccc
+  1
+  - 7c3bad9141dc
+  0
+  - 1f0dee641bb7
+  $ hg up 3 -q
+  Working directory parent is obsolete
+  $ mkcommit obsol_d # 6
+  created new head
+  1 new unstables changesets
+  $ hg debugobsolete `getid 5` `getid 6`
+  $ qlog
+  6
+  - 95de7fc6918d
+  4
+  - 725c380fe99b
+  3
+  - 0d3f46688ccc
+  1
+  - 7c3bad9141dc
+  0
+  - 1f0dee641bb7
+  $ qlog -r 'obsolete()'
+  3
+  - 0d3f46688ccc
+  $ hg push ../other-new
+  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 ignore warnings)
+  [255]
+  $ hg push ../other-new -f # use f because there is unstability
+  pushing to ../other-new
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files (+1 heads)
+  $ qlog -R ../other-new
+  5
+  - 95de7fc6918d
+  3
+  - 725c380fe99b
+  2
+  - 0d3f46688ccc
+  1
+  - 7c3bad9141dc
+  0
+  - 1f0dee641bb7
+  $ qlog -R ../other-new -r 'obsolete()'
+  2
+  - 0d3f46688ccc
+
+Pushing again does not advertise extinct changeset
+
+  $ hg push ../other-new
+  pushing to ../other-new
+  searching for changes
+  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 `getid 6` `getid 7`
+  $ hg pull -R ../other-new .
+  pulling from .
+  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)
+  $ qlog -R ../other-new
+  6
+  - 909a0fb57e5d
+  3
+  - 725c380fe99b
+  2
+  - 0d3f46688ccc
+  1
+  - 7c3bad9141dc
+  0
+  - 1f0dee641bb7
+
+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 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-old
+  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
+  updating to branch default
+  4 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+  $ qlog -R ../cloned --hidden
+  7
+  - 909a0fb57e5d
+  6
+  - 95de7fc6918d
+  5
+  - a7a6f2b5d8a5
+  4
+  - 725c380fe99b
+  3
+  - 0d3f46688ccc
+  2
+  - 4538525df7e2
+  1
+  - 7c3bad9141dc
+  0
+  - 1f0dee641bb7
+
+Test rollback support
+
+  $ hg up .^ -q # 3
+  Working directory parent is obsolete
+  $ mkcommit "obsol_d''"
+  created new head
+  1 new unstables changesets
+  $ hg debugobsolete `getid 7` `getid 8`
+  $ cd ../other-new
+  $ hg up -q 3
+  $ hg pull ../local/
+  pulling from ../local/
+  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)
+
+  $ hg up -q 7 # to check rollback update behavior
+  $ qlog
+  7
+  - 159dfc9fa5d3
+  3
+  - 725c380fe99b
+  2
+  - 0d3f46688ccc
+  1
+  - 7c3bad9141dc
+  0
+  - 1f0dee641bb7
+  $ hg rollback
+  repository tip rolled back to revision 6 (undo pull)
+  working directory now based on revision 3
+  $ hg summary
+  parent: 3:725c380fe99b 
+   add obsol_c'
+  branch: default
+  commit: 1 deleted, 2 unknown (clean)
+  update: 4 new changesets, 4 branch heads (merge)
+  $ qlog
+  6
+  - 909a0fb57e5d
+  3
+  - 725c380fe99b
+  2
+  - 0d3f46688ccc
+  1
+  - 7c3bad9141dc
+  0
+  - 1f0dee641bb7
+  $ cd ../local
+
+obsolete public changeset
+
+# move draft boundary from 0 to 1
+  $ sed -e 's/1f0dee641bb7258c56bd60e93edfa2405381c41e/7c3bad9141dcb46ff89abf5f61856facd56e476c/' -i'.back' .hg/store/phaseroots
+
+  $ hg up null
+  0 files updated, 0 files merged, 4 files removed, 0 files unresolved
+  $ mkcommit toto # 9
+  created new head
+  $ hg id -n
+  9
+  $ 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
+allow to just kill changeset
+
+  $ qlog
+  9
+  - 83b5778897ad
+  8
+  - 159dfc9fa5d3
+  4
+  - 725c380fe99b
+  3
+  - 0d3f46688ccc
+  1
+  - 7c3bad9141dc
+  0
+  - 1f0dee641bb7
+
+  $ hg debugobsolete `getid 9` #kill
+  $ hg up null -q # to be not based on 9 anymore
+  $ qlog
+  8
+  - 159dfc9fa5d3
+  4
+  - 725c380fe99b
+  3
+  - 0d3f46688ccc
+  1
+  - 7c3bad9141dc
+  0
+  - 1f0dee641bb7
+
+check rebase compat
+
+  $ hg glog -r 'not extinct()'  --template='{rev} - {node|short}\n'
+  o  8 - 159dfc9fa5d3
+  |
+  | o  4 - 725c380fe99b
+  | |
+  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'`
+  x  9 - 83b5778897ad
+  
+  o  8 - 159dfc9fa5d3
+  |
+  | x  7 - 909a0fb57e5d
+  |/
+  | x  6 - 95de7fc6918d
+  |/
+  | x  5 - a7a6f2b5d8a5
+  |/
+  | o  4 - 725c380fe99b
+  | |
+  x |  3 - 0d3f46688ccc
+  |/
+  | x  2 - 4538525df7e2
+  |/
+  o  1 - 7c3bad9141dc
+  |
+  o  0 - 1f0dee641bb7
+  
+
+should not rebase extinct changeset
+
+  $ 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
+  |
+  o  10 - 2033b4e49474
+  |
+  o  4 - 725c380fe99b
+  |
+  o  1 - 7c3bad9141dc
+  |
+  o  0 - 1f0dee641bb7
+  
+
+Does not complain about new head if you obsolete the old one
+
+  $ hg push ../other-new --traceback
+  pushing to ../other-new
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 1 changes to 1 files
+  $ hg up -q 10
+  $ mkcommit "obsol_d'''"
+  created new head
+  $ hg debugobsolete `getid 11` `getid 12`
+  $ hg push ../other-new --traceback
+  pushing to ../other-new
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files (+1 heads)
+  $ cd ..
+
+check latecomer detection
+(make an obsolete changeset public)
+
+  $ cd local
+  $ hg phase --public 11
+  1 new latecomers changesets
+  $ hg --config extensions.graphlog=glog glog --template='{rev} - ({phase}) {node|short} {desc}\n'
+  @  12 - (draft) 6db5e282cb91 add obsol_d'''
+  |
+  | o  11 - (public) 9468a5f5d8b2 add obsol_d''
+  |/
+  o  10 - (public) 2033b4e49474 add obsol_c
+  |
+  o  4 - (public) 725c380fe99b add obsol_c'
+  |
+  o  1 - (public) 7c3bad9141dc add b
+  |
+  o  0 - (public) 1f0dee641bb7 add a
+  
+  $ hg log -r 'latecomer()'
+  changeset:   12:6db5e282cb91
+  tag:         tip
+  parent:      10:2033b4e49474
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     add obsol_d'''
+  
+  $ hg push ../other-new/
+  pushing to ../other-new/
+  searching for changes
+  abort: push includes a latecomer changeset: 6db5e282cb91!
+  (use 'hg stabilize' to get a stable history or --force to ignore warnings)
+  [255]
+
+Check hg commit --amend compat
+
+  $ hg up 'desc(obsol_c)'
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ mkcommit f
+  created new head
+  $ echo 42 >> f
+  $ hg commit --amend --traceback
+  saved backup bundle to $TESTTMP/local/.hg/strip-backup/0b1b6dd009c0-amend-backup.hg
+  $ hg glog
+  @  changeset:   13:3734a65252e6
+  |  tag:         tip
+  |  parent:      10:2033b4e49474
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     add f
+  |
+  | o  changeset:   12:6db5e282cb91
+  |/   parent:      10:2033b4e49474
+  |    user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    summary:     add obsol_d'''
+  |
+  | o  changeset:   11:9468a5f5d8b2
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    summary:     add obsol_d''
+  |
+  o  changeset:   10:2033b4e49474
+  |  parent:      4:725c380fe99b
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     add obsol_c
+  |
+  o  changeset:   4:725c380fe99b
+  |  parent:      1:7c3bad9141dc
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     add obsol_c'
+  |
+  o  changeset:   1:7c3bad9141dc
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     add b
+  |
+  o  changeset:   0:1f0dee641bb7
+     user:        test
+     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
+  909a0fb57e5d 159dfc9fa5d3
+  9468a5f5d8b2 6db5e282cb91
+  95de7fc6918d 909a0fb57e5d
+  a7a6f2b5d8a5 95de7fc6918d
+
+Check conflict detection
+
+  $ hg up 9468a5f5d8b2 #  add obsol_d''
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ mkcommit "obsolet_conflicting_d"
+  $ hg summary
+  parent: 14:50f11e5e3a63 tip
+   add obsolet_conflicting_d
+  branch: default
+  commit: (clean)
+  update: 9 new changesets, 9 branch heads (merge)
+  $ hg debugobsolete `getid a7a6f2b5d8a5` `getid 50f11e5e3a63`
+  $ hg log -r 'conflicting()'
+  changeset:   14:50f11e5e3a63
+  tag:         tip
+  parent:      11:9468a5f5d8b2
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     add obsolet_conflicting_d
+  
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-oldconvert.t	Mon Jul 30 14:45:42 2012 +0200
@@ -0,0 +1,114 @@
+  $ cat >> $HGRCPATH <<EOF
+  > [web]
+  > push_ssl = false
+  > allow_push = *
+  > [phases]
+  > publish=False
+  > [alias]
+  > odiff=diff --rev 'limit(obsparents(.),1)' --rev .
+  > [extensions]
+  > hgext.graphlog=
+  > EOF
+  $ mkcommit() {
+  >    echo "$1" > "$1"
+  >    hg add "$1"
+  >    hg ci -m "add $1"
+  > }
+
+create commit
+
+  $ hg init repo
+  $ cd repo
+  $ mkcommit a
+  $ mkcommit b
+  $ hg up -q 0
+  $ mkcommit c
+  created new head
+
+forge old style relation files
+
+  $ hg log -r 2 --template='{node} ' > .hg/obsolete-relations
+  $ hg log -r 1 --template='{node}' >> .hg/obsolete-relations
+
+enable the extensions
+
+  $ echo "obsolete=$(echo $(dirname $TESTDIR))/hgext/obsolete.py" >> $HGRCPATH
+
+  $ hg glog
+  abort: old format of obsolete marker detected!
+  run `hg debugconvertobsolete` once.
+  [255]
+  $ hg debugconvertobsolete --traceback
+  1 obsolete marker converted
+  $ hg glog
+  @  changeset:   2:d67cd0334eee
+  |  tag:         tip
+  |  parent:      0:1f0dee641bb7
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     add c
+  |
+  o  changeset:   0:1f0dee641bb7
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     add a
+  
+  $ hg debugsuccessors
+  7c3bad9141dc d67cd0334eee
+  $ hg debugconvertobsolete
+  nothing to do
+  0 obsolete marker converted
+
+Convert json
+
+  $ cat > .hg/store/obsoletemarkers << EOF
+  > [
+  >     {
+  >         "reason": "import from older format.", 
+  >         "subjects": [
+  >             "3218406b50ed13480765e7c260669620f37fba6e"
+  >         ], 
+  >         "user": "Pierre-Yves David <pierre-yves.david@ens-lyon.org>", 
+  >         "date": [
+  >             1336503323.9768269, 
+  >             -7200
+  >         ], 
+  >         "object": "3e03d82708d4da97a92158558dd13386d8f09ad5", 
+  >         "id": "4743f676eaf3923cb98c921ee06b2e91052c365b"
+  >     }, 
+  >     {
+  >         "reason": "import from older format.", 
+  >         "user": "Pierre-Yves David <pierre-yves.david@logilab.fr>", 
+  >         "date": [
+  >             1336557472.7875929, 
+  >             -7200
+  >         ], 
+  >         "object": "5c722672795c3a2cb94d0cc9a821c394c1475f87", 
+  >         "id": "1fd90a84b7225d2e3062b7e1b3100aa2e060fc72"
+  >     }, 
+  >     {
+  >         "reason": "import from older format.", 
+  >         "subjects": [
+  >             "0000000000000000000000000000000000000000"
+  >         ], 
+  >         "user": "Pierre-Yves David <pierre-yves.david@logilab.fr>", 
+  >         "date": [
+  >             1336557472.784307, 
+  >             -7200
+  >         ], 
+  >         "object": "2c3784e102bb34ccc93862af5bd6d609ee30c577", 
+  >         "id": "7d940c5ee1f886c8a6c0d805b43e522cb3ef7a15"
+  >     }
+  > ]
+  > EOF
+  $ hg glog
+  abort: old format of obsolete marker detected!
+  run `hg debugconvertobsolete` once.
+  [255]
+  $ hg debugconvertobsolete --traceback
+  3 obsolete marker converted
+  $ hg debugsuccessors
+  2c3784e102bb
+  3e03d82708d4 3218406b50ed
+  5c722672795c
+  7c3bad9141dc d67cd0334eee
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-qsync.t	Mon Jul 30 14:45:42 2012 +0200
@@ -0,0 +1,240 @@
+  $ cat >> $HGRCPATH <<EOF
+  > [defaults]
+  > amend=-d "0 0"
+  > [web]
+  > push_ssl = false
+  > allow_push = *
+  > [phases]
+  > publish = False
+  > [alias]
+  > qlog = log --template='{rev} - {node|short} {desc} ({phase})\n'
+  > mqlog = log --mq --template='{rev} - {desc}\n'
+  > [diff]
+  > git = 1
+  > unified = 0
+  > [extensions]
+  > hgext.rebase=
+  > 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() {
+  >    echo "$1" > "$1"
+  >    hg add "$1"
+  >    hg ci -m "add $1"
+  > }
+
+basic sync
+
+  $ hg init local
+  $ cd local
+  $ hg qinit -c
+  $ hg qci -m "initial commit"
+  $ mkcommit a
+  $ mkcommit b
+  $ hg qlog
+  1 - 7c3bad9141dc add b (draft)
+  0 - 1f0dee641bb7 add a (draft)
+  $ hg qsync -a
+  $ hg mqlog
+  2 - qsubmit commit
+  
+  * DEFAULT-add_a.diff ready for review
+  * DEFAULT-add_b.diff ready for review
+  1 - qsubmit init
+  0 - initial commit
+
+basic sync II
+
+  $ hg init local
+  $ cd local
+  $ hg qinit -c
+  $ hg qci -m "initial commit"
+  $ mkcommit a
+  $ mkcommit b
+  $ hg qlog
+  1 - 7c3bad9141dc add b (draft)
+  0 - 1f0dee641bb7 add a (draft)
+  $ hg qsync -a
+  $ hg mqlog
+  2 - qsubmit commit
+  
+  * DEFAULT-add_a.diff ready for review
+  * DEFAULT-add_b.diff ready for review
+  1 - qsubmit init
+  0 - initial commit
+
+  $ echo "b" >> b
+  $ hg amend
+  $ hg qsync -a
+  $ hg mqlog
+  3 - qsubmit commit
+  
+  * DEFAULT-add_b.diff ready for review
+  2 - qsubmit commit
+  
+  * DEFAULT-add_a.diff ready for review
+  * DEFAULT-add_b.diff ready for review
+  1 - qsubmit init
+  0 - initial commit
+
+  $ hg up -r 0
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ echo "a" >> a
+  $ hg amend
+  1 new unstables changesets
+  $ hg graft -O 3
+  grafting revision 3
+  $ hg qsync -a
+  $ hg mqlog
+  4 - qsubmit commit
+  
+  * DEFAULT-add_a.diff ready for review
+  * DEFAULT-add_b.diff ready for review
+  3 - qsubmit commit
+  
+  * DEFAULT-add_b.diff ready for review
+  2 - qsubmit commit
+  
+  * DEFAULT-add_a.diff ready for review
+  * DEFAULT-add_b.diff ready for review
+  1 - qsubmit init
+  0 - initial commit
+
+sync with published changeset
+
+  $ hg init local
+  $ cd local
+  $ hg qinit -c
+  $ hg qci -m "initial commit"
+  $ mkcommit a
+  $ mkcommit b
+  $ hg qlog
+  1 - 7c3bad9141dc add b (draft)
+  0 - 1f0dee641bb7 add a (draft)
+  $ hg qsync -a
+  $ hg mqlog
+  2 - qsubmit commit
+  
+  * DEFAULT-add_a.diff ready for review
+  * DEFAULT-add_b.diff ready for review
+  1 - qsubmit init
+  0 - initial commit
+
+  $ hg phase -p 0
+  $ hg qsync -a
+  $ hg mqlog
+  3 - qsubmit commit
+  
+  * applied DEFAULT-add_a.diff
+  2 - qsubmit commit
+  
+  * DEFAULT-add_a.diff ready for review
+  * DEFAULT-add_b.diff ready for review
+  1 - qsubmit init
+  0 - initial commit
+
+  $ mkcommit c
+  $ mkcommit d
+  $ hg qsync -a
+  $ hg mqlog
+  4 - qsubmit commit
+  
+  * DEFAULT-add_c.diff ready for review
+  * DEFAULT-add_d.diff ready for review
+  3 - qsubmit commit
+  
+  * applied DEFAULT-add_a.diff
+  2 - qsubmit commit
+  
+  * DEFAULT-add_a.diff ready for review
+  * DEFAULT-add_b.diff ready for review
+  1 - qsubmit init
+  0 - initial commit
+
+  $ cd ..
+  $ hg qclone -U local local2
+  $ cd local2
+  $ hg qlog
+  3 - 47d2a3944de8 add d (draft)
+  2 - 4538525df7e2 add c (draft)
+  1 - 7c3bad9141dc add b (draft)
+  0 - 1f0dee641bb7 add a (public)
+  $ hg strip -n 1 --no-backup
+  $ hg up
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg up --mq 4
+  6 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg qseries
+  DEFAULT-add_b.diff
+  DEFAULT-add_c.diff
+  DEFAULT-add_d.diff
+  $ hg qpush
+  applying DEFAULT-add_b.diff
+  now at: DEFAULT-add_b.diff
+  $ hg qfinish -a
+  $ hg phase -p .
+  $ hg qci -m "applied DEFAULT-add_b.diff"
+  $ cd ../local
+  $ hg pull ../local2
+  pulling from ../local2
+  searching for changes
+  no changes found
+  $ hg pull --mq ../local2/.hg/patches
+  pulling from ../local2/.hg/patches
+  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 qlog
+  3 - 47d2a3944de8 add d (draft)
+  2 - 4538525df7e2 add c (draft)
+  1 - 7c3bad9141dc add b (public)
+  0 - 1f0dee641bb7 add a (public)
+  $ hg mqlog -l 1
+  5 - applied DEFAULT-add_b.diff
+  $ hg status --mq --rev tip:-2
+  M series
+  A DEFAULT-add_b.diff
+  $ hg qsync -a
+  $ hg status --mq --rev tip:-2
+  M qsubmitdata
+  $ hg mqlog -l 1
+  6 - qsubmit commit
+  
+  * applied DEFAULT-add_b.diff
+  $ hg qsync -a
+  abort: Nothing changed
+  [255]
+
+mixed sync
+
+  $ hg init local
+  $ cd local
+  $ hg qinit -c
+  $ mkcommit a
+  $ mkcommit b
+  $ hg qlog
+  1 - 7c3bad9141dc add b (draft)
+  0 - 1f0dee641bb7 add a (draft)
+  $ hg qsync -a
+  $ hg mqlog
+  1 - qsubmit commit
+  
+  * DEFAULT-add_a.diff ready for review
+  * DEFAULT-add_b.diff ready for review
+  0 - qsubmit init
+  $ hg phase -p 0
+  $ echo "b" >> b
+  $ hg amend
+  $ hg qsync -a
+  $ hg mqlog -l 1
+  2 - qsubmit commit
+  
+  * applied DEFAULT-add_a.diff
+  * DEFAULT-add_b.diff ready for review
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-stabilize-order.t	Mon Jul 30 14:45:42 2012 +0200
@@ -0,0 +1,171 @@
+  $ cat >> $HGRCPATH <<EOF
+  > [defaults]
+  > amend=-d "0 0"
+  > [extensions]
+  > 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' "$@"
+  > }
+
+  $ hg init repo
+  $ cd repo
+  $ echo root > root
+  $ hg ci -Am addroot
+  adding root
+  $ echo a > a
+  $ hg ci -Am adda
+  adding a
+  $ echo b > b
+  $ hg ci -Am addb
+  adding b
+  $ echo c > c
+  $ hg ci -Am addc
+  adding c
+  $ glog
+  @  3:7a7552255fb5@default(draft) addc
+  |
+  o  2:ef23d6ef94d6@default(draft) addb
+  |
+  o  1:93418d2c0979@default(draft) adda
+  |
+  o  0:c471ef929e6a@default(draft) addroot
+  
+  $ hg gdown
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  [2] addb
+  $ echo b >> b
+  $ hg amend
+  1 new unstables 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
+  $ glog
+  @  7:f5ff10856e5a@default(draft) adda
+  |
+  | o  5:ab8cbb6d87ff@default(draft) addb
+  | |
+  | | o  3:7a7552255fb5@default(draft) addc
+  | | |
+  | | x  2:ef23d6ef94d6@default(draft) addb
+  | |/
+  | x  1:93418d2c0979@default(draft) adda
+  |/
+  o  0:c471ef929e6a@default(draft) addroot
+  
+
+Test stabilizing a predecessor child
+
+  $ hg stabilize -v
+  move:[5] addb
+  atop:[7] adda
+  hg rebase -Dr ab8cbb6d87ff -d f5ff10856e5a
+  resolving manifests
+  getting b
+  b
+  $ glog
+  @  8:6bf44048e43f@default(draft) addb
+  |
+  o  7:f5ff10856e5a@default(draft) adda
+  |
+  | o  3:7a7552255fb5@default(draft) addc
+  | |
+  | x  2:ef23d6ef94d6@default(draft) addb
+  | |
+  | x  1:93418d2c0979@default(draft) adda
+  |/
+  o  0:c471ef929e6a@default(draft) addroot
+  
+
+Test stabilizing a descendant predecessors child
+
+  $ hg up 7
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ hg debugsuccessors > successors.old
+  $ hg stabilize -v
+  move:[3] addc
+  atop:[8] addb
+  hg rebase -Dr 7a7552255fb5 -d 6bf44048e43f
+  resolving manifests
+  getting b
+  resolving manifests
+  getting c
+  c
+  $ hg debugsuccessors > 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
+  [1]
+  $ glog
+  @  9:5e819fbb0d27@default(draft) addc
+  |
+  o  8:6bf44048e43f@default(draft) addb
+  |
+  o  7:f5ff10856e5a@default(draft) adda
+  |
+  o  0:c471ef929e6a@default(draft) addroot
+  
+  $ hg stabilize -v
+  no unstable changeset
+  [1]
+
+Test behaviour with --any
+
+  $ hg up 8
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ echo b >> b
+  $ hg amend
+  1 new unstables changesets
+  $ glog
+  @  11:4e7cec6b4afe@default(draft) addb
+  |
+  | o  9:5e819fbb0d27@default(draft) addc
+  | |
+  | x  8:6bf44048e43f@default(draft) addb
+  |/
+  o  7:f5ff10856e5a@default(draft) adda
+  |
+  o  0:c471ef929e6a@default(draft) addroot
+  
+  $ 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 ?)
+  [2]
+  $ hg stabilize --any -v
+  move:[9] addc
+  atop:[11] addb
+  hg rebase -Dr 5e819fbb0d27 -d 4e7cec6b4afe
+  resolving manifests
+  removing c
+  getting b
+  resolving manifests
+  getting c
+  c
+  $ glog
+  @  12:24f95816bb21@default(draft) addc
+  |
+  o  11:4e7cec6b4afe@default(draft) addb
+  |
+  o  7:f5ff10856e5a@default(draft) adda
+  |
+  o  0:c471ef929e6a@default(draft) addroot
+  
+  $ hg stabilize --any -v
+  no unstable changeset
+  [1]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-stabilize-result.t	Mon Jul 30 14:45:42 2012 +0200
@@ -0,0 +1,51 @@
+  $ cat >> $HGRCPATH <<EOF
+  > [defaults]
+  > amend=-d "0 0"
+  > [extensions]
+  > 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}) bk:[{bookmarks}] {desc|firstline}\n' "$@"
+  > }
+
+Test stabilize removing the changeset being stabilized
+
+  $ hg init empty
+  $ cd empty
+  $ echo a > a
+  $ hg ci -Am adda a
+  $ echo b > b
+  $ hg ci -Am addb b
+  $ echo a >> a
+  $ hg ci -m changea
+  $ hg bookmark changea
+  $ hg up 1
+  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
+  move:[2] changea
+  atop:[4] changea
+  hg rebase -Dr cce2c55b8965 -d 1447e1c4828d
+  resolving manifests
+  $ glog --hidden
+  @  4:1447e1c4828d@default(draft) bk:[changea] changea
+  |
+  | x  3:41ad4fe8c795@default(draft) bk:[] amends 102a90ea7b4a3361e4082ed620918c261189a36a
+  | |
+  | | x  2:cce2c55b8965@default(draft) bk:[] changea
+  | |/
+  | x  1:102a90ea7b4a@default(draft) bk:[] addb
+  |/
+  o  0:07f494440405@default(draft) bk:[] adda
+  
+  $ hg debugsuccessors
+  102a90ea7b4a 1447e1c4828d
+  41ad4fe8c795 1447e1c4828d
+  cce2c55b8965 000000000000
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-tutorial.t	Mon Jul 30 14:45:42 2012 +0200
@@ -0,0 +1,1 @@
+../docs/tutorials/tutorial.t
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-uncommit.t	Mon Jul 30 14:45:42 2012 +0200
@@ -0,0 +1,336 @@
+  $ cat >> $HGRCPATH <<EOF
+  > [extensions]
+  > 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}({obsolete}/{phase}) {desc|firstline}\n' "$@"
+  > }
+
+  $ hg init repo
+  $ cd repo
+
+Cannot uncommit null changeset
+
+  $ hg uncommit
+  abort: cannot rewrite immutable changeset
+  [255]
+
+Cannot uncommit public changeset
+
+  $ echo a > a
+  $ hg ci -Am adda a
+  $ hg phase --public .
+  $ hg uncommit
+  abort: cannot rewrite immutable changeset
+  [255]
+  $ hg phase --force --draft .
+
+Cannot uncommit merge
+
+  $ hg up -q null
+  $ echo b > b
+  $ echo c > c
+  $ echo d > d
+  $ echo f > f
+  $ echo g > g
+  $ echo j > j
+  $ echo m > m
+  $ echo n > n
+  $ echo o > o
+  $ hg ci -Am addmore
+  adding b
+  adding c
+  adding d
+  adding f
+  adding g
+  adding j
+  adding m
+  adding n
+  adding o
+  created new head
+  $ hg merge
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ hg uncommit
+  abort: cannot uncommit while merging
+  [255]
+  $ hg ci -m merge
+  $ hg uncommit
+  abort: cannot uncommit merge changeset
+  [255]
+
+Prepare complicated changeset
+
+  $ hg branch bar
+  marked working directory as branch bar
+  (branches are permanent and global, did you want a bookmark?)
+  $ hg cp a aa
+  $ echo b >> b
+  $ hg rm c
+  $ echo d >> d
+  $ echo e > e
+  $ hg mv f ff
+  $ hg mv g h
+  $ echo j >> j
+  $ echo k > k
+  $ echo l > l
+  $ hg rm m
+  $ hg rm n
+  $ echo o >> o
+  $ hg ci -Am touncommit
+  adding e
+  adding k
+  adding l
+  $ hg st --copies --change .
+  M b
+  M d
+  M j
+  M o
+  A aa
+    a
+  A e
+  A ff
+    f
+  A h
+    g
+  A k
+  A l
+  R c
+  R f
+  R g
+  R m
+  R n
+  $ hg man -r .
+  a
+  aa
+  b
+  d
+  e
+  ff
+  h
+  j
+  k
+  l
+  o
+
+Add a couple of bookmarks
+
+  $ glog --hidden
+  @  3:5eb72dbe0cb4@bar(stable/draft) touncommit
+  |
+  o    2:f63b90038565@default(stable/draft) merge
+  |\
+  | o  1:f15c744d48e8@default(stable/draft) addmore
+  |
+  o  0:07f494440405@default(stable/draft) adda
+  
+  $ hg bookmark -r 2 unrelated
+  $ hg bookmark touncommit-bm
+  $ hg bookmark --inactive touncommit-bm-inactive
+  $ hg bookmarks
+   * touncommit-bm             3:5eb72dbe0cb4
+     touncommit-bm-inactive    3:5eb72dbe0cb4
+     unrelated                 2:f63b90038565
+
+Prepare complicated working directory
+
+  $ hg branch foo
+  marked working directory as branch foo
+  (branches are permanent and global, did you want a bookmark?)
+  $ hg mv ff f
+  $ hg mv h i
+  $ hg rm j
+  $ hg rm k
+  $ echo l >> l
+  $ echo m > m
+  $ echo o > o
+
+Test uncommit without argument, should be a no-op
+
+  $ hg uncommit
+  abort: nothing to uncommit
+  [255]
+  $ hg bookmarks
+   * touncommit-bm             3:5eb72dbe0cb4
+     touncommit-bm-inactive    3:5eb72dbe0cb4
+     unrelated                 2:f63b90038565
+
+Test no matches
+
+  $ hg uncommit --include nothere
+  abort: nothing to uncommit
+  [255]
+
+Enjoy uncommit
+
+  $ hg uncommit aa b c f ff g h j k l m o
+  $ hg branch
+  foo
+  $ hg st --copies
+  M b
+  A aa
+    a
+  A i
+    g
+  A l
+  R c
+  R g
+  R j
+  R m
+  $ cat aa
+  a
+  $ cat b
+  b
+  b
+  $ cat l
+  l
+  l
+  $ cat m
+  m
+  $ test -f c && echo 'error: c was removed!'
+  [1]
+  $ test -f j && echo 'error: j was removed!'
+  [1]
+  $ test -f k && echo 'error: k was removed!'
+  [1]
+  $ hg st --copies --change .
+  M d
+  A e
+  R n
+  $ hg man -r .
+  a
+  b
+  c
+  d
+  e
+  f
+  g
+  j
+  m
+  o
+  $ hg cat -r . d
+  d
+  d
+  $ hg cat -r . e
+  e
+  $ glog --hidden
+  @  4:e8db4aa611f6@bar(stable/draft) touncommit
+  |
+  | x  3:5eb72dbe0cb4@bar(extinct/draft) touncommit
+  |/
+  o    2:f63b90038565@default(stable/draft) merge
+  |\
+  | o  1:f15c744d48e8@default(stable/draft) addmore
+  |
+  o  0:07f494440405@default(stable/draft) adda
+  
+  $ hg bookmarks
+   * touncommit-bm             4:e8db4aa611f6
+     touncommit-bm-inactive    4:e8db4aa611f6
+     unrelated                 2:f63b90038565
+  $ hg debugsuccessors
+  5eb72dbe0cb4 e8db4aa611f6
+
+Test phase is preserved, no local changes
+
+  $ hg up -C 3
+  8 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  Working directory parent is obsolete
+  $ hg --config extensions.purge= purge
+  $ hg uncommit -I 'set:added() and e'
+  2 new conflictings changesets
+  $ hg st --copies
+  A e
+  $ hg st --copies --change .
+  M b
+  M d
+  M j
+  M o
+  A aa
+  A ff
+    f
+  A h
+    g
+  A k
+  A l
+  R c
+  R f
+  R g
+  R m
+  R n
+  $ glog --hidden
+  @  5:c706fe2c12f8@bar(stable/draft) touncommit
+  |
+  | o  4:e8db4aa611f6@bar(stable/draft) touncommit
+  |/
+  | x  3:5eb72dbe0cb4@bar(extinct/draft) touncommit
+  |/
+  o    2:f63b90038565@default(stable/draft) merge
+  |\
+  | o  1:f15c744d48e8@default(stable/draft) addmore
+  |
+  o  0:07f494440405@default(stable/draft) adda
+  
+  $ hg debugsuccessors
+  5eb72dbe0cb4 c706fe2c12f8
+  5eb72dbe0cb4 e8db4aa611f6
+
+Test --all
+
+  $ hg up -C 3
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  Working directory parent is obsolete
+  $ hg --config extensions.purge= purge
+  $ hg uncommit --all -X e
+  1 new conflictings changesets
+  $ hg st --copies
+  M b
+  M d
+  M j
+  M o
+  A aa
+    a
+  A ff
+    f
+  A h
+    g
+  A k
+  A l
+  R c
+  R f
+  R g
+  R m
+  R n
+  $ hg st --copies --change .
+  A e
+
+  $ hg debugsuccessors
+  5eb72dbe0cb4 c4cbebac3751
+  5eb72dbe0cb4 c706fe2c12f8
+  5eb72dbe0cb4 e8db4aa611f6
+
+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
+
+Test instability warning
+
+  $ hg ci -m touncommit
+  $ echo unrelated > unrelated
+  $ hg ci -Am addunrelated unrelated
+  $ hg gdown
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  [8] touncommit
+  $ hg uncommit aa
+  1 new unstables changesets