add nopushpublish extension.
--- /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