New user guide for evolve

Greg Ward greg at gerg.ca
Sat Apr 26 23:30:39 UTC 2014


And the new user-guide.rst:

"""
.. Copyright 2014 Greg Ward <greg at gerg.ca>

------------------
Evolve: User Guide
------------------

Add a changeset: ``commit``
---------------------------

To create a new changeset, simply run ``hg commit`` as usual.
``evolve`` does not change the behaviour of ``commit`` at all.

However, it's important to understand that new changesets are in the
*draft* phase: they are mutable. This means that they can be modified
by Mercurial's existing history-editing commands (``rebase``,
``histedit``, etc.), and also by ``evolve``. Generally speaking,
changesets remain in *draft* phase until they are pushed to another
repository, at which point they enter *public* phase. ::

  $ hg commit -m"implement feature X"
  $ hg phase -r .
  1: draft

Modify (rewrite) a changeset: ``commit --amend``
------------------------------------------------

Imagine you've just committed a new changeset, and then you discover a
mistake. Maybe you forgot to run the tests and a failure slipped in.
You want to modify history so that you push one perfect changeset,
rather than one flawed changeset followed by an "oops" commit. (Or
perhaps you made a typo in the commit message—this is really feature
*Y*, not feature X. You can't fix that with a followup commit.)

This is actually trivial with plain vanilla Mercurial since 2.2: fix
your mistake and run ::

  $ hg commit --amend -m"implement feature Y"

to create a new, amended changeset. The drawback of doing this with
vanilla Mercurial is that your original, flawed, changeset is lost
forever. This is *unsafe* history editing. It's probably not too
serious if all you did was fix a syntax error, but still.

  [figure UG1: DAG with unsafe commit --amend]

The difference when using ``evolve`` is subtle: outwardly, things look
the same::

  $ hg commit --amend -m"implement feature Y"

(Alternately, ``hg amend`` is nearly synonymous with ``hg commit
--amend``. The difference is that ``hg amend`` uses the existing
commit message by default, whereas ``hg commit --amend`` will run your
editor if you don't pass ``-m`` or ``-l``.)

But under the hood, Mercurial has simply marked the old changeset
*obsolete*, replacing it with the new one. If it turns out that your
trivial patch to get the tests passing again was a terrible idea, you
can recover the original changeset and try again (see below, under
"Recovering an obsolete changeset"). Thus, this is *safe* history
modification.

It's important to understand that Mercurial tracks the second-order
relationship between your flawed changeset and the improved version:
the new improved changeset is the *successor*, and the original (now
obsolete) changeset is its *predecessor*.

 [figure UG2: DAG with safe amend and successor/predecessor]

A final note: in this case, the obsolete changeset is also *hidden*.
That is the usual end state for obsolete changesets. But many
scenarios result in obsolete changesets that are still visible, which
indicates your history modification work is not yet done. We'll see an
example of that next.

Modify an older changeset
-------------------------

Sometimes you don't notice your mistakes until after you have
committed some new changesets on top of them. Traditionally, this
looks something like ::

  $ hg commit -m"fix bug 17"         # rev 4 (mistake here)
  $ hg commit -m"cleanup"            # rev 5
  $ hg commit -m"feature 23"         # rev 6
  $ hg commit -m"oops"               # rev 7 (fix mistake)

The trouble with this, of course, is that it makes you look bad. You
made a mistake, and the record of that mistake is recorded in history
for all eternity. More subtly, there now exist changesets that are
*worse* than what came before—the code no longer builds, the tests
don't pass, or similar. Anyone reviewing these patches will waste time
noticing the error in the earlier patch, and then the correction later
on.

You can avoid all this by amending and evolving. Here's how it works,
assuming you have just committed revision 7 and noticed the mistake
in revision 4::

  $ hg update 4
  [fix mistake]
  $ hg amend

At this point, revision 4 is *obsolete* and revisions 5 and 6—the
descendants of 4—are in a funny state: they are *unstable*.

  [figure UG3: obsolete and unstable changesets]

All non-obsolete descendants of an obsolete changeset are unstable. An
interesting consequence of this is that revision 4 is still visible,
even though it is obsolete. Obsolete changesets with non-obsolete
descendants are not hidden.

The fix is to *evolve* history::

  $ hg evolve --all

This is a separate step, not automatically part of ``hg amend``,
because there might be conflicts. If your evolved changeset modifies a
file that one of its descendants modified, Mercurial has to fire up
your merge tool to resolve the conflict. More importantly, you have to
switch contexts from "writing code" to "resolving conflicts". That can
be an expensive context switch, so Mercurial lets you decide when to
do it.

The end state, after ``evolve`` finishes, is that revisions 4-6 are
obsolete and hidden. Their successor revisions (8–10) replace them.
(In case you're wondering why there's a gap at revision 7: current
versions of ``evolve`` create a "temporary amend commit" every time
you amend a changeset with descendants. You'll see them if you run
``hg --hidden log``. This is an undesirable feature which will be
fixed in a future version.)

  [figure UG4: obsolete revs with successors]

Remove unwanted changes
-----------------------

Sometimes you make a change, and then decide it was such a bad idea
that you don't want anyone to know about it. Or maybe it was a
debugging hack that you needed to keep around for a while, but do not
intend to ever push publicly.

In either case, ``hg prune`` is the answer. ``prune`` simply marks
changesets obsolete without creating any new changesets to replace
them. Let's say you've just committed the following changesets::

  $ hg commit -m"useful work"       # rev 11
  $ hg commit -m"debug hack"        # rev 12
  $ hg commit -m"more work"         # rev 13

You want to drop revision 12, but keep 11 and 13. No problem::

  $ hg prune 12

As above, this leaves your repository in a funny intermediate state:
revision 13 is the non-obsolete descendant of obsolete revision 12.
That is, revision 13 is unstable.

  [figure UG5: 12 obsolete, 13 non-obsolete/unstable]

So you need to evolve your repository::

  $ hg evolve --all

This rebases revision 13 on top of 11 as the new revision 14, leaving
12 and 13 obsolete and hidden:

  [figure UG6: new 14, 12 and 13 obsolete and hidden]


Uncommitting files, version 1: immediate fix
--------------------------------------------

Occasionally you commit more than you intended to: perhaps you only
meant to commit certain files, and forgot to specify those files on
the ``commit`` command line, so Mercurial commits everything. This is
easy to fix with ``uncommit``. Here's an example::

  $ hg commit -m"fix bug 234"      # rev 15

Say you meant to commit only ``file1.c``, but accidentally committed
``file2.c`` as well. If you immediately notice your mistake, fix it
with ::

  $ hg uncommit file2.c

This obsoletes your previous changeset and replaces it with a new one:

  [figure UG7: rev 15 obsolete, rev 16 successor]

The unrelated changes to ``file2.c`` are left in your working
directory, for you to deal with as you see fit::

  $ hg status
  M file2.c

Uncommitting files, version 2: fix later with revert
----------------------------------------------------

Say you don't notice your mistake immediately, and do something like
this::

  $ hg commit -m"fix bug 234"      # rev 15 (too many files)
  $ hg commit -m"fix bug 641"      # rev 16

To fix this, you need to travel back in time and amend revision 15,
leaving your changes to ``file2.c`` back in the working directory::

  $ hg update 15
  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
  $ hg uncommit file2.c
  1 new unstable changesets
  $ hg status
  M file2.c

At this point your repository needs to be evolved, since it has
unstable changesets. But first, you need to decide what to do with
``file2.c``, since ``hg evolve`` requires a clean working directory
for resolving merge conflicts.

If the change to ``file2.c`` was a temporary debugging hack, we can
discard it and immediately evolve the unstable changeset::

  $ hg revert file2.c
  $ hg evolve --all
  move:[16] fix bug 641
  atop:[17] fix bug 234

This results in a slightly more complicated picture:

  [figure UG8: rev 15 and 16 obsolete, successors 17 and 18]

Uncommitting files, version 3: fix later with commit
----------------------------------------------------

If your change to ``file2.c`` is actually valuable enough to commit,
things get a bit more complicated. As above, we backup to revision 15
and uncommit, leaving a modified ``file2.c`` in the working
directory::

  $ hg update -q 15
  $ hg uncommit -q file2.c
  $ hg status
  M file2.c

Now let's save those valuable changes before evolving::

  $ hg commit -m'unrelated change to file2.c'

This creates a new changeset, revision 18, which is a child of
revision 17. And lurking in the background, we still have to worry
about unstable revision 16, which was destabilized by amending
revision 15 with ``hg uncommit``::

  [figure UG9: rev 15 obsolete, rev 16 unstable, rev 17 and 18 normal]

This is where things get complicated. As usual when a repository has
unstable changesets, we want to evolve it::

  $ hg evolve --all

The problem is that ``hg evolve`` has rebased revision 16 onto
revision 17, creating 19 (the successor of 16). This is entirely
logical: 16 was the child of 15, and 15's successor was 17. So of
course 16's successor (19) should be the child of 15's successor (17).
Unfortunately, that leaves us with a two-headed repository:

  [figure UG10: rev 15, 16 obsolete; 17, 18 as before; adds 19 as child of 17 and successor of 16]

As usual when faced with a two-headed repository, you can either merge
or rebase. It's up to you.


Understanding revision numbers
------------------------------

You may have noticed some funny business going on with revision
numbers: there are now gaps in the sequence. That's something you
don't see with plain vanilla Mercurial; normally, revision N is always
followed by revision N+1.

This is just the visible manifestation of hidden changesets. If
revision 95 is followed by revision 98, that means there are two
hidden changesets, 96 and 97, in between.

Note that changeset IDs are still the permanent, immutable identifier
for changesets. Revision numbers are, as ever, a handy shorthand that
work in your local repository, but cannot be used across repositories.
They also have the useful property of showing when there are hidden
changesets lurking under the covers, which is why this document uses
revision numbers.


Recovering an obsolete changeset
--------------------------------
"""

No, I haven't written that last section yet. ;-)

Let the bikeshedding begin!

       Greg
-- 
Greg Ward                            http://www.gerg.ca
<greg at gerg.ca>                       @gergdotca



More information about the Evolve-testers mailing list