Boris Feld
octobus.net
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
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 =
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
In this section, we are going to learn how to do basic history rewriting like rewriting a changeset or rebasing.
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?
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:
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
.
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.
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.
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:
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 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:
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:
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:
-e
/--edit
option edits the commit message in an editor, which is not opened by default any more.-U
/--current-user
and -D
/--current-date
options.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.
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:
We have made three changes that generated three successors.
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)
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)
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)
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:
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
One big problem when working with a DVCS to identify and switch between the different features/bugfixes you are working on.
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:
But it also has several disadvantages:
hg commit --close-branch
.--keepbranches
option of the hg rebase
command.--new-branch
option of the hg push
command.We will use named branches for this training, but other solutions are possible, like topics.
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:
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.
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)
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.
Instabilities are a normal step when using Evolve-powered workflows. Several tools are provided to fix them smoothly.
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:
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.
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)
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)
:
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!
Create two commits:
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.
Coming Soon™