Commits

Pierre-Yves David  committed 5a0a01c Merge

merge stable into default

  • Participants
  • Parent commits 53d7e34, dc107ac

Comments (0)

Files changed (42)

 \.err$
 ^tests/easy_run.sh$
 ^build/
+^MANIFEST$
+^docs/tutorials/.*\.rst$
+\.ico$
 18a0d96ed559089edf90206c469f3f8c26681c64 0.7
 18a0d96ed559089edf90206c469f3f8c26681c64 0.7
 1b2757c1bd918509184f6c1d06b2329a847e31b0 0.7
+b18b000363550f02f413aed008f8e306318c608c 1.0.0
+ca5bb72d14aeb6e6053e3a53c064a2b7dc8010e5 1.0.1
+b1bdcb4506defef0e857e2710633f7686d8034a5 1.0.2
+5559e5a4b656978c592d364f242edc62369d7e84 1.0.2
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+	51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+		    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+			    NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+
+	    How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year  name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.
+recursive-include docs/figures *.svg
+include docs/figures/hgview-example.png
+include docs/*.rst
+include docs/*.py
+include docs/tutorials/*.t
+include docs/makefile
+include docs/static/*.svg
+include hgext/__init__.py
+include hgext/evolve.py
+include setup.py
+include README
+include COPYING
+include tests/*.t
+include tests/*.py
+exclude tests/test-oldconvert.t
+exclude tests/test-qsync.t
 PYTHON=python
 HG=`which hg`
+VERSION=$(shell python setup.py --version)
+
 
 help:
 	@echo 'Commonly used make targets:'
 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
 
+deb-prepare:
+	python setup.py sdist --dist-dir ..
+	mv -f ../hg-evolve-$(VERSION).tar.gz ../mercurial-evolve_$(VERSION).orig.tar.gz
+	tar xf ../mercurial-evolve_$(VERSION).orig.tar.gz
+	rm -rf ../mercurial-evolve_$(VERSION).orig
+	mv hg-evolve-$(VERSION) ../mercurial-evolve_$(VERSION).orig
+	cp -r debian/ ../mercurial-evolve_$(VERSION).orig/
+	@cd ../mercurial-evolve_$(VERSION).orig && echo 'debian build directory ready at' `pwd`
+
 .PHONY: tests all-version-tests
 Mutable History For Mercurial
 =============================
 
-:obsolete:
 
-    Introduce an ``obsolete`` concept that tracks new versions of rewritten
-    changesets.
+Extends Mercurial feature related to Changeset Evolution
 
-:evolve:
+This extension provides several commands to mutate history and deal with
+issues it may raise.
 
-    A collection of commands to rewrite the mutable part of the history.
+It also:
 
-
+    - enables the "Changeset Obsolescence" feature of mercurial,
+    - alters core commands and extensions that rewrite history to use
+      this feature,
+    - improves some aspect of the early implementation in 2.3
 
 **These extensions are experimental and are not meant for production.**
 
-You can quicky enable them using::
+You can quicky enable it by adding the line below to the extensions
+section of you hgrc::
 
-    ./enable.sh >> ~/.hgrc
+    evolve=PATH/TO/evolve.py
 
 But it's recommended to look at the doc in the first place.
 
 Changelog
 ==================
 
-1.0
+ --
+
+- fix troubles creation reporting from rebase
+
+1.0.2 --
+
+- fix hg fold bug
+- fix hg pull --rebase
+- fix detection of conflict with external tools
+- adapt to core movement (caches and --amend)
+
+1.0.1 -- 2012-08-31
+
+- documentation improvement
+- fix a performance bug with hgweb
+
+1.0 -- 2012-08-29
 
 - Align with Mercurial version 2.3 (drop 2.2 support).
 - stabilize handle killed parent

File debian/changelog

+mercurial-evolve (1.0.2-1) UNRELEASED; urgency=low
+
+  * New upstream Release
+
+ -- Pierre-Yves David <pierre-yves.david@logilab.fr>  Wed, 19 Sep 2012 17:38:47 +0200
+
+mercurial-evolve (1.0.1-1) UNRELEASED; urgency=low
+
+  * New bug fix release
+  * remove conflicting __init__.py
+
+ -- Pierre-Yves David <pierre-yves.david@logilab.fr>  Fri, 31 Aug 2012 11:31:03 +0200
+
+mercurial-evolve (1.0.0-1) UNRELEASED; urgency=low
+
+  * Initial release.
+
+ -- Julien Cristau <jcristau@debian.org>  Fri, 24 Aug 2012 16:46:30 +0200

File debian/compat

+8

File debian/control

+Source: mercurial-evolve
+Section: vcs
+Priority: optional
+Maintainer: Logilab <contact@logilab.fr>
+Uploaders:
+ Julien Cristau <julien.cristau@logilab.fr>,
+ Pierre-Yves David <pierre-yves.david@logilab.fr>,
+Standards-Version: 3.9.3
+Build-Depends:
+ mercurial (>= 2.3~),
+ mercurial-common (>= 2.3~),
+ python,
+ debhelper (>= 8),
+ python-sphinx (>= 1.0.8),
+ imagemagick,
+ librsvg2-bin,
+Python-Version: >= 2.6
+Homepage: https://bitbucket.org/marmoute/mutable-history
+
+Package: mercurial-evolve
+Architecture: all
+Depends:
+ ${python:Depends},
+ ${misc:Depends},
+ mercurial (>= 2.3~),
+Description: evolve extension for Mercurial
+ This package provides the experimental "evolve" extension for the Mercurial
+ DVCS.
+ .
+ This extension provides several commands to mutate history and deal with issues
+ it may raise.
+ .
+ It also:
+  - enables the "Changeset Obsolescence" feature of mercurial,
+  - alters core command and extension that rewrite history to use this feature,
+  - improves some aspects of the early implementation in Mercurial 2.3.
+ .
+ **These extensions are experimental and are not meant for production.**
+
+

File debian/copyright

+This software was downloaded from
+https://bitbucket.org/marmoute/mutable-history
+
+Copyright 2011 Peter Arrenbrecht <peter.arrenbrecht@gmail.com>
+               Logilab SA        <contact@logilab.fr>
+               Pierre-Yves David <pierre-yves.david@ens-lyon.org>
+	       Patrick Mezard <patrick@mezard.eu>
+
+
+This software may be used and distributed according to the terms of the GNU
+General Public License version 2 or any later version.
+
+On Debian systems, the complete text of the GNU General Public License version
+2 can be found in `/usr/share/common-licenses/GPL-2'.
+
+html

File debian/rules

+#!/usr/bin/make -f
+
+%:
+	dh $@ --with python2 --buildsystem=python_distutils
+
+build:
+	dh build --with python2 --buildsystem=python_distutils
+	$(MAKE) -C docs
+
+.PHONY: build
+
+override_dh_auto_test:
+	cd tests &&  python run-tests.py --with-hg=`which hg`
+
+override_dh_python2:
+	# avoid conflict with mercurial's own hgext/__init__.py
+	find debian -name __init__.py -delete
+	dh_python2

File docs/conf.py

 
 # The name of an image file (within the static path) to place at the top of
 # the sidebar.
-#html_logo = None
+html_logo = 'logo-evolve.svg'
 
 # 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
+html_favicon = 'logo-evolve.ico'
 
 # 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']
+html_static_path = ['static']
 
 # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
 # using the given strftime format.

File docs/evolve-collaboration.rst

+.. Copyright 2011 Pierre-Yves David <pierre-yves.david@ens-lyon.org>
+..                Logilab SA        <contact@logilab.fr>
 
 ------------------------------------------------
 Collaboration Using Evolve: A user story

File docs/evolve-faq.rst

+.. Copyright 2011 Pierre-Yves David <pierre-yves.david@ens-lyon.org>
+..                Logilab SA        <contact@logilab.fr>
 
 ---------------------------------------------------------------------
 Evolve How To
 `public` changesets.
 
 To understand what the result of amend will be I use the two following
-aliases  [#]_::
+aliases   [#]_::
 
     # diff what amend will look likes
     pdiff=diff --rev .^
           XXX add idank example
 
 
-.. [#] (added by enable.sh)
+.. [#] (defined by the evolve extension for you)
 
 
 
 .. note:: those command only exist for the convenience of getting qpush and qpop
           feeling back.
 
-Collapse changesets: ``amend``
+Collapse changesets: ``fold``
 ------------------------------------------------------------
 
-you can use amend -c to collapse multiple changeset in a single one.
+you can use ``hg fold`` to collapse multiple changesets in a single one.
 
 Getting changes out of a commit
 ------------------------------------------------------------
 .. warning:: Beware that rebasing obsolete changesets will result in
              conflicting versions of the changesets.
 
-Stabilize history: ``evolve``
+Resolve history troubles: ``evolve``
 ------------------------------------------------------------
 
 When you rewrite (amend) a changeset with children without rewriting
 View obsolete markers
 ------------------------------------------------------------
 
-hgview is the only viewer that support this feature. You need an experimental
-version available here:
+hgview_ is the only viewer that currently supports this feature. You
+need version 1.6.2
 
-    $ hg clone http://hg-dev.octopoid.net/hgwebdir.cgi/hgview/
+.. _hgview: http://www.logilab.org/project/hgview/
+
+.. image:: figures/hgview-example.png
+    :scale: 50%
+
 
 You can also use a debug command
 

File docs/evolve-good-practice.rst

+.. Copyright 2011 Pierre-Yves David <pierre-yves.david@ens-lyon.org>
+..                Logilab SA        <contact@logilab.fr>
+
 -----------------------------------------
 Good pratice for (early) user of evolve
 -----------------------------------------

File docs/figures/hgview-example.png

Added
New image

File docs/from-mq.rst

+.. Copyright 2011 Pierre-Yves David <pierre-yves.david@ens-lyon.org>
+..                Logilab SA        <contact@logilab.fr>
+
 -------------------------------------------
 From MQ To Evolve, The Refugee Book
 -------------------------------------------
 qseries                         ``log``
 qnew                            ``commit``
 qrefresh                        ``amend``
-qpop                            ``update`` or ``qdown``
+qpop                            ``update`` or ``gdown``
 qpush                           ``update`` or ``gup`` sometimes ``evolve``
 qrm                             ``prune``
-qfold                           ``amend -c`` (for now, ``collapse`` soon)
+qfold                           ``fold``
 qdiff                           ``odiff``
+qrecord                         ``/qrecord``
 
 qfinish                         --
 qimport                         --
 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'
+.. -c is very confusig
+..
+.. The amend command also has a -c switch which allows you to make an
+.. explicit amending commit before rewriting a changeset.::
+..
+..   $ hg record -m 'feature A'
+..   # oups, I forgot 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 prune <revset>
 
+hg qrm
+```````
+
+::
+
+  $ hg fold <first>::<last>
+
 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 fold first::last
 
 hg qdiff
 `````````
 
-``odiff`` is an alias for `hg diff -r .^` it works as qdiff, but outside mq.
+``pdiff`` is an alias for `hg diff -r .^` it works as qdiff, but outside mq.
 
 
 

File docs/index.rst

+.. Copyright 2011 Pierre-Yves David <pierre-yves.david@ens-lyon.org>
+..                Logilab SA        <contact@logilab.fr>
+
 ========================================
 Safe Mutable History
 ========================================
 
 * 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.
+.. warning:: The evolve extension and obsolete markers are at an experimental
+             stage. While using obsolete you willl likely be exposed to complex
+             implications of the **obsolete marker** concept. I do not recommend
+             non-power users to test this at this stage.
 
-            XXX make sure to read the XXX section before using it.
+             While numbered 1.0.0, the command line API of this version should
+             **not** be regarded as *stable*, command behavior, name and
+             options may change in future release or once integrated in
+             mercurial. It is still an immature extension, a lot of
+             features are still missing but there is no high risk of
+             repository corruption.
 
-            Production ready version should hide such details to normal user.
+             Production ready version should hide such details to normal user.
 
 The evolve extension require mercurial 2.3
 
    obs-concept
    obs-terms
    obs-implementation
-   obs-road-map
 
 
 Known limitation and bug

File docs/instability.rst

+.. Copyright 2011 Pierre-Yves David <pierre-yves.david@ens-lyon.org>
+..                Logilab SA        <contact@logilab.fr>
 
 -----------------------------------
 The instability Principle

File docs/makefile

 
-all: tutorial
+all: tutorial static/logo-evolve.ico
 	sphinx-build . ../html/
 
 tutorial:
 	python test2rst.py tutorials/
+
+static/logo-evolve.ico: static/logo-evolve.svg
+	convert -resize 36x36 static/logo-evolve.svg static/logo-evolve.ico
+
+

File docs/obs-concept.rst

+.. Copyright 2011 Pierre-Yves David <pierre-yves.david@ens-lyon.org>
+..                Logilab SA        <contact@logilab.fr>
+
 -----------------------------------------------------------
 Why Do We Need a New Concept
 -----------------------------------------------------------

File docs/obs-implementation.rst

+.. Copyright 2011 Pierre-Yves David <pierre-yves.david@ens-lyon.org>
+..                Logilab SA        <contact@logilab.fr>
 
 -----------------------------------------------------
 Implementation of Obsolete Marker
 -----------------------------------------------------
 
 
-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 ?
 Current status
 -----------------------------------------------------
 
-An experimental implementatione exists. What have been done so far.
+Obsolete marker are partialy in core.
 
+2.3:
 
-* 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)
-
+- storage over obsolete marker
+- exchange suing pushkey
+- extinct changeset are properly hidden
+- extinct changeset are excluded from exchange

File docs/obs-terms.rst

+.. Copyright 2011 Pierre-Yves David <pierre-yves.david@ens-lyon.org>
+..                Logilab SA        <contact@logilab.fr>
+
 -----------------------------------------------------------
 Terminology of the obsolete concept
 -----------------------------------------------------------
 **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*.
+          better distinction between *direct successors* and **any successors**.
 
 Possible changesets "type"
 ---------------------------------

File docs/qsync.rst

+.. Copyright 2011 Pierre-Yves David <pierre-yves.david@ens-lyon.org>
+..                Logilab SA        <contact@logilab.fr>
+
 ---------------------------------------------------------------------
 Qsync: Mercurial to MQ exporter
 ---------------------------------------------------------------------

File docs/static/logo-evolve.svg

Added
New image

File docs/test2rst.py

     if os.path.isdir(base):
         one_dir(base)
     else:
-        print one_file(base)
+        one_file(base)
 
 
 def one_dir(base):
     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
 

File docs/tutorials/tutorial.t

 
 for simplicity shake we get the bathroom change in line again
 
-  $ hg rebase -Dr 8d39a843582d -d a2fccc2e7b08
+  $ hg rebase -r 8d39a843582d -d a2fccc2e7b08
   merging shopping
   $ hg phase --draft .
   $ hg log -G
   $ hg stabilize --dry-run
   move:[15] animals
   atop:[14] bathroom stuff
-  hg rebase -Dr 9ac5d0e790a2 -d ffa278c50818
+  hg rebase -r 9ac5d0e790a2 -d ffa278c50818
 
 Let's do it
 
-  $ hg rebase -Dr 9ac5d0e790a2 -d ffa278c50818
+  $ hg rebase -r 9ac5d0e790a2 -d ffa278c50818
   merging shopping
 
 The old version of bathroom is hidden again.

File hgext/__init__.py

-#f00
+# Copyright 2011 Logilab SA <contact@logilab.fr>

File hgext/evolve.py

-# 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>
+#                Patrick Mezard <patrick@mezard.eu>
 #
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
 '''Extends Mercurial feature related to Changeset Evolution
 
-This extension Provide several command tommutate history and deal with issue it may raise.
+This extension provides several commands to mutate history and deal with
+issues it may raise.
 
 It also:
 
-    - enable the "Changeset Obsolescence" feature of mercurial,
-    - alter core command and extension that rewrite history to use this feature,
-    - improve some aspect of the early implementation in 2.3
+    - enables the "Changeset Obsolescence" feature of mercurial,
+    - alters core commands and extensions that rewrite history to use
+      this feature,
+    - improves some aspect of the early implementation in 2.3
 '''
 
 import random
 from mercurial import extensions
 from mercurial import hg
 from mercurial import localrepo
+from mercurial import lock as lockmod
 from mercurial import merge
 from mercurial import node
 from mercurial import phases
 from mercurial.commands import walkopts, commitopts, commitopts2
 from mercurial.node import nullid
 
+import mercurial.hgweb.hgweb_mod
+
 
 
 # This extension contains the following code
 ### Obsolescence Caching Logic                                    ###
 #####################################################################
 
+# IN CORE fb72eec7efd8
+
 # Obsolescence related logic can be very slow if we don't have efficient cache.
 #
 # This section implements a cache mechanism that did not make it into core for
-# time reason. It store meaningful set of revision related to obsolescence
-# (obsolete, unstabletble ...
+# time reason. It stores meaningful set of revisions related to obsolescence
+# (obsolete, unstable, etc.)
 #
 # Here is:
 #
-# - Computation of meaningful set,
+# - Computation of meaningful sets
 # - Cache access logic,
 # - Cache invalidation logic,
 # - revset and ctx using this cache.
 #:   function take a single "repo" argument.
 #:
 #: Use the `cachefor` decorator to register new cache function
-cachefuncs = {}
-def cachefor(name):
-    """Decorator to register a function as computing the cache for a set"""
-    def decorator(func):
-        assert name not in cachefuncs
-        cachefuncs[name] = func
-        return func
-    return decorator
+try:
+    cachefuncs = obsolete.cachefuncs
+    cachefor = obsolete.cachefor
+    getobscache = obsolete.getobscache
+    clearobscaches = obsolete.clearobscaches
+except AttributeError:
+    cachefuncs = {}
 
-@cachefor('obsolete')
-def _computeobsoleteset(repo):
-    """the set of obsolete revisions"""
-    obs = set()
-    nm = repo.changelog.nodemap
-    for prec in repo.obsstore.precursors:
-        rev = nm.get(prec)
-        if rev is not None:
-            obs.add(rev)
-    return set(repo.revs('%ld - public()', obs))
+    def cachefor(name):
+        """Decorator to register a function as computing the cache for a set"""
+        def decorator(func):
+            assert name not in cachefuncs
+            cachefuncs[name] = func
+            return func
+        return decorator
 
-@cachefor('unstable')
-def _computeunstableset(repo):
-    """the set of non obsolete revisions with obsolete parents"""
-    return set(repo.revs('(obsolete()::) - obsolete()'))
+    @cachefor('obsolete')
+    def _computeobsoleteset(repo):
+        """the set of obsolete revisions"""
+        obs = set()
+        nm = repo.changelog.nodemap
+        for prec in repo.obsstore.precursors:
+            rev = nm.get(prec)
+            if rev is not None:
+                obs.add(rev)
+        return set(repo.revs('%ld - public()', obs))
 
-@cachefor('suspended')
-def _computesuspendedset(repo):
-    """the set of obsolete parents with non obsolete descendants"""
-    return set(repo.revs('obsolete() and obsolete()::unstable()'))
+    @cachefor('unstable')
+    def _computeunstableset(repo):
+        """the set of non obsolete revisions with obsolete parents"""
+        return set(repo.revs('(obsolete()::) - obsolete()'))
 
-@cachefor('extinct')
-def _computeextinctset(repo):
-    """the set of obsolete parents without non obsolete descendants"""
-    return set(repo.revs('obsolete() - obsolete()::unstable()'))
+    @cachefor('suspended')
+    def _computesuspendedset(repo):
+        """the set of obsolete parents with non obsolete descendants"""
+        return set(repo.revs('obsolete() and obsolete()::unstable()'))
 
-@eh.wrapfunction(obsolete.obsstore, '__init__')
-def _initobsstorecache(orig, obsstore, *args, **kwargs):
-    """add a cache attribute to obsstore"""
-    obsstore.caches = {}
-    return orig(obsstore, *args, **kwargs)
+    @cachefor('extinct')
+    def _computeextinctset(repo):
+        """the set of obsolete parents without non obsolete descendants"""
+        return set(repo.revs('obsolete() - obsolete()::unstable()'))
+
+    @eh.wrapfunction(obsolete.obsstore, '__init__')
+    def _initobsstorecache(orig, obsstore, *args, **kwargs):
+        """add a cache attribute to obsstore"""
+        obsstore.caches = {}
+        return orig(obsstore, *args, **kwargs)
 
 ### Cache access
 
-def getobscache(repo, name):
-    """Return the set of revision that belong to the <name> set
+    def getobscache(repo, name):
+        """Return the set of revision that belong to the <name> set
 
-    Such access may compute the set and cache it for future use"""
-    if not repo.obsstore:
-        return ()
-    if name not in repo.obsstore.caches:
-        repo.obsstore.caches[name] = cachefuncs[name](repo)
-    return repo.obsstore.caches[name]
+        Such access may compute the set and cache it for future use"""
+        if not repo.obsstore:
+            return ()
+        if name not in repo.obsstore.caches:
+            repo.obsstore.caches[name] = cachefuncs[name](repo)
+        return repo.obsstore.caches[name]
 
 ### Cache clean up
 #
 # - strip is used a repo
 
 
-def clearobscaches(repo):
-    """Remove all obsolescence related cache from a repo
+    def clearobscaches(repo):
+        """Remove all obsolescence related cache from a repo
 
-    This remove all cache in obsstore is the obsstore already exist on the
-    repo.
+        This remove all cache in obsstore is the obsstore already exist on the
+        repo.
 
-    (We could be smarter here)"""
-    if 'obsstore' in repo._filecache:
-        repo.obsstore.caches.clear()
+        (We could be smarter here)"""
+        if 'obsstore' in repo._filecache:
+            repo.obsstore.caches.clear()
 
-@eh.wrapfunction(localrepo.localrepository, 'addchangegroup')  # new changeset
-@eh.wrapfunction(phases, 'retractboundary')  # phase movement
-@eh.wrapfunction(phases, 'advanceboundary')  # phase movement
-@eh.wrapfunction(localrepo.localrepository, 'destroyed')  # strip
-def wrapclearcache(orig, repo, *args, **kwargs):
-    try:
-        return orig(repo, *args, **kwargs)
-    finally:
-        # we are a bit wide here
-        # we could restrict to:
-        # advanceboundary + phase==public
-        # retractboundary + phase==draft
-        clearobscaches(repo)
+    @eh.wrapfunction(localrepo.localrepository, 'addchangegroup')  # new changeset
+    @eh.wrapfunction(phases, 'retractboundary')  # phase movement
+    @eh.wrapfunction(phases, 'advanceboundary')  # phase movement
+    @eh.wrapfunction(localrepo.localrepository, 'destroyed')  # strip
+    def wrapclearcache(orig, repo, *args, **kwargs):
+        try:
+            return orig(repo, *args, **kwargs)
+        finally:
+            # we are a bit wide here
+            # we could restrict to:
+            # advanceboundary + phase==public
+            # retractboundary + phase==draft
+            clearobscaches(repo)
 
-@eh.wrapfunction(obsolete.obsstore, 'add')  # new marker
-def clearonadd(orig, obsstore, *args, **kwargs):
-    try:
-        return orig(obsstore, *args, **kwargs)
-    finally:
-        obsstore.caches.clear()
+    @eh.wrapfunction(obsolete.obsstore, 'add')  # new marker
+    def clearonadd(orig, obsstore, *args, **kwargs):
+        try:
+            return orig(obsstore, *args, **kwargs)
+        finally:
+            obsstore.caches.clear()
 
 ### Use the case
 # Function in core that could benefic from the cache are overwritten by cache using version
 
 # changectx method
 
-@eh.addattr(context.changectx, 'unstable')
-def unstable(ctx):
-    """is the changeset unstable (have obsolete ancestor)"""
-    if ctx.node() is None:
-        return False
-    return ctx.rev() in getobscache(ctx._repo, 'unstable')
+    @eh.addattr(context.changectx, 'unstable')
+    def unstable(ctx):
+        """is the changeset unstable (have obsolete ancestor)"""
+        if ctx.node() is None:
+            return False
+        return ctx.rev() in getobscache(ctx._repo, 'unstable')
 
 
-@eh.addattr(context.changectx, 'extinct')
-def extinct(ctx):
-    """is the changeset extinct by other"""
-    if ctx.node() is None:
-        return False
-    return ctx.rev() in getobscache(ctx._repo, 'extinct')
+    @eh.addattr(context.changectx, 'extinct')
+    def extinct(ctx):
+        """is the changeset extinct by other"""
+        if ctx.node() is None:
+            return False
+        return ctx.rev() in getobscache(ctx._repo, 'extinct')
 
 # revset
 
-@eh.revset('obsolete')
-def revsetobsolete(repo, subset, x):
-    """``obsolete()``
-    Changeset is obsolete.
-    """
-    args = revset.getargs(x, 0, 0, 'obsolete takes no argument')
-    obsoletes = getobscache(repo, 'obsolete')
-    return [r for r in subset if r in obsoletes]
+    @eh.revset('obsolete')
+    def revsetobsolete(repo, subset, x):
+        """``obsolete()``
+        Changeset is obsolete.
+        """
+        args = revset.getargs(x, 0, 0, 'obsolete takes no argument')
+        obsoletes = getobscache(repo, 'obsolete')
+        return [r for r in subset if r in obsoletes]
 
-@eh.revset('unstable')
-def revsetunstable(repo, subset, x):
-    """``unstable()``
-    Unstable changesets are non-obsolete with obsolete ancestors.
-    """
-    args = revset.getargs(x, 0, 0, 'unstable takes no arguments')
-    unstables = getobscache(repo, 'unstable')
-    return [r for r in subset if r in unstables]
+    @eh.revset('unstable')
+    def revsetunstable(repo, subset, x):
+        """``unstable()``
+        Unstable changesets are non-obsolete with obsolete ancestors.
+        """
+        args = revset.getargs(x, 0, 0, 'unstable takes no arguments')
+        unstables = getobscache(repo, 'unstable')
+        return [r for r in subset if r in unstables]
 
-@eh.revset('extinct')
-def revsetextinct(repo, subset, x):
-    """``extinct()``
-    Obsolete changesets with obsolete descendants only.
-    """
-    args = revset.getargs(x, 0, 0, 'extinct takes no arguments')
-    extincts = getobscache(repo, 'extinct')
-    return [r for r in subset if r in extincts]
+    @eh.revset('extinct')
+    def revsetextinct(repo, subset, x):
+        """``extinct()``
+        Obsolete changesets with obsolete descendants only.
+        """
+        args = revset.getargs(x, 0, 0, 'extinct takes no arguments')
+        extincts = getobscache(repo, 'extinct')
+        return [r for r in subset if r in extincts]
 
 #####################################################################
 ### Complete troubles computation logic                           ###
     return orig(repo, remote, outgoing, *args, **kwargs)
 
 #####################################################################
-### Filter extinct changeset from common operation                ###
+### Filter extinct changesets from common operations              ###
 #####################################################################
 
 @eh.wrapfunction(merge, 'update')
 @eh.wrapfunction(localrepo.localrepository, 'branchtip')
 def obsbranchtip(orig, repo, branch):
     """ensure "stable" reference does not end on a hidden changeset"""
+    if not getattr(repo, '_dofilterbranchtip', True):
+        return orig(repo, branch)
     result = ()
     heads = repo.branchmap().get(branch, ())
     if heads:
     return result[0].node()
 
 
+@eh.wrapfunction(mercurial.hgweb.hgweb_mod.hgweb, '__init__')
+@eh.wrapfunction(mercurial.hgweb.hgweb_mod.hgweb, 'refresh')
+def nofilter(orig, hgweb, *args, **kwargs):
+    orig(hgweb, *args, **kwargs)
+    hgweb.repo._dofilterbranchtip = False
+
+
 #####################################################################
 ### Additional Utilities                                          ###
 #####################################################################
 # - function to find useful changeset to stabilize
 
 ### Marker Create
+# NOW IN CORE f85816af6294
+try:
+    createmarkers = obsolete.createmarkers
+except AttributeError:
+    def createmarkers(repo, relations, metadata=None, flag=0):
+        """Add obsolete markers between changeset in a repo
 
-def createmarkers(repo, relations, metadata=None, flag=0):
-    """Add obsolete markers between changeset in a repo
+        <relations> must be an iterable of (<old>, (<new>, ...)) tuple.
+        `old` and `news` are changectx.
 
-    <relations> must be an iterable of (<old>, (<new>, ...)) tuple.
-    `old` and `news` are changectx.
+        Current user and date are used except if specified otherwise in the
+        metadata attribute.
 
-    Current user and date are used except if specified otherwise in the
-    metadata attribute.
-
-    /!\ assume the repo have been locked by the user /!\
-    """
-    # prepare metadata
-    if metadata is None:
-        metadata = {}
-    if 'date' not in metadata:
-        metadata['date'] = '%i %i' % util.makedate()
-    if 'user' not in metadata:
-        metadata['user'] = repo.ui.username()
-    # check future marker
-    tr = repo.transaction('add-obsolescence-marker')
-    try:
-        for prec, sucs in relations:
-            if not prec.mutable():
-                raise util.Abort("Cannot obsolete immutable changeset: %s" % prec)
-            nprec = prec.node()
-            nsucs = tuple(s.node() for s in sucs)
-            if nprec in nsucs:
-                raise util.Abort("Changeset %s cannot obsolete himself" % prec)
-            repo.obsstore.create(tr, nprec, nsucs, flag, metadata)
-            clearobscaches(repo)
-        tr.close()
-    finally:
-        tr.release()
+        /!\ assume the repo have been locked by the user /!\
+        """
+        # prepare metadata
+        if metadata is None:
+            metadata = {}
+        if 'date' not in metadata:
+            metadata['date'] = '%i %i' % util.makedate()
+        if 'user' not in metadata:
+            metadata['user'] = repo.ui.username()
+        # check future marker
+        tr = repo.transaction('add-obsolescence-marker')
+        try:
+            for prec, sucs in relations:
+                if not prec.mutable():
+                    raise util.Abort("cannot obsolete immutable changeset: %s" % prec)
+                nprec = prec.node()
+                nsucs = tuple(s.node() for s in sucs)
+                if nprec in nsucs:
+                    raise util.Abort("changeset %s cannot obsolete himself" % prec)
+                repo.obsstore.create(tr, nprec, nsucs, flag, metadata)
+                clearobscaches(repo)
+            tr.close()
+        finally:
+            tr.release()
 
 
 ### Useful alias
     Changesets with troubles.
     """
     _ = revset.getargs(x, 0, 0, 'troubled takes no arguments')
-    return list(repo.revs('%ld and (unstable() + latecomer() + conflicting())',
-                          subset))
+    return repo.revs('%ld and (unstable() + latecomer() + conflicting())',
+                     subset)
 
 
 ### Obsolescence graph
 # this section add several useful revset symbol not yet in core.
 # they are subject to changes
 
-### hidden revset is not in core yet
 
-@eh.revset('hidden')
-def revsethidden(repo, subset, x):
-    """``hidden()``
-    Changeset is hidden.
-    """
-    args = revset.getargs(x, 0, 0, 'hidden takes no argument')
-    return [r for r in subset if r in repo.hiddenrevs]
+if 'hidden' not in revset.symbols:
+    # in 2.3+
+    @eh.revset('hidden')
+    def revsethidden(repo, subset, x):
+        """``hidden()``
+        Changeset is hidden.
+        """
+        args = revset.getargs(x, 0, 0, 'hidden takes no argument')
+        return [r for r in subset if r in repo.hiddenrevs]
 
 ### XXX I'm not sure this revset is useful
 @eh.revset('suspended')
     priorlatecomers = len(repo.revs('latecomer()'))
     priorconflictings = len(repo.revs('conflicting()'))
     ret = orig(ui, repo, *args, **kwargs)
+    # workaround phase stupidity
+    phases._filterunknown(ui, repo.changelog, repo._phasecache.phaseroots)
     newunstables = len(repo.revs('unstable()')) - priorunstables
     newlatecomers = len(repo.revs('latecomer()')) - priorlatecomers
     newconflictings = len(repo.revs('conflicting()')) - priorconflictings
 
 @eh.wrapcommand("summary")
 def obssummary(orig, ui, repo, *args, **kwargs):
+    def write(fmt, count):
+        s = fmt % count
+        if count:
+            ui.write(s)
+        else:
+            ui.note(s)
+
     ret = orig(ui, repo, *args, **kwargs)
     nbunstable = len(getobscache(repo, 'unstable'))
     nblatecomer = len(getobscache(repo, 'latecomer'))
     nbconflicting = len(getobscache(repo, 'unstable'))
-    if nbunstable:
-        ui.write('unstable: %i changesets\n' % nbunstable)
-    else:
-        ui.note('unstable: 0 changesets\n')
-    if nblatecomer:
-        ui.write('latecomer: %i changesets\n' % nblatecomer)
-    else:
-        ui.note('latecomer: 0 changesets\n')
-    if nbconflicting:
-        ui.write('conflicting: %i changesets\n' % nbconflicting)
-    else:
-        ui.note('conflicting: 0 changesets\n')
+    write('unstable: %i changesets\n', nbunstable)
+    write('latecomer: %i changesets\n', nblatecomer)
+    write('conflicting: %i changesets\n', nbconflicting)
     return ret
 
 
 #
 # The precursor is still strip from the repository.
 
-@eh.wrapfunction(cmdutil, 'amend')
-def wrapcmdutilamend(orig, ui, repo, commitfunc, old, *args, **kwargs):
-    oldnode = old.node()
-    new = orig(ui, repo, commitfunc, old, *args, **kwargs)
-    if new != oldnode:
-        lock = repo.lock()
-        try:
-            tr = repo.transaction('post-amend-obst')
+# IN CORE 63e45aee46d4
+
+if getattr(cmdutil, 'obsolete', None) is None:
+    @eh.wrapfunction(cmdutil, 'amend')
+    def wrapcmdutilamend(orig, ui, repo, commitfunc, old, *args, **kwargs):
+        oldnode = old.node()
+        new = orig(ui, repo, commitfunc, old, *args, **kwargs)
+        if new != oldnode:
+            lock = repo.lock()
             try:
-                meta = {
-                    'date':  '%i %i' % util.makedate(),
-                    'user': ui.username(),
-                    }
-                repo.obsstore.create(tr, oldnode, [new], 0, meta)
-                tr.close()
-                clearobscaches(repo)
+                tr = repo.transaction('post-amend-obst')
+                try:
+                    meta = {
+                        'date':  '%i %i' % util.makedate(),
+                        'user': ui.username(),
+                        }
+                    repo.obsstore.create(tr, oldnode, [new], 0, meta)
+                    tr.close()
+                    clearobscaches(repo)
+                finally:
+                    tr.release()
             finally:
-                tr.release()
-        finally:
-            lock.release()
-    return new
+                lock.release()
+        return new
 
 ### rebase
 #
         repo.ui.warn(_('whole rebase set is extinct and ignored.\n'))
         return {}
     root = min(rebaseset)
-    if not repo._rebasekeep and not repo[root].mutable():
+    if (not getattr(repo, '_rebasekeep', False)
+        and not repo[root].mutable()):
         raise util.Abort(_("can't rebase immutable changeset %s") % repo[root],
                          hint=_('see hg help phases for details'))
     return orig(repo, dest, rebaseset, *ags, **kws)
     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)
+            incore = getattr(rebase, 'obsolete', None) is not None
+            if not incore:
+                extensions.wrapcommand(rebase.cmdtable, "rebase", cmdrebase)
+            extensions.wrapcommand(rebase.cmdtable, 'rebase', warnobserrors)
+            if not incore:
+                extensions.wrapfunction(rebase, 'buildstate', buildstate)
+                extensions.wrapfunction(rebase, 'defineparents', defineparents)
+                extensions.wrapfunction(rebase, 'concludenode', concludenode)
     except KeyError:
         pass  # rebase not found
 
         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:
+            if rebase.rebasenode.func_code.co_argcount == 5:
+                # rebasenode collapse argument was introduced by
+                # d1afbf03e69a (2.3)
+                r = rebase.rebasenode(repo, orig.node(), dest.node(),
+                                      {node.nullrev: node.nullrev}, False)
+            else:
+                r = rebase.rebasenode(repo, orig.node(), dest.node(),
+                                     {node.nullrev: node.nullrev})
+            if r[-1]: #some conflict
+                raise util.Abort(
+                        'unresolved merge conflicts (see hg help resolve)')
             nodenew = rebase.concludenode(repo, orig.node(), dest.node(),
                                           node.nullid)
         except util.Abort, exc:
         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(allprecursors(%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.
         graftcmd = commands.table['graft'][0]
         return graftcmd(ui, repo, old_obsolete=True, **{'continue': True})
 
-    troubled = list(repo.revs('troubled()'))
     tr = _picknexttroubled(ui, repo, anyopt)
     if tr is None:
+        troubled = repo.revs('troubled()')
         if troubled:
             ui.write_err(_('nothing to evolve here\n'))
             ui.status(_('(%i troubled changesets, do you want --any ?)\n')
     else:
         assert False  # WHAT? unknown troubles
 
-def _picknexttroubled(ui, repo, any=False):
+def _picknexttroubled(ui, repo, pickany=False):
     """Pick a the next trouble changeset to solve"""
     tr = _stabilizableunstable(repo, repo['.'])
     if tr is None:
         wdp = repo['.']
         if 'conflicting' in wdp.troubles():
             tr = wdp
-    if tr is None and any:
+    if tr is None and pickany:
         troubled = list(repo.set('unstable()'))
         if not troubled:
             troubled = list(repo.set('latecomer()'))
 
     return tr
 
+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(allprecursors(%d))',
+                                  ctx.rev()))
+        if unstables:
+            return unstables[0]
+    return None
 
 def _solveunstable(ui, repo, orig, dryrun=False):
     """Stabilize a unstable changeset"""
     # search of a parent which is not killed
     while newer == [()]:
         ui.debug("stabilize target %s is plain dead,"
-                 " trying to stabilize on it's parent")
+                 " trying to stabilize on its parent")
         obs = obs.parents()[0]
         newer = newerversion(repo, obs.node())
     if len(newer) > 1:
     repo.ui.status(_('atop:'))
     if not ui.quiet:
         displayer.show(target)
-    todo = 'hg rebase -Dr %s -d %s\n' % (orig, target)
+    todo = 'hg rebase -r %s -d %s\n' % (orig, target)
     if dryrun:
         repo.ui.write(todo)
     else:
     if not ui.quiet:
         displayer.show(prec)
     if dryrun:
-        todo = 'hg rebase --rev %s --detach %s;\n' % (latecomer, prec.p1())
+        todo = 'hg rebase --rev %s --dest %s;\n' % (latecomer, prec.p1())
         repo.ui.write(todo)
         repo.ui.write('hg update %s;\n' % prec)
         repo.ui.write('hg revert --all --rev %s;\n' % latecomer)
     if len(other.parents()) > 1:
         raise util.Abort("conflicting changeset can't be a merge (yet)")
     if other.p1() not in conflicting.parents():
-        raise util.Abort("parent are not common (not handled yet)")
+        raise util.Abort("parents are not common (not handled yet)")
 
     displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate})
     ui.status(_('merge:'))
         displayer.show(base)
     if dryrun:
         ui.write('hg update -c %s &&\n' % conflicting)
-        ui.write('hg merge %s && \n' % other)
-        ui.write('hg commit -m "auto merge resolving conflict between %s and %s"&&\n'
-                  % (conflicting, other))
+        ui.write('hg merge %s &&\n' % other)
+        ui.write('hg commit -m "auto merge resolving conflict between '
+                 '%s and %s"&&\n' % (conflicting, other))
         ui.write('hg up -C %s &&\n' % base)
         ui.write('hg revert --all --rev tip &&\n')
-        ui.write('hg commit -m "`hg log -r %s --template={desc}`";\n' % conflicting)
+        ui.write('hg commit -m "`hg log -r %s --template={desc}`";\n'
+                 % conflicting)
         return
-    #oldphase = max(conflicting.phase(), other.phase())
-    wlock = repo.wlock()
+    wlock = lock = None
     try:
+        wlock = repo.wlock()
         lock = repo.lock()
+        if conflicting not in repo[None].parents():
+            repo.ui.status(_('updating to "local" conflict\n'))
+            hg.update(repo, conflicting.rev())
+        repo.ui.note(_('merging conflicting changeset\n'))
+        stats = merge.update(repo,
+                             other.node(),
+                             branchmerge=True,
+                             force=False,
+                             partial=None,
+                             ancestor=base.node(),
+                             mergeancestor=True)
+        hg._showstats(repo, stats)
+        if stats[3]:
+            repo.ui.status(_("use 'hg resolve' to retry unresolved file merges "
+                             "or 'hg update -C .' to abandon\n"))
+        if stats[3] > 0:
+            raise util.Abort('Merge conflict between several amendments, and this is not yet automated',
+                hint="""/!\ You can try:
+/!\ * manual merge + resolve => new cset X
+/!\ * hg up to the parent of the amended changeset (which are named W and Z)
+/!\ * hg revert --all -r X
+/!\ * hg ci -m "same message as the amended changeset" => new cset Y
+/!\ * hg kill -n Y W Z
+""")
+        tr = repo.transaction('stabilize-conflicting')
         try:
-            if conflicting not in repo[None].parents():
-                repo.ui.status(_('updating to "local" conflict\n'))
-                hg.update(repo, conflicting.rev())
-            repo.ui.note(_('merging conflicting changeset\n'))
-            stats = merge.update(repo,
-                                 other.node(),
-                                 branchmerge=True,
-                                 force=False,
-                                 partial=None,
-                                 ancestor=base.node(),
-                                 mergeancestor=True)
-            hg._showstats(repo, stats)
-            if stats[3]:
-                repo.ui.status(_("use 'hg resolve' to retry unresolved file merges "
-                                 "or 'hg update -C .' to abandon\n"))
-            #repo.dirstate.write()
-            if stats[3] > 0:
-                raise util.Abort('GASP! Merge Conflict! You are on you own chap!',
-                                 hint='/!\\ hg evolve --continue will NOT work /!\\')
-            tr = repo.transaction('stabilize-conflicting')
-            try:
-                repo.dirstate.setparents(conflicting.node(), node.nullid)
-                oldlen = len(repo)
-                amend(ui, repo)
-                if oldlen == len(repo):
-                    new = conflicting
-                    # no changes
-                else:
-                    new = repo['.']
-                createmarkers(repo, [(other, (new,))])
-                phases.retractboundary(repo, other.phase(), [new.node()])
-                tr.close()
-            finally:
-                tr.release()
+            repo.dirstate.setparents(conflicting.node(), node.nullid)
+            oldlen = len(repo)
+            amend(ui, repo)
+            if oldlen == len(repo):
+                new = conflicting
+                # no changes
+            else:
+                new = repo['.']
+            createmarkers(repo, [(other, (new,))])
+            phases.retractboundary(repo, other.phase(), [new.node()])
+            tr.close()
         finally:
-            lock.release()
+            tr.release()
     finally:
-        wlock.release()
+        lockmod.release(lock, wlock)
 
 
 def conflictingdata(ctx):
         try:
             new = set(noderange(repo, opts['new']))
             targetnodes = set(noderange(repo, revs))
+            if not targetnodes:
+                raise util.Abort('nothing to prune')
             if new:
                 sucs = tuple(repo[n] for n in new)
             else:
     _('[-r] revs'))
 def touch(ui, repo, *revs, **opts):
     """Create successors with exact same property but hash
-    
-    This is used to "resurect" changeset"""
+
+    This is used to "resurrect" changesets
+    """
     revs = list(revs)
     revs.extend(opts['rev'])
     if not revs:
         return 1
     if repo.revs('public() and %ld', revs):
         raise util.Abort("can't touch public revision")
-    wlock = repo.wlock()
+    wlock = lock = None
     try:
+        wlock = repo.wlock()
         lock = repo.lock()
+        tr = repo.transaction('touch')
         try:
-            tr = repo.transaction('touch')
-            try:
-                for r in revs:
-                    ctx = repo[r]
-                    extra = ctx.extra().copy()
-                    extra['__touch-noise__'] = random.randint(0, 0xffffffff)
-                    new, _ = rewrite(repo, ctx, [], ctx,
-                                     [ctx.p1().node(), ctx.p2().node()],
-                                     commitopts={'extra': extra})
-                    createmarkers(repo, [(ctx, (repo[new],))])
-                    phases.retractboundary(repo, ctx.phase(), [new])
-                    if ctx in repo[None].parents():
-                        repo.dirstate.setparents(new, node.nullid)
-                tr.close()
-            finally:
-                tr.release()
+            for r in revs:
+                ctx = repo[r]
+                extra = ctx.extra().copy()
+                extra['__touch-noise__'] = random.randint(0, 0xffffffff)
+                new, _ = rewrite(repo, ctx, [], ctx,
+                                 [ctx.p1().node(), ctx.p2().node()],
+                                 commitopts={'extra': extra})
+                createmarkers(repo, [(ctx, (repo[new],))])
+                phases.retractboundary(repo, ctx.phase(), [new])
+                if ctx in repo[None].parents():
+                    repo.dirstate.setparents(new, node.nullid)
+            tr.close()
         finally:
-            lock.release()
+            tr.release()
     finally:
-        wlock.release()
+        lockmod.release(lock, wlock)
 
 @command('^fold',
-    [('r', 'rev', [], 'revision to fold'),],
+    [('r', 'rev', [], 'revisions to fold'),],
     # allow to choose the seed ?
     _('[-r] revs'))
 def fold(ui, repo, *revs, **opts):
-    """Fold multiple revision into a single one"""
+    """Fold multiple revisions into a single one"""
     revs = list(revs)
     revs.extend(opts['rev'])
-    if not revs:
-        revs = ['.']
-    revs = scmutil.revrange(repo, revs)
+    if revs:
+        revs = scmutil.revrange(repo, revs)
     if not revs:
         ui.write_err('no revision to fold\n')
         return 1
     roots = repo.revs('roots(%ld)', revs)
     if len(roots) > 1:
-        raise util.Abort("set have multiple roots")
+        raise util.Abort("set has multiple roots")
     root = repo[roots[0]]
     if root.phase() <= phases.public:
-        raise util.Abort("can't touch public revision")
+        raise util.Abort("can't fold public revisions")
     heads = repo.revs('heads(%ld)', revs)
     if len(heads) > 1:
-        raise util.Abort("set have multiple heads")
+        raise util.Abort("set has multiple heads")
     head = repo[heads[0]]
-    wlock = repo.wlock()
+    wlock = lock = None
     try:
+        wlock = repo.wlock()
         lock = repo.lock()
+        tr = repo.transaction('touch')
         try:
-            tr = repo.transaction('touch')
-            try:
-                allctx = [repo[r] for r in revs]
-                targetphase = max(c.phase() for c in allctx)
-                msg = '\n\n***\n\n'.join(c.description() for c in allctx)
-                newid, _ = rewrite(repo, root, allctx, head,
-                                 [root.p1().node(), root.p2().node()],
-                                 commitopts={'message': msg})
-                phases.retractboundary(repo, targetphase, [newid])
-                createmarkers(repo, [(ctx, (repo[newid],))
-                                               for ctx in allctx])
-                tr.close()
-            finally:
-                tr.release()
+            allctx = [repo[r] for r in revs]
+            targetphase = max(c.phase() for c in allctx)
+            msg = '\n\n***\n\n'.join(c.description() for c in allctx)
+            newid, _ = rewrite(repo, root, allctx, head,
+                             [root.p1().node(), root.p2().node()],
+                             commitopts={'message': msg})
+            phases.retractboundary(repo, targetphase, [newid])
+            createmarkers(repo, [(ctx, (repo[newid],))
+                                 for ctx in allctx])
+            tr.close()
         finally:
-            lock.release()
+            tr.release()
         ui.status('%i changesets folded\n' % len(revs))
         if repo.revs('. and %ld', revs):
-            repo.dirstate.setparents(newid, node.nullid)
+            hg.update(repo, newid)
     finally:
-        wlock.release()
+        lockmod.release(lock, wlock)
 
 
 @eh.wrapcommand('graft')
     except KeyError:
         raise error.Abort(_('evolution extension requires rebase extension.'))
 
-    for cmd in ['amend', 'kill', 'uncommit']:
+    for cmd in ['amend', 'kill', 'uncommit', 'touch', 'fold']:
         entry = extensions.wrapcommand(cmdtable, cmd,
                                        warnobserrors)
 

File hgext/obsolete.py

-# obsolete.py - introduce the obsolete concept in mercurial.
-#
 # Copyright 2011 Pierre-Yves David <pierre-yves.david@ens-lyon.org>
 #                Logilab SA        <contact@logilab.fr>
 #

File hgext/qsync.py

+# Copyright 2011 Logilab SA <contact@logilab.fr>
 """synchronize patches queues and evolving changesets"""
 
 import re

File qsync-enable.sh

-#!/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