New evolve docs, take 2: 2/3: user-guide.rst
Greg Ward
greg at gerg.ca
Wed May 28 18:36:11 UTC 2014
.. 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
(Strictly speaking, changesets only become public when they are pushed
to a *publishing* repository. But you have to explicitly configure
repositories to be *non-publishing*; all repositories are publishing
by default. Non-publishing repositories are an advanced topic which
we'll see when we get to `sharing mutable history`_.)
.. _`sharing mutable history`: sharing.html
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:: figure-ug01.jpg
Figure 1: unsafe history modification with core Mercurial (not
using ``evolve``): the original revision 1 is destroyed.
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:: figure-ug02.jpg
Figure 2: safe history modification using ``evolve``: the original
revision 1 is preserved as an obsolete changeset. (The "temporary
amend commit", marked with T, is an implementation detail stemming
from limitations in Mercurial's current merge machinery. Future
versions of Mercurial will not create them.)
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:: figure-ug03.jpg
Figure 3: amending a changeset with descendants means the amended
changeset is obsolete but remains visible; its descendants are
unstable. The temporary amend commit, revision 7, is hidden because
it has no non-obsolete descendants.
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:: figure-ug04.jpg
Figure 4: evolve your repository (``hg evolve --all``) to take care
of instability. Unstable changesets become obsolete, and are
replaced by successors just like the amended changeset was.
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:: figure-ug05.jpg
Figure 5: ``hg prune`` marks a changeset obsolete without creating
a successor. Just like ``hg amend``, descendants of the pruned
changeset become 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:: figure-ug06.jpg
Figure 6: once again, ``hg evolve --all` takes care of instability.
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:: figure-ug07.jpg
Figure 7: ``hg uncommit`` is a specialized form of ``hg amend``
that moves some changes back to your working directory.
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:: figure-ug08.jpg
Figure 8: ``hg uncommit`` of a changeset with descendants results
in instability *and* a dirty working directory, both of which must
be dealt with. (TODO: second transition arrow should be labelled ``hg
revert file2.c && hg evolve --all``.)
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:: figure-ug09.jpg
Figure 9: Uncommitting a file and then committing that change
separately will soon result in a two-headed repository. (TODO: 16
should be labelled "unstable".)
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:: figure-ug10.jpg
Figure 10: Sometimes, evolving instability away means we have to
deal with two heads.
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
--------------------------------
More information about the Evolve-testers
mailing list