Changeset Evolution training

Boris Feld
octobus.net

Introduction

Welcome

Hello everyone, and welcome to this Changeset Evolution training. During this session, you will learn how to safely rewrite history with Mercurial and Evolve, and how to collaborate together with your colleagues while rewriting the history at the same time.

This training is designed to last approximately ¾ hours.

You will use this repository during the training: https://bitbucket.org/octobus/evolve_training_repo. Please clone it somewhere relevant.

$ hg clone https://bitbucket.org/octobus/evolve_training_repo
$ cd evolve_training_repo

Copy the provided hgrc to ensure a smooth training experience:

$ cp hgrc .hg/hgrc

This training support will contains commands you are expected to type and launch. These commands will be in the following format:

$ COMMAND YOU ARE EXPECTED TO TYPE
output you are expecting to see

Preliminary checks

Mercurial version

First let's use the following command to verify which version of Mercurial you are using:

$ hg --version
Mercurial Distributed SCM (version 4.4.2)
(see https://mercurial-scm.org for more information)

Copyright (C) 2005-2017 Matt Mackall and others
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

You need at least Mercurial version 4.1. If you don't have a recent enough version, please call your instructor.

In order to activate the Evolve extension, add these lines in your user configuration (using the command hg config --edit):

[extensions]
evolve =
topic =

Mercurial extensions

Now let's check the version of your extensions. You will need all of these for the training:

$ hg --version --verbose
[...]
  evolve       external  7.1.0
  topic        external  0.6.0
  rebase       internal
  histedit     internal

The Basics

In this section, we are going to learn how to do basic history rewriting like rewriting a changeset or rebasing.

Amend

The smallest possible history rewriting is rewriting a changeset description message. We often save and close the editor too early, and/or haven't seen a typo.

It is very easy to fix a changeset description message, so let's do that. First be sure that you are in your clone of the evolve_training_repo. then update to the typo branch:

$ hg update typo

Check what the current repository looks like:

$ hg log -G -r "::typo"
@  changeset:   1:5d48a444aba7
|  branch:      typo
|  user:        Boris Feld <boris.feld@octobus.net>
|  date:        Thu Dec 07 11:26:53 2017 +0100
|  summary:     Fx bug
|
o  changeset:   0:d2eb2ac6a5bd
   user:        Boris Feld <boris.feld@octobus.net>
   date:        Thu Dec 07 11:26:05 2017 +0100
   summary:     ROOT

We have a root commit and another based on it. Double-check that you are on the right changeset with the hg summary command:

$ hg summary
parent: 1:5d48a444aba7 
 Fx bug
branch: typo
commit: (clean)
update: (current)
phases: 16 draft

The current commit description message seems wrong, Fx bug, there is definitely a letter missing. Let's fix this typo with the hg commit command.

Usually, the hg commit is used to create new commit but we can use the --amend option to instead modify the current commit (see hg help commit for more information):

$ hg commit --amend --message "Fix bug"

Let's take a look at the repository now:

$ hg log -G -r "::typo"
@  changeset:   17:708369dc1bfe
|  branch:      typo
|  tag:         tip
|  parent:      0:d2eb2ac6a5bd
|  user:        Boris Feld <boris.feld@octobus.net>
|  date:        Thu Dec 07 11:26:53 2017 +0100
|  summary:     Fix bug
|
o  changeset:   0:d2eb2ac6a5bd
   user:        Boris Feld <boris.feld@octobus.net>
   date:        Thu Dec 07 11:26:05 2017 +0100
   summary:     ROOT

The logs before and after amending looks pretty similar, we are going to analyze the differences later. Did you catch the differences?

Rebase

Let's try to rebase something now. Let's say that you have a branch named build/linuxsupport-v2 which was started on another branch named build/v2. Everything was fine until build/v2 grew a new commit, and now you want to rebase build/linuxsupport-v2 on top of build/v2 to be up-to-date with other the changes:

$ hg update build/linuxsupport-v2
$ hg log -G -r "::desc(v2)"
o  changeset:   6:0e694460372e
|  branch:      build/v2
|  parent:      2:f3bd0ab4ee87
|  user:        Boris Feld <boris.feld@octobus.net>
|  date:        Mon Dec 11 11:22:16 2017 +0100
|  summary:     New commit on build/v2
|
| @  changeset:   5:39e9774ab30b
| |  branch:      build/linuxsupport-v2
| |  user:        Boris Feld <boris.feld@octobus.net>
| |  date:        Mon Dec 11 11:21:02 2017 +0100
| |  summary:     Third commit on build/linuxsupport-v2
| |
| o  changeset:   4:5ad93176b041
| |  branch:      build/linuxsupport-v2
| |  user:        Boris Feld <boris.feld@octobus.net>
| |  date:        Mon Dec 11 11:20:24 2017 +0100
| |  summary:     Second commit on build/linuxsupport-v2.
| |
| o  changeset:   3:424916b62f4c
|/   branch:      build/linuxsupport-v2
|    user:        Boris Feld <boris.feld@octobus.net>
|    date:        Thu Dec 07 16:46:32 2017 +0100
|    summary:     First commit on build/linuxsupport-v2
|
o  changeset:   2:f3bd0ab4ee87
|  branch:      build/v2
|  parent:      0:d2eb2ac6a5bd
|  user:        Boris Feld <boris.feld@octobus.net>
|  date:        Thu Dec 07 16:45:07 2017 +0100
|  summary:     First commit on build/v2
|
o  changeset:   0:d2eb2ac6a5bd
   user:        Boris Feld <boris.feld@octobus.net>
   date:        Thu Dec 07 11:26:05 2017 +0100
   summary:     ROOT

Let's rebase our branch on top of build/v2 with the hg rebase command. The hg rebase command have many ways to select commits:

  1. Explicitly select them using "--rev".
  2. Use "--source" to select a root commit and include all of its descendants.
  3. Use "--base" to select a commit; rebase will find ancestors and their descendants which are not also ancestors of the destination.
  4. If you do not specify any of "--rev", "source", or "--base", rebase will use "--base ." as above.

For this first example, we are gonna stays simple and explicitly select the commits we want to rebase with the --rev option.

The hg rebase command also accepts a destination with the --dest option. And finally, as we are using named branches, don't forget to use the --keepbranches or the rebased commits will be on the wrong branch:

$ hg rebase -r "branch(build/linuxsupport-v2)" --dest "build/v2" --keepbranches
rebasing 3:424916b62f4c "First commit on build/linuxsupport-v2"
rebasing 4:5ad93176b041 "Second commit on build/linuxsupport-v2."
rebasing 5:39e9774ab30b "Third commit on build/linuxsupport-v2"

Now we have a nice, clean and flat history:

$ hg log -G -r "::desc(v2)"
@  changeset:   20:3d2c8a2356a2
|  branch:      build/linuxsupport-v2
|  tag:         tip
|  user:        Boris Feld <boris.feld@octobus.net>
|  date:        Mon Dec 11 11:21:02 2017 +0100
|  summary:     Third commit on build/linuxsupport-v2
|
o  changeset:   19:4686378320d7
|  branch:      build/linuxsupport-v2
|  user:        Boris Feld <boris.feld@octobus.net>
|  date:        Mon Dec 11 11:20:24 2017 +0100
|  summary:     Second commit on build/linuxsupport-v2.
|
o  changeset:   18:7b62ce2c283e
|  branch:      build/linuxsupport-v2
|  parent:      6:0e694460372e
|  user:        Boris Feld <boris.feld@octobus.net>
|  date:        Thu Dec 07 16:46:32 2017 +0100
|  summary:     First commit on build/linuxsupport-v2
|
o  changeset:   6:0e694460372e
|  branch:      build/v2
|  parent:      2:f3bd0ab4ee87
|  user:        Boris Feld <boris.feld@octobus.net>
|  date:        Mon Dec 11 11:22:16 2017 +0100
|  summary:     New commit on build/v2
|
o  changeset:   2:f3bd0ab4ee87
|  branch:      build/v2
|  parent:      0:d2eb2ac6a5bd
|  user:        Boris Feld <boris.feld@octobus.net>
|  date:        Thu Dec 07 16:45:07 2017 +0100
|  summary:     First commit on build/v2
|
o  changeset:   0:d2eb2ac6a5bd
   user:        Boris Feld <boris.feld@octobus.net>
   date:        Thu Dec 07 11:26:05 2017 +0100
   summary:     ROOT

For more details about how to use the hg rebase command, see hg help rebase.

Under the hood

What did happened when we just ran the hg amend and hg rebase commands? What was done exactly to make the whole process work seamlessly?

Let's go back to our previous amend example.

Amend

When we did our amend, the status of the repository was:

x  changeset:   1:5d48a444aba7
|  branch:      typo
|  user:        Boris Feld <boris.feld@octobus.net>
|  date:        Thu Dec 07 11:26:53 2017 +0100
|  obsolete:    reworded using amend as 17:708369dc1bfe
|  summary:     Fx bug
|
o  changeset:   0:d2eb2ac6a5bd
   user:        Boris Feld <boris.feld@octobus.net>
   date:        Thu Dec 07 11:26:05 2017 +0100
   summary:     ROOT

And after the amend, the repository looked like:

$ hg log -G -r "::typo"
o  changeset:   17:708369dc1bfe
|  branch:      typo
|  parent:      0:d2eb2ac6a5bd
|  user:        Boris Feld <boris.feld@octobus.net>
|  date:        Thu Dec 07 11:26:53 2017 +0100
|  summary:     Fix bug
|
o  changeset:   0:d2eb2ac6a5bd
   user:        Boris Feld <boris.feld@octobus.net>
   date:        Thu Dec 07 11:26:05 2017 +0100
   summary:     ROOT

Do you see what is the difference?

The big difference, apart from the fixed changeset message, is the revision hash and revision number. The Fix bug revision changed from d2eb2ac6a5bd to 708369dc1bfe. It means that the fixed changeset is a new one. But where did the old changeset go?

It didn't actually go very far, as it just became hidden. When we rewrite a changeset with the Evolve extension, instead of blindly delete it, we create a new changeset and hide the old one, which is still there, and we can even see it with the --hidden option available on most Mercurial commands:

$ hg log -G -r "::branch(typo)" --hidden
o  changeset:   17:708369dc1bfe
|  branch:      typo
|  parent:      0:d2eb2ac6a5bd
|  user:        Boris Feld <boris.feld@octobus.net>
|  date:        Thu Dec 07 11:26:53 2017 +0100
|  summary:     Fix bug
|
| x  changeset:   1:5d48a444aba7
|/   branch:      typo
|    user:        Boris Feld <boris.feld@octobus.net>
|    date:        Thu Dec 07 11:26:53 2017 +0100
|    obsolete:    reworded using amend as 17:708369dc1bfe
|    summary:     Fx bug
|
o  changeset:   0:d2eb2ac6a5bd
   user:        Boris Feld <boris.feld@octobus.net>
   date:        Thu Dec 07 11:26:05 2017 +0100
   summary:     ROOT

Notice the x in the log output which shows that a changeset is hidden.

In addition to hiding the original changeset, we are also storing additional information which is recording the relation between a changeset, the precursor and its successor. It basically stores the information that the commit X was rewritten into the commit Y by the user U at the date D. This piece of information is stored in something called an obsolescence marker. It will be displayed like this:

Here the commit 5d48a444aba7 was rewritten into 708369dc1bfe. Also please notice the difference of style of the commit 5d48a444aba7, that's because it have been rewritten.

Rebase

Successors don't need to share anything with their precursor. They could have a different description message, user, date or even parents.

Let's look at our earlier rebase example. The status before the rebase was:

$ hg log -G -r "::branch(build/v2) or ::precursors('build/linuxsupport-v2')" --hidden
o  changeset:   6:0e694460372e
|  branch:      build/v2
|  parent:      2:f3bd0ab4ee87
|  user:        Boris Feld <boris.feld@octobus.net>
|  date:        Mon Dec 11 11:22:16 2017 +0100
|  summary:     New commit on build/v2
|
| x  changeset:   5:39e9774ab30b
| |  branch:      build/linuxsupport-v2
| |  user:        Boris Feld <boris.feld@octobus.net>
| |  date:        Mon Dec 11 11:21:02 2017 +0100
| |  obsolete:    rebased using rebase as 20:3d2c8a2356a2
| |  summary:     Third commit on build/linuxsupport-v2
| |
| x  changeset:   4:5ad93176b041
| |  branch:      build/linuxsupport-v2
| |  user:        Boris Feld <boris.feld@octobus.net>
| |  date:        Mon Dec 11 11:20:24 2017 +0100
| |  obsolete:    rebased using rebase as 19:4686378320d7
| |  summary:     Second commit on build/linuxsupport-v2.
| |
| x  changeset:   3:424916b62f4c
|/   branch:      build/linuxsupport-v2
|    user:        Boris Feld <boris.feld@octobus.net>
|    date:        Thu Dec 07 16:46:32 2017 +0100
|    obsolete:    rebased using rebase as 18:7b62ce2c283e
|    summary:     First commit on build/linuxsupport-v2
|
o  changeset:   2:f3bd0ab4ee87
|  branch:      build/v2
|  parent:      0:d2eb2ac6a5bd
|  user:        Boris Feld <boris.feld@octobus.net>
|  date:        Thu Dec 07 16:45:07 2017 +0100
|  summary:     First commit on build/v2
|
o  changeset:   0:d2eb2ac6a5bd
   user:        Boris Feld <boris.feld@octobus.net>
   date:        Thu Dec 07 11:26:05 2017 +0100
   summary:     ROOT

And after it was:

$ hg log -G -r "::desc(v2)"
@  changeset:   20:3d2c8a2356a2
|  branch:      build/linuxsupport-v2
|  tag:         tip
|  user:        Boris Feld <boris.feld@octobus.net>
|  date:        Mon Dec 11 11:21:02 2017 +0100
|  summary:     Third commit on build/linuxsupport-v2
|
o  changeset:   19:4686378320d7
|  branch:      build/linuxsupport-v2
|  user:        Boris Feld <boris.feld@octobus.net>
|  date:        Mon Dec 11 11:20:24 2017 +0100
|  summary:     Second commit on build/linuxsupport-v2.
|
o  changeset:   18:7b62ce2c283e
|  branch:      build/linuxsupport-v2
|  parent:      6:0e694460372e
|  user:        Boris Feld <boris.feld@octobus.net>
|  date:        Thu Dec 07 16:46:32 2017 +0100
|  summary:     First commit on build/linuxsupport-v2
|
o  changeset:   6:0e694460372e
|  branch:      build/v2
|  parent:      2:f3bd0ab4ee87
|  user:        Boris Feld <boris.feld@octobus.net>
|  date:        Mon Dec 11 11:22:16 2017 +0100
|  summary:     New commit on build/v2
|
o  changeset:   2:f3bd0ab4ee87
|  branch:      build/v2
|  parent:      0:d2eb2ac6a5bd
|  user:        Boris Feld <boris.feld@octobus.net>
|  date:        Thu Dec 07 16:45:07 2017 +0100
|  summary:     First commit on build/v2
|
o  changeset:   0:d2eb2ac6a5bd
   user:        Boris Feld <boris.feld@octobus.net>
   date:        Thu Dec 07 11:26:05 2017 +0100
   summary:     ROOT

Did the same thing happen under the hood?

Yes, exactly! The old changesets are still around, and they are just hidden.

$ hg log -G -r "::desc(v2)" --hidden
@  changeset:   20:3d2c8a2356a2
|  branch:      build/linuxsupport-v2
|  tag:         tip
|  user:        Boris Feld <boris.feld@octobus.net>
|  date:        Mon Dec 11 11:21:02 2017 +0100
|  summary:     Third commit on build/linuxsupport-v2
|
o  changeset:   19:4686378320d7
|  branch:      build/linuxsupport-v2
|  user:        Boris Feld <boris.feld@octobus.net>
|  date:        Mon Dec 11 11:20:24 2017 +0100
|  summary:     Second commit on build/linuxsupport-v2.
|
o  changeset:   18:7b62ce2c283e
|  branch:      build/linuxsupport-v2
|  parent:      6:0e694460372e
|  user:        Boris Feld <boris.feld@octobus.net>
|  date:        Thu Dec 07 16:46:32 2017 +0100
|  summary:     First commit on build/linuxsupport-v2
|
o  changeset:   6:0e694460372e
|  branch:      build/v2
|  parent:      2:f3bd0ab4ee87
|  user:        Boris Feld <boris.feld@octobus.net>
|  date:        Mon Dec 11 11:22:16 2017 +0100
|  summary:     New commit on build/v2
|
| x  changeset:   5:39e9774ab30b
| |  branch:      build/linuxsupport-v2
| |  user:        Boris Feld <boris.feld@octobus.net>
| |  date:        Mon Dec 11 11:21:02 2017 +0100
| |  obsolete:    rebased using rebase as 20:3d2c8a2356a2
| |  summary:     Third commit on build/linuxsupport-v2
| |
| x  changeset:   4:5ad93176b041
| |  branch:      build/linuxsupport-v2
| |  user:        Boris Feld <boris.feld@octobus.net>
| |  date:        Mon Dec 11 11:20:24 2017 +0100
| |  obsolete:    rebased using rebase as 19:4686378320d7
| |  summary:     Second commit on build/linuxsupport-v2.
| |
| x  changeset:   3:424916b62f4c
|/   branch:      build/linuxsupport-v2
|    user:        Boris Feld <boris.feld@octobus.net>
|    date:        Thu Dec 07 16:46:32 2017 +0100
|    obsolete:    rebased using rebase as 18:7b62ce2c283e
|    summary:     First commit on build/linuxsupport-v2
|
o  changeset:   2:f3bd0ab4ee87
|  branch:      build/v2
|  parent:      0:d2eb2ac6a5bd
|  user:        Boris Feld <boris.feld@octobus.net>
|  date:        Thu Dec 07 16:45:07 2017 +0100
|  summary:     First commit on build/v2
|
o  changeset:   0:d2eb2ac6a5bd
   user:        Boris Feld <boris.feld@octobus.net>
   date:        Thu Dec 07 11:26:05 2017 +0100
   summary:     ROOT

And we created three obsolescence markers, between each rebased commit and its successor:

Evolution History

Mercurial is designed to track the history of files. Evolution goes beyond, and tracks the history of the history of files. It basically tracks the different versions of your commits.

As it is a new dimension of history, the classical Mercurial commands are not always the best to visualize this new history.

We have seen that we can see the hidden changesets with the --hidden option on hg log:

$ hg log -G -r "::branch(typo)" --hidden
o  changeset:   17:708369dc1bfe
|  branch:      typo
|  parent:      0:d2eb2ac6a5bd
|  user:        Boris Feld <boris.feld@octobus.net>
|  date:        Thu Dec 07 11:26:53 2017 +0100
|  summary:     Fix bug
|
| x  changeset:   1:5d48a444aba7
|/   branch:      typo
|    user:        Boris Feld <boris.feld@octobus.net>
|    date:        Thu Dec 07 11:26:53 2017 +0100
|    obsolete:    reworded using amend as 17:708369dc1bfe
|    summary:     Fx bug
|
o  changeset:   0:d2eb2ac6a5bd
   user:        Boris Feld <boris.feld@octobus.net>
   date:        Thu Dec 07 11:26:05 2017 +0100
   summary:     ROOT

To visualize the obsolescence history of a particular changeset, we can use the dedicated command hg obslog. The option are quite similar to hg log (you can read hg help obslog for more information):

$ hg obslog -G -r typo
o  708369dc1bfe (17) Fix bug
|
x  5d48a444aba7 (1) Fx bug
     rewritten(description) as 708369dc1bfe by test (Thu Jan 01 00:00:00 1970 +0000)

We can even print what changed between the two versions with the --patch option:

$ hg obslog -G -r typo --patch
o  708369dc1bfe (17) Fix bug
|
x  5d48a444aba7 (1) Fx bug
     rewritten(description) as 708369dc1bfe by test (Thu Jan 01 00:00:00 1970 +0000)
       --- a/5d48a444aba7-changeset-description
       +++ b/708369dc1bfe-changeset-description
       @@ -1,1 +1,1 @@
       -Fx bug
       +Fix bug


Obslog works both ways, as it can display precursors and successors with the --all option:

$ hg obslog -G -r 5d48a444aba7 --hidden
x  5d48a444aba7 (1) Fx bug
     rewritten(description) as 708369dc1bfe by test (Thu Jan 01 00:00:00 1970 +0000)

$ hg obslog -G -r 5d48a444aba7 --hidden --all
o  708369dc1bfe (17) Fix bug
|
x  5d48a444aba7 (1) Fx bug
     rewritten(description) as 708369dc1bfe by test (Thu Jan 01 00:00:00 1970 +0000)

We can also use obslog on the changesets that we rebased earlier:

$ hg obslog -r "build/linuxsupport-v2"
@  3d2c8a2356a2 (20) Third commit on build/linuxsupport-v2
|
x  39e9774ab30b (5) Third commit on build/linuxsupport-v2
     rewritten(parent) as 3d2c8a2356a2 by test (Thu Jan 01 00:00:00 1970 +0000)

Why the hg obslog command is only showing two commits while we rebased three of them?

$ hg obslog -r "branch('build/linuxsupport-v2')"
@  3d2c8a2356a2 (20) Third commit on build/linuxsupport-v2
|
| o  4686378320d7 (19) Second commit on build/linuxsupport-v2.
| |
| | o  7b62ce2c283e (18) First commit on build/linuxsupport-v2
| | |
x | |  39e9774ab30b (5) Third commit on build/linuxsupport-v2
 / /     rewritten(parent) as 3d2c8a2356a2 by test (Thu Jan 01 00:00:00 1970 +0000)
| |
| x  424916b62f4c (3) First commit on build/linuxsupport-v2
|      rewritten(parent) as 7b62ce2c283e by test (Thu Jan 01 00:00:00 1970 +0000)
|
x  5ad93176b041 (4) Second commit on build/linuxsupport-v2.
     rewritten(parent) as 4686378320d7 by test (Thu Jan 01 00:00:00 1970 +0000)

And why the hg obslog command show disconnected graphs when asking for the obslog of the whole branch?

While these two obsolescence logs look very similar —because they show a similar change—, the two changesets log histories looked quite different.

Using the hg log command to understand the Evolution history is hard because it is designed for displaying the files history, not the Evolution history. The hg obslog has been specially designed for this use-case and is more suited for this use-case.

TortoiseHG

TortoiseHG should be able to display obsolescence history for your repositories.

To display all the hidden commits, we need to click on the search icon, then on the Show/Hide hidden changesets at the right of the filter check box. It is also possible to provide a revset to filter the repository, for example :6 + ::20 to display only the revisions we have been working with until now:

Medium level

More rewriting commands

The hg amend and hg rebase commands are the foundations for changeset evolution in Mercurial. You could do everything with these, but, luckily for us, the evolve extension provides human-friendly commands for common needs. We are going to see them now:

Amend

The Evolve extension provides its own hg amend command, which is similar to the hg commit --amend that we used previously, and adds several nice features:

  • The -e/--edit option edits the commit message in an editor, which is not opened by default any more.
  • The user and date can be updated to the current ones with the -U/--current-user and -D/--current-date options.
  • More capabilities for rewriting the changeset.

The hg amend command accepts either file paths, to add all the modifications on these files in the current changeset, or the -i/--interactive option to select precisely what to add in it.

We are going to use it to rewrite the author of the changeset:

$ hg update amend-extract

We have two commits on the amend-extract branch:

$ hg log -G -r "::amend-extract"
@  changeset:   8:e288d12d5e96
|  branch:      amend-extract
|  user:        Bad User
|  date:        Fri Dec 08 15:28:46 2017 +0100
|  summary:     Commit to be extracted
|
o  changeset:   7:4ae0d1de7a58
|  branch:      amend-extract
|  parent:      0:d2eb2ac6a5bd
|  user:        Boris Feld <boris.feld@octobus.net>
|  date:        Fri Dec 08 15:04:09 2017 +0100
|  summary:     Base file
|
o  changeset:   0:d2eb2ac6a5bd
   user:        Boris Feld <boris.feld@octobus.net>
   date:        Thu Dec 07 11:26:05 2017 +0100
   summary:     ROOT

The user for the amend-extract head seems wrong, so let's fix it with the hg amend command:

$ hg amend --user "Good User"

Now let's check that the user has been amended correctly:

$ hg export .
# HG changeset patch
# User Good User
# Date 1512743326 -3600
#      Fri Dec 08 15:28:46 2017 +0100
# Branch amend-extract
# Node ID 5935c1c3ad24c4d3338d94473261eb89a73ef0d5
# Parent  4ae0d1de7a58916e6f24fdc42e890a71fccbd931
Commit to be extracted

diff -r 4ae0d1de7a58 -r 5935c1c3ad24 badfile
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/badfile	Fri Dec 08 15:28:46 2017 +0100
@@ -0,0 +1,1 @@
+badbadfile
diff -r 4ae0d1de7a58 -r 5935c1c3ad24 fileextract
--- a/fileextract	Fri Dec 08 15:04:09 2017 +0100
+++ b/fileextract	Fri Dec 08 15:28:46 2017 +0100
@@ -1,5 +1,6 @@
 # The file dedicated to be extracted
 
+0
 1
 2
 3
@@ -10,4 +11,5 @@
 8
 9
 10
+42
 

The user is the good one, but the diff looks weird. It seems that both a bad file and an incorrect line have slipped in this commit. We need to fix that.

There are several solutions here, and we could manually edit the file and amend it. But, luckily for us, the hg amend command also has a very helpful option named --extract that will help us.

Amend extract

The hg amend command is meant to move file modifications from your working directory to the current changeset (which is considered as the parent of working directory). hg amend also provides the option --extract that can be used to invert the meaning of the command: with this option, hg amend will move the file modifications from your current changeset to your working directory.

This is often used to remove a file or a line that is not meant to be in the current commit.

As usual, we can either pass file paths or use the -i option to select which lines to extract.

First, let's extract the badfile:

$ hg amend --extract badfile

Now let's check the status of the changeset and the working directory:

$ hg export -r .
# HG changeset patch
# User Good User
# Date 1512743326 -3600
#      Fri Dec 08 15:28:46 2017 +0100
# Branch amend-extract
# Node ID 1e04751ef00ae76e357fe083f08e3f2234c3b26b
# Parent  4ae0d1de7a58916e6f24fdc42e890a71fccbd931
Commit to be extracted

diff -r 4ae0d1de7a58 -r 1e04751ef00a fileextract
--- a/fileextract	Fri Dec 08 15:04:09 2017 +0100
+++ b/fileextract	Fri Dec 08 15:28:46 2017 +0100
@@ -1,5 +1,6 @@
 # The file dedicated to be extracted
 
+0
 1
 2
 3
@@ -10,4 +11,5 @@
 8
 9
 10
+42
 

The file is not included in the commit anymore! Did it just vanish? What if you wanted to keep it and, for example, put it in another commit?

Don't worry, the extracted files and lines still are in your working directory:

$ hg status
A badfile

As we are not going to need this file anymore, let's forget it with the hg revert command:

$ hg revert --all --no-backup
forgetting badfile

Also don't forget to remove the file:

$ rm badfile

Ok. Now we still have a line to extract from our commit, so let's use the handy interactive mode of hg amend --extract to extract lines:

$ hg amend --extract --interactive
diff --git a/fileextract b/fileextract
2 hunks, 2 lines changed
examine changes to 'fileextract'? [Ynesfdaq?] y

@@ -1,5 +1,6 @@
 # The file dedicated to be extracted
 
+0
 1
 2
 3
discard change 1/2 to 'fileextract'? [Ynesfdaq?] n

@@ -10,4 +11,5 @@
 8
 9
 10
+42
 
discard change 2/2 to 'fileextract'? [Ynesfdaq?] y

Much better! One last thing, as the line that we extracted is still in our working directory, just like when we extracted a file:

$ hg status
M fileextract
$ hg diff
diff -r 76ace846a3f9 fileextract
--- a/fileextract	Fri Dec 08 15:28:46 2017 +0100
+++ b/fileextract	Thu Jan 01 00:00:00 1970 +0000
@@ -11,4 +11,5 @@
 8
 9
 10
+42
 

Don't forget to revert the change, as we are not going to need it any more:

$ hg revert --all --no-backup
reverting fileextract

Now let's take a look at the obsolescence history:

$ hg obslog -p -r .
@  76ace846a3f9 (24) Commit to be extracted
|
x  1e04751ef00a (22) Commit to be extracted
|    rewritten(content) as 76ace846a3f9 by test (Thu Jan 01 00:00:00 1970 +0000)
|      diff -r 1e04751ef00a -r 76ace846a3f9 fileextract
|      --- a/fileextract	Fri Dec 08 15:28:46 2017 +0100
|      +++ b/fileextract	Fri Dec 08 15:28:46 2017 +0100
|      @@ -11,5 +11,4 @@
|       8
|       9
|       10
|      -42
|
|
|
x  5935c1c3ad24 (21) Commit to be extracted
|    rewritten(content) as 1e04751ef00a by test (Thu Jan 01 00:00:00 1970 +0000)
|      diff -r 5935c1c3ad24 -r 1e04751ef00a badfile
|      --- a/badfile	Fri Dec 08 15:28:46 2017 +0100
|      +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
|      @@ -1,1 +0,0 @@
|      -badbadfile
|
|
x  e288d12d5e96 (8) Commit to be extracted
     rewritten(user) as 5935c1c3ad24 by test (Thu Jan 01 00:00:00 1970 +0000)

The obslog is read from bottom to top:

  • First we rewrite the user,
  • Then we extracted a whole file,
  • Then we extracted a line from a file

We have made three changes that generated three successors.

Fold

Sometimes we want to group together several consecutive changesets. Evolve has a command for that: hg fold. First, let's update to the right branch:

$ hg update fold

Three changesets change the same file, and they could be folded together. This would make a cleaner and more linear history, and hide those pesky intermediate changesets:

$ hg log -r "branch(fold)" -G -p
@  changeset:   12:966df9f031c1
|  branch:      fold
|  user:        Boris Feld <boris.feld@octobus.net>
|  date:        Fri Dec 08 16:50:38 2017 +0100
|  summary:     Really fix the test
|
|  diff -r b316dc02bddc -r 966df9f031c1 test/unit
|  --- a/test/unit	Fri Dec 08 16:50:17 2017 +0100
|  +++ b/test/unit	Fri Dec 08 16:50:38 2017 +0100
|  @@ -1,1 +1,1 @@
|  -assert 42 = 43
|  +assert 42 = 42
|
o  changeset:   11:b316dc02bddc
|  branch:      fold
|  user:        Boris Feld <boris.feld@octobus.net>
|  date:        Fri Dec 08 16:50:17 2017 +0100
|  summary:     Fix the test
|
|  diff -r 03174536bb2a -r b316dc02bddc test/unit
|  --- a/test/unit	Fri Dec 08 16:49:45 2017 +0100
|  +++ b/test/unit	Fri Dec 08 16:50:17 2017 +0100
|  @@ -1,1 +1,1 @@
|  -assert 42 = 0
|  +assert 42 = 43
|
o  changeset:   10:03174536bb2a
|  branch:      fold
~  parent:      0:d2eb2ac6a5bd
   user:        Boris Feld <boris.feld@octobus.net>
   date:        Fri Dec 08 16:49:45 2017 +0100
   summary:     add a test

   diff -r d2eb2ac6a5bd -r 03174536bb2a test/unit
   --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   +++ b/test/unit	Fri Dec 08 16:49:45 2017 +0100
   @@ -0,0 +1,1 @@
   +assert 42 = 0

We all have been in a similar situation. Let's make a nice and clean changeset with fold:

$ hg fold --from -r "branch(fold)" -m "add a test"
3 changesets folded
1 files updated, 0 files merged, 0 files removed, 0 files unresolved

That was easy!

$ hg log -r "::fold" -G
@  changeset:   25:dab6ed4b3c75
|  branch:      fold
|  tag:         tip
|  parent:      0:d2eb2ac6a5bd
|  user:        Boris Feld <boris.feld@octobus.net>
|  date:        Thu Jan 01 00:00:00 1970 +0000
|  summary:     add a test
|
o  changeset:   0:d2eb2ac6a5bd
   user:        Boris Feld <boris.feld@octobus.net>
   date:        Thu Dec 07 11:26:05 2017 +0100
   summary:     ROOT

$ hg log -r "::branch(fold)" -G --hidden
@  changeset:   25:dab6ed4b3c75
|  branch:      fold
|  tag:         tip
|  parent:      0:d2eb2ac6a5bd
|  user:        Boris Feld <boris.feld@octobus.net>
|  date:        Thu Jan 01 00:00:00 1970 +0000
|  summary:     add a test
|
| x  changeset:   12:966df9f031c1
| |  branch:      fold
| |  user:        Boris Feld <boris.feld@octobus.net>
| |  date:        Fri Dec 08 16:50:38 2017 +0100
| |  obsolete:    rewritten as 25:dab6ed4b3c75
| |  summary:     Really fix the test
| |
| x  changeset:   11:b316dc02bddc
| |  branch:      fold
| |  user:        Boris Feld <boris.feld@octobus.net>
| |  date:        Fri Dec 08 16:50:17 2017 +0100
| |  obsolete:    rewritten as 25:dab6ed4b3c75
| |  summary:     Fix the test
| |
| x  changeset:   10:03174536bb2a
|/   branch:      fold
|    parent:      0:d2eb2ac6a5bd
|    user:        Boris Feld <boris.feld@octobus.net>
|    date:        Fri Dec 08 16:49:45 2017 +0100
|    obsolete:    rewritten as 25:dab6ed4b3c75
|    summary:     add a test
|
o  changeset:   0:d2eb2ac6a5bd
   user:        Boris Feld <boris.feld@octobus.net>
   date:        Thu Dec 07 11:26:05 2017 +0100
   summary:     ROOT

Can you imagine what the graphs will looks like?

$ hg obslog -r .
@    dab6ed4b3c75 (25) add a test
|\
| \
| |\
x | |  03174536bb2a (10) add a test
 / /     rewritten(date, content) as dab6ed4b3c75 by test (Thu Jan 01 00:00:00 1970 +0000)
| |
x |  966df9f031c1 (12) Really fix the test
 /     rewritten(description, date, parent, content) as dab6ed4b3c75 by test (Thu Jan 01 00:00:00 1970 +0000)
|
x  b316dc02bddc (11) Fix the test
     rewritten(description, date, parent, content) as dab6ed4b3c75 by test (Thu Jan 01 00:00:00 1970 +0000)

Split

Sometimes you want to fold changesets together, and sometimes you want to split a changeset into several ones, because it is too big.

$ hg update split

Evolve also has a command for that, hg split:

$ hg log -r "::split" -G
@  changeset:   13:5d5029b9daed
|  branch:      split
|  parent:      0:d2eb2ac6a5bd
|  user:        Boris Feld <boris.feld@octobus.net>
|  date:        Fri Dec 08 17:33:15 2017 +0100
|  summary:     To be splitted
|
o  changeset:   0:d2eb2ac6a5bd
   user:        Boris Feld <boris.feld@octobus.net>
   date:        Thu Dec 07 11:26:05 2017 +0100
   summary:     ROOT

Split accepts a list of revisions and will interactively ask you how you want to split them:

$ hg split -r .
0 files updated, 0 files merged, 3 files removed, 0 files unresolved
adding src/A
adding src/B
adding src/C
diff --git a/src/A b/src/A
new file mode 100644
examine changes to 'src/A'? [Ynesfdaq?] Y

diff --git a/src/B b/src/B
new file mode 100644
examine changes to 'src/B'? [Ynesfdaq?] N

diff --git a/src/C b/src/C
new file mode 100644
examine changes to 'src/C'? [Ynesfdaq?] N

created new head
Done splitting? [yN] N
diff --git a/src/B b/src/B
new file mode 100644
examine changes to 'src/B'? [Ynesfdaq?] Y

diff --git a/src/C b/src/C
new file mode 100644
examine changes to 'src/C'? [Ynesfdaq?] N

Done splitting? [yN] N
diff --git a/src/C b/src/C
new file mode 100644
examine changes to 'src/C'? [Ynesfdaq?] Y

no more change to split

Now let's check the state of the repository:

$ hg log -r "::split" -G
@  changeset:   28:1b7281b1e052
|  branch:      split
|  tag:         tip
|  user:        Boris Feld <boris.feld@octobus.net>
|  date:        Thu Jan 01 00:00:00 1970 +0000
|  summary:     To be splitted
|
o  changeset:   27:6fb7bfb44ffe
|  branch:      split
|  user:        Boris Feld <boris.feld@octobus.net>
|  date:        Thu Jan 01 00:00:00 1970 +0000
|  summary:     To be splitted
|
o  changeset:   26:59f0ddc4bd4b
|  branch:      split
|  parent:      0:d2eb2ac6a5bd
|  user:        Boris Feld <boris.feld@octobus.net>
|  date:        Thu Jan 01 00:00:00 1970 +0000
|  summary:     To be splitted
|
o  changeset:   0:d2eb2ac6a5bd
   user:        Boris Feld <boris.feld@octobus.net>
   date:        Thu Dec 07 11:26:05 2017 +0100
   summary:     ROOT

It looks good. What about the obsolescence history?

$ hg obslog -r .
@  1b7281b1e052 (28) To be splitted
|
x  5d5029b9daed (13) To be splitted
     rewritten(date, parent, content) as 1b7281b1e052, 59f0ddc4bd4b, 6fb7bfb44ffe by test (Thu Jan 01 00:00:00 1970 +0000)

$ hg obslog --all -r .
@  1b7281b1e052 (28) To be splitted
|
| o  59f0ddc4bd4b (26) To be splitted
|/
| o  6fb7bfb44ffe (27) To be splitted
|/
x  5d5029b9daed (13) To be splitted
     rewritten(date, parent, content) as 1b7281b1e052, 59f0ddc4bd4b, 6fb7bfb44ffe by test (Thu Jan 01 00:00:00 1970 +0000)

Prune

After rewriting and rebasing changesets, the next common use case for history rewriting is removing a changeset.

But we can't permanently remove a changeset without leaving a trace. What if other users are working with the changeset that we want to remove?

The common solution is to mark the changeset as removed, and simulate the fact that it has been removed.

This is why the Evolve extension is offering the prune command. Let's try to prune a changeset:

$ hg update prune
$ hg log -G -r "::prune"
@  changeset:   9:324b72ebbb21
|  branch:      prune
|  parent:      0:d2eb2ac6a5bd
|  user:        Boris Feld <boris.feld@octobus.net>
|  date:        Fri Dec 08 16:12:23 2017 +0100
|  summary:     Commit to prune
|
o  changeset:   0:d2eb2ac6a5bd
   user:        Boris Feld <boris.feld@octobus.net>
   date:        Thu Dec 07 11:26:05 2017 +0100
   summary:     ROOT

prune is easy to use, just give it the revisions you want to prune:

$ hg prune -r .
0 files updated, 0 files merged, 1 files removed, 0 files unresolved
working directory now at d2eb2ac6a5bd
1 changesets pruned

Now the changeset is not visible any more:

$ hg log -G -r "::prune"
abort: unknown revision 'prune'!

But we can still access it with the --hidden option:

$ hg log -G -r "::prune" --hidden
x  changeset:   9:324b72ebbb21
|  branch:      prune
|  parent:      0:d2eb2ac6a5bd
|  user:        Boris Feld <boris.feld@octobus.net>
|  date:        Fri Dec 08 16:12:23 2017 +0100
|  obsolete:    pruned
|  summary:     Commit to prune
|
@  changeset:   0:d2eb2ac6a5bd
   user:        Boris Feld <boris.feld@octobus.net>
   date:        Thu Dec 07 11:26:05 2017 +0100
   summary:     ROOT

The output of obslog changes a bit when displaying pruned changesets:

$ hg obslog -r prune --hidden
x  324b72ebbb21 (9) Commit to prune
     pruned by test (Thu Jan 01 00:00:00 1970 +0000)

Histedit

The hg histedit command is a power-user command. It allows you to edit a linear series of changesets, and applies a combination of operations on them:

  • 'pick' to [re]order a changeset
  • 'drop' to omit changeset
  • 'mess' to reword the changeset commit message
  • 'fold' to combine it with the preceding changeset (using the later date)
  • 'roll' like fold, but discarding this commit's description and date
  • 'edit' to edit this changeset (preserving date)
  • 'base' to checkout changeset and apply further changesets from there

It's similar to the git rebase -i command.

First, let's update to the right branch:

$ hg update histedit
$ hg log -G -r "::histedit"
@  changeset:   16:1b1e58a9ed27
|  branch:      histedit
|  user:        Boris Feld <boris.feld@octobus.net>
|  date:        Sat Dec 09 17:37:15 2017 +0100
|  summary:     Add test for myfeature
|
o  changeset:   15:23eb6f9e4c51
|  branch:      histedit
|  user:        Boris Feld <boris.feld@octobus.net>
|  date:        Sat Dec 09 17:35:15 2017 +0100
|  summary:     Add code for myfeature
|
o  changeset:   14:d102c718e607
|  branch:      histedit
|  parent:      0:d2eb2ac6a5bd
|  user:        Boris Feld <boris.feld@octobus.net>
|  date:        Sat Dec 09 17:33:15 2017 +0100
|  summary:     First commit on histedit branch
|
o  changeset:   0:d2eb2ac6a5bd
   user:        Boris Feld <boris.feld@octobus.net>
   date:        Thu Dec 07 11:26:05 2017 +0100
   summary:     ROOT

When launching the hg histedit command, an editor will show up with the following contents:

$ hg histedit -r ".~1"
pick 23eb6f9e4c51 15 Add code for myfeature
pick 1b1e58a9ed27 16 Add test for myfeature

# Edit history between 23eb6f9e4c51 and 1b1e58a9ed27
#
# Commits are listed from least to most recent
#
# You can reorder changesets by reordering the lines
#
# Commands:
#
#  e, edit = use commit, but stop for amending
#  m, mess = edit commit message without changing commit content
#  p, pick = use commit
#  b, base = checkout changeset and apply further changesets from there
#  d, drop = remove commit from history
#  f, fold = use commit, but combine it with the one above
#  r, roll = like fold, but discard this commit's description and date
#

Swap the first two lines with your text editor:

pick 1b1e58a9ed27 16 Add test for myfeature
pick 23eb6f9e4c51 15 Add code for myfeature

Save and exit. Histedit will apply your instructions and finish.

Let's see the state of the repository:

$ hg log -G -r "::histedit"
@  changeset:   30:27cb89067c43
|  branch:      histedit
|  tag:         tip
|  user:        Boris Feld <boris.feld@octobus.net>
|  date:        Sat Dec 09 17:35:15 2017 +0100
|  summary:     Add code for myfeature
|
o  changeset:   29:a2082e406c4f
|  branch:      histedit
|  parent:      14:d102c718e607
|  user:        Boris Feld <boris.feld@octobus.net>
|  date:        Sat Dec 09 17:37:15 2017 +0100
|  summary:     Add test for myfeature
|
o  changeset:   14:d102c718e607
|  branch:      histedit
|  parent:      0:d2eb2ac6a5bd
|  user:        Boris Feld <boris.feld@octobus.net>
|  date:        Sat Dec 09 17:33:15 2017 +0100
|  summary:     First commit on histedit branch
|
o  changeset:   0:d2eb2ac6a5bd
   user:        Boris Feld <boris.feld@octobus.net>
   date:        Thu Dec 07 11:26:05 2017 +0100
   summary:     ROOT

$ hg log -G -r "::branch(histedit)" --hidden
@  changeset:   30:27cb89067c43
|  branch:      histedit
|  tag:         tip
|  user:        Boris Feld <boris.feld@octobus.net>
|  date:        Sat Dec 09 17:35:15 2017 +0100
|  summary:     Add code for myfeature
|
o  changeset:   29:a2082e406c4f
|  branch:      histedit
|  parent:      14:d102c718e607
|  user:        Boris Feld <boris.feld@octobus.net>
|  date:        Sat Dec 09 17:37:15 2017 +0100
|  summary:     Add test for myfeature
|
| x  changeset:   16:1b1e58a9ed27
| |  branch:      histedit
| |  user:        Boris Feld <boris.feld@octobus.net>
| |  date:        Sat Dec 09 17:37:15 2017 +0100
| |  obsolete:    rebased using histedit as 29:a2082e406c4f
| |  summary:     Add test for myfeature
| |
| x  changeset:   15:23eb6f9e4c51
|/   branch:      histedit
|    user:        Boris Feld <boris.feld@octobus.net>
|    date:        Sat Dec 09 17:35:15 2017 +0100
|    obsolete:    rebased using histedit as 30:27cb89067c43
|    summary:     Add code for myfeature
|
o  changeset:   14:d102c718e607
|  branch:      histedit
|  parent:      0:d2eb2ac6a5bd
|  user:        Boris Feld <boris.feld@octobus.net>
|  date:        Sat Dec 09 17:33:15 2017 +0100
|  summary:     First commit on histedit branch
|
o  changeset:   0:d2eb2ac6a5bd
   user:        Boris Feld <boris.feld@octobus.net>
   date:        Thu Dec 07 11:26:05 2017 +0100
   summary:     ROOT

Stack

Stack definition

One big problem when working with a DVCS to identify and switch between the different features/bugfixes you are working on.

Named branches

One solution is to use named branches. Named branches are a battle-tested, long-supported solution in Mercurial. Basically, a branch name is stored inside each changeset.

This solution has several advantages:

  • It's supported in all recent-ish Mercurial versions.
  • It's simple to use.
  • Most tools are supporting it.

But it also has several disadvantages:

  • Branches do not disappear once they are merged. You need to explicitely close them with hg commit --close-branch.
  • Branches are lost when rebasing them without the --keepbranches option of the hg rebase command.
  • New branches needs to be explicitly pushed with the --new-branch option of the hg push command.

We will use named branches for this training, but other solutions are possible, like topics.

Stack

The topic extension provides a command to show your current stack, no matter how you defined it. Let's try it on some changesets that we rewrote earlier:

$ hg update typo
$ hg stack
### target: typo (branch)
b1@ Fix bug (current)
b0^ ROOT (base)

The stack output shows three important data:

  • First, which branch you are working on (a.k.a. the current branch).
  • Then, all the commits that you are currently working on, with the current one highlighted.
  • Finally, which commit your branch is based on (b0).

This branch is not very interesting, so let's move to another one.

$ hg update build/linuxsupport-v2
$ hg stack
### target: build/linuxsupport-v2 (branch)
b3@ Third commit on build/linuxsupport-v2 (current)
b2: Second commit on build/linuxsupport-v2.
b1: First commit on build/linuxsupport-v2
b0^ New commit on build/v2 (base)

This is more interesting, as now we can see all the three changesets grouped together in the same view. The stack view provides a nice and linear view, even if the changesets are not immediate neighbors.

Stack movement

There is an easy way to navigate in your stack, the hg next and hg prev commands:

$ hg prev
0 files updated, 0 files merged, 1 files removed, 0 files unresolved
[19] Second commit on build/linuxsupport-v2.
$ hg stack
### target: build/linuxsupport-v2 (branch)
b3: Third commit on build/linuxsupport-v2
b2@ Second commit on build/linuxsupport-v2. (current)
b1: First commit on build/linuxsupport-v2
b0^ New commit on build/v2 (base)

And now for the hg next command:

$ hg next
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
[20] Third commit on build/linuxsupport-v2
$ hg stack
### target: build/linuxsupport-v2 (branch)
b3@ Third commit on build/linuxsupport-v2 (current)
b2: Second commit on build/linuxsupport-v2.
b1: First commit on build/linuxsupport-v2
b0^ New commit on build/v2 (base)

The stack view also displays nice and easy relative ids for these changesets. You can use theses ids in all commands, for example with the hg export command:

$ hg export -r b1
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1512661592 -3600
#      Thu Dec 07 16:46:32 2017 +0100
# Branch build/linuxsupport-v2
# Node ID 7b62ce2c283e6fa23af1811efea529c30620196a
# Parent  0e694460372ee8e9ca759c90f05a31f11eee34ac
First commit on build/linuxsupport-v2

Or with the hg update command:

$ hg update -r b2
0 files updated, 0 files merged, 1 files removed, 0 files unresolved

These ids are handy because you don't need to manipulate changeset ids or revision numbers: contrary to the latters, the formers won't be affected by history edition. They only depend on their order in the branch.

$ hg stack
### target: build/linuxsupport-v2 (branch)
b3: Third commit on build/linuxsupport-v2
b2@ Second commit on build/linuxsupport-v2. (current)
b1: First commit on build/linuxsupport-v2
b0^ New commit on build/v2 (base)

Edit mid-stack

Now that we are in the middle of a stack, let's try amending a commit. The current commit message ends with a dot ., and we want to remove it:

$ hg stack
### target: build/linuxsupport-v2 (branch)
b3: Third commit on build/linuxsupport-v2
b2@ Second commit on build/linuxsupport-v2. (current)
b1: First commit on build/linuxsupport-v2
b0^ New commit on build/v2 (base)
$ hg amend -m "Second commit on build/linuxsupport-v2"
1 new orphan changesets

The message 1 new orphan changesets means that, by amending a changeset having a child, this child is now unstable, as we can see with the hg stack command:

$ hg stack
### target: build/linuxsupport-v2 (branch)
b3$ Third commit on build/linuxsupport-v2 (unstable)
b2@ Second commit on build/linuxsupport-v2 (current)
b1: First commit on build/linuxsupport-v2
b0^ New commit on build/v2 (base)

hg stack tries to simplify the view for you. We have amended b2, and b3's parent is the precursor version of b2, so it is not stable any more. It is now orphan.

For once, let's use log to see in detail in which situation we are:

$ hg log -r "branch(build/linuxsupport-v2)" -G
@  changeset:   31:5c069dd03e05
|  branch:      build/linuxsupport-v2
|  tag:         tip
|  parent:      18:7b62ce2c283e
|  user:        Boris Feld <boris.feld@octobus.net>
|  date:        Mon Dec 11 11:20:24 2017 +0100
|  summary:     Second commit on build/linuxsupport-v2
|
| o  changeset:   20:3d2c8a2356a2
| |  branch:      build/linuxsupport-v2
| |  user:        Boris Feld <boris.feld@octobus.net>
| |  date:        Mon Dec 11 11:21:02 2017 +0100
| |  instability: orphan
| |  summary:     Third commit on build/linuxsupport-v2
| |
| x  changeset:   19:4686378320d7
|/   branch:      build/linuxsupport-v2
|    user:        Boris Feld <boris.feld@octobus.net>
|    date:        Mon Dec 11 11:20:24 2017 +0100
|    obsolete:    reworded using amend as 31:5c069dd03e05
|    summary:     Second commit on build/linuxsupport-v2.
|
o  changeset:   18:7b62ce2c283e
|  branch:      build/linuxsupport-v2
~  parent:      6:0e694460372e
   user:        Boris Feld <boris.feld@octobus.net>
   date:        Thu Dec 07 16:46:32 2017 +0100
   summary:     First commit on build/linuxsupport-v2

How can we resolve this situation? It is actually very easy, and we are going to see how in the next section.

Basic instabilities + stabilization

Instabilities are a normal step when using Evolve-powered workflows. Several tools are provided to fix them smoothly.

Log

First, let's clarify some vocabulary. An obsolete changeset is a changeset that has been rewritten. In the current stack, only one commit is obsolete:

$ hg log -r "branch(build/linuxsupport-v2)" -G -T "{node|short}: {obsolete}\n"
@  5c069dd03e05:
|
| o  3d2c8a2356a2:
| |
| x  4686378320d7: obsolete
|/
o  7b62ce2c283e:
|
~

A changeset can also be unstable, meaning that it could be subject to one or more instabilities:

  • orphan, a changeset whose an ancestor is obsolete.
  • content-divergent, a changeset which has been rewritten in two different versions.
  • phase-divergent, a changeset which has been both rewritten and published.

For the moment, we will only see the orphan instability. We can display the instabilities of a commit with the {instabilities} template keyword:

$ hg log -r "branch(build/linuxsupport-v2)" -G -T "{node|short}: {instabilities}\n"
@  5c069dd03e05:
|
| o  3d2c8a2356a2: orphan
| |
| x  4686378320d7:
|/
o  7b62ce2c283e:
|
~

Here we have also one orphan commit, which is the child of the obsolete commit.

Evolve --list

The hg evolve command has a --list option which can list all the instabilities of your repository.

$ hg evolve --list
3d2c8a2356a2: Third commit on build/linuxsupport-v2
  unstable: 4686378320d7 (obsolete parent)

TortoiseHG

Tortoise HG also has a nice support for displaying the instabilities of your repository:

If you want to filter to get a better view, you can use the revset branch(build/linuxsupport-v2):

Stabilization using hg next --evolve

$ hg stack
### target: build/linuxsupport-v2 (branch)
b3$ Third commit on build/linuxsupport-v2 (unstable)
b2@ Second commit on build/linuxsupport-v2 (current)
b1: First commit on build/linuxsupport-v2
b0^ New commit on build/v2 (base)

In our current situation, a simple solution to solve the instability is to use the hg next command with the --evolve option. It will update to the next changeset on the stack, and stabilize it if necessary:

$ hg next --evolve
move:[20] Third commit on build/linuxsupport-v2
atop:[31] Second commit on build/linuxsupport-v2
working directory now at 52e790f9d4c3

Here, it just rebased our old version of b3 on top of the new version of b2.

And now hg stack shows us a clean view again:

$ hg stack
### target: build/linuxsupport-v2 (branch)
b3@ Third commit on build/linuxsupport-v2 (current)
b2: Second commit on build/linuxsupport-v2
b1: First commit on build/linuxsupport-v2
b0^ New commit on build/v2 (base)

That's better!

Advanced

Moving change from one commit to another

Create two commits:

  • The first one create a new file, add some content in it.
  • The second one create another file and modify the first file.

Now try to move the change on the first file present in the second commit back in the first commit so that the first commit contains all change on the first file and the second change contains all changes on the second file.

Exchange

Coming Soon™