credit: redshirtjosh, http://www.flickr.com/photos/43273828@N06/4111258568/

On the behalf of Logilab I put a lot of efforts to include a new core feature named phases in Mercurial 2.1. Phases are a system for tracking which changesets have been or should be shared. This helps to prevent common mistakes when modifying history (for instance, with the mq or rebase extensions). It will transparently benefit to all users. This concept is the first step towards simple, safe and powerful rewritting mecanisms for history in mercurial.

This serie of three blog entries will explain:

  1. how phases will help mercurial users,
  2. how one can control them,
  3. how older mercurial versions interact with newer versions that support phases.

Preventing erroneous history rewriting

credit: anita.priks, http://www.flickr.com/photos/46785534@N06/6358218623/

History rewriting is a common practice in DVCS. However when done the wrong way the most common error results in duplicated history. The phase concept aims to make rewriting history safer. For this purpose Mercurial 2.1 introduces a distinction between the "past" part of your history (that is expected to stay there forever) and the "present" part of the history (that you are currently evolving). The old and immutable part is called public and the mutable part of your history is called draft.

Let's see how this happens using a simple scenario.


A new Mercurial user clones a repository:

babar@Chessy ~ $ hg clone http://hg.celesteville.com/palace
requesting all changes
adding changesets
adding manifests
adding file changes
added 2 changesets with 2 changes to 2 files
updating to branch default
2 files updated, 0 files merged, 0 files removed, 0 files unresolved
babar@Chessy ~/palace $ cd palace
babar@Chessy ~/palace $ hg log --graph
@  changeset:   1:2afbcfd2af83
|  tag:         tip
|  user:        Celeste the Queen <Celeste@celesteville.com>
|  date:        Wed Jan 25 16:41:56 2012 +0100
|  summary:     We need a kitchen too.
|
o  changeset:   0:898889b143fb
   user:        Celeste the Queen <Celeste@celesteville.com>
   date:        Wed Jan 25 16:39:07 2012 +0100
   summary:     First description of the throne room

The repository already contains some changesets. Our user makes some improvements and commits them:

babar@Chessy ~/palace $ echo The wall shall be Blue >> throne-room
babar@Chessy ~/palace $ hg ci -m 'Add wall color'
babar@Chessy ~/palace $ echo In the middle stands a three meters round table >> kitchen
babar@Chessy ~/palace $ hg ci -m 'Add a table in the kichen'

But when he tries to push new changesets, he discovers that someone else already pushed one:

babar@Chessy ~/palace $ hg push
pushing to http://hg.celesteville.com/palace
searching for changes
abort: push creates new remote head bcd4d53319ec!
(you should pull and merge or use push -f to force)
babar@Chessy ~/palace $ hg pull
pulling from http://hg.celesteville.com/palace
searching for changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 1 changes to 1 files (+1 heads)
(run 'hg heads' to see heads, 'hg merge' to merge)
babar@Chessy ~/palace $ hg log --graph
o  changeset:   4:0a5b3d7e4e5f
|  tag:         tip
|  parent:      1:2afbcfd2af83
|  user:        Celeste the Queen <Celeste@celesteville.com>
|  date:        Wed Jan 25 16:58:23 2012 +0100
|  summary:     Some bedroom description.
|
| @  changeset:   3:bcd4d53319ec
| |  user:        Babar the King <babar@celesteville.com>
| |  date:        Wed Jan 25 16:52:02 2012 +0100
| |  summary:     Add a table in the kichen
| |
| o  changeset:   2:f9f14815935d
|/   user:        Babar the King <babar@celesteville.com>
|    date:        Wed Jan 25 16:51:51 2012 +0100
|    summary:     Add wall color
|
o  changeset:   1:2afbcfd2af83
|  user:        Celeste the Queen <Celeste@celesteville.com>
|  date:        Wed Jan 25 16:41:56 2012 +0100
|  summary:     We need a kitchen too.
|
o  changeset:   0:898889b143fb
   user:        Celeste the Queen <Celeste@celesteville.com>
   date:        Wed Jan 25 16:39:07 2012 +0100
   summary:     First description of the throne room

Note

From here on this scenario becomes very unlikely. Mercurial is simple enough for a new user not to be that confused by such a trivial situation. But we keep the example simple to focus on phases.

Recently, our new user read some hype blog about "rebase" and the benefit of linear history. So, he decides to rewrite his history instead of merging.

Despite reading the wonderful rebase help, our new user makes the wrong decision when it comes to using it. He decides to rebase the remote changeset 0a5b3d7e4e5f:"Some bedroom description." on top of his local changeset.

With previous versions of mercurial, this mistake was allowed and would result in a duplication of the changeset 0a5b3d7e4e5f:"Some bedroom description."

babar@Chessy ~/palace $ hg rebase -s 4 -d 3
babar@Chessy ~/palace $ hg push
pushing to http://hg.celesteville.com/palace
searching for changes
abort: push creates new remote head bcd4d53319ec!
(you should pull and merge or use push -f to force)
babar@Chessy ~/palace $ hg pull
pulling from http://hg.celesteville.com/palace
searching for changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 1 changes to 1 files (+1 heads)
(run 'hg heads' to see heads, 'hg merge' to merge)
babar@Chessy ~/palace $ hg log --graph
@  changeset:   5:55d9bae1e1cb
|  tag:         tip
|  parent:      3:bcd4d53319ec
|  user:        Celeste the Queen <Celeste@celesteville.com>
|  date:        Wed Jan 25 16:58:23 2012 +0100
|  summary:     Some bedroom description.
|
| o  changeset:   4:0a5b3d7e4e5f
| |  parent:      1:2afbcfd2af83
| |  user:        Celeste the Queen <Celeste@celesteville.com>
| |  date:        Wed Jan 25 16:58:23 2012 +0100
| |  summary:     Some bedroom description.
| |
o |  changeset:   3:bcd4d53319ec
| |  user:        Babar the King <babar@celesteville.com>
| |  date:        Wed Jan 25 16:52:02 2012 +0100
| |  summary:     Add a table in the kichen
| |
o |  changeset:   2:f9f14815935d
|/   user:        Babar the King <babar@celesteville.com>
|    date:        Wed Jan 25 16:51:51 2012 +0100
|    summary:     Add wall color
|
o  changeset:   1:2afbcfd2af83
|  user:        Celeste the Queen <Celeste@celesteville.com>
|  date:        Wed Jan 25 16:41:56 2012 +0100
|  summary:     We need a kitchen too.
|
o  changeset:   0:898889b143fb
   user:        Celeste the Queen <Celeste@celesteville.com>
   date:        Wed Jan 25 16:39:07 2012 +0100
   summary:     First description of the throne room

In more complicated setups it's a fairly common mistake, Even in big and successful projects and with other DVCSs.

In the new Mercurial version the user won't be able to make this mistake anymore. Trying to rebase the wrong way will result in:

babar@Chessy ~/palace $ hg rebase -s 4 -d 3
abort: can't rebase immutable changeset 0a5b3d7e4e5f
(see hg help phases for details)

The correct rebase still works as expected:

babar@Chessy ~/palace $ hg rebase -s 2 -d 4
babar@Chessy ~/palace $ hg log --graph
@  changeset:   4:139ead8a540f
|  tag:         tip
|  user:        Babar the King <babar@celesteville.com>
|  date:        Wed Jan 25 16:52:02 2012 +0100
|  summary:     Add a table in the kichen
|
o  changeset:   3:0d1feb1bca54
|  user:        Babar the King <babar@celesteville.com>
|  date:        Wed Jan 25 16:51:51 2012 +0100
|  summary:     Add wall color
|
o  changeset:   2:0a5b3d7e4e5f
|  user:        Celeste the Queen <Celeste@celesteville.com>
|  date:        Wed Jan 25 16:58:23 2012 +0100
|  summary:     Some bedroom description.
|
o  changeset:   1:2afbcfd2af83
|  user:        Celeste the Queen <Celeste@celesteville.com>
|  date:        Wed Jan 25 16:41:56 2012 +0100
|  summary:     We need a kitchen too.
|
o  changeset:   0:898889b143fb
   user:        Celeste the Queen <Celeste@celesteville.com>
   date:        Wed Jan 25 16:39:07 2012 +0100
   summary:     First description of the throne room

What is happening here:

  • Changeset 0a5b3d7e4e5f from Celeste was set to the public phase because it was pulled from the outside. The public phase is immutable.
  • Changesets f9f14815935d and bcd4d53319ec (rebased as 0d1feb1bca54 and 139ead8a540f) have been commited locally and haven't been transmitted from this repository to another. As such, they are still in the draft phase. Unlike the public phase, the draft phase is mutable.

Let's watch the whole action in slow motion, paying attention to phases:

babar@Chessy ~ $ cat >> ~/.hgrc << EOF
[ui]
username=Babar the King <babar@celesteville.com>
logtemplate='[{phase}] {desc} ({node|short})\\n'
EOF

First, changesets cloned from a public server are public:

babar@Chessy ~ $ hg clone --quiet http://hg.celesteville.com/palace
babar@Chessy ~/palace $ cd palace
babar@Chessy ~/palace $ hg log --graph
@  [public] We need a kitchen too. (2afbcfd2af83)
|
o  [public] First description of the throne room (898889b143fb)

Second, new changesets committed locally are in the draft phase:

babar@Chessy ~/palace $ echo The wall shall be Blue >> throne-room
babar@Chessy ~/palace $ hg ci -m 'Add wall color'
babar@Chessy ~/palace $ echo In the middle stand a three meters round table >> kitchen
babar@Chessy ~/palace $ hg ci -m 'Add a table in the kichen'
babar@Chessy ~/palace $ hg log --graph
@  [draft] Add a table in the kichen (bcd4d53319ec)
|
o  [draft] Add wall color (f9f14815935d)
|
o  [public] We need a kitchen too. (2afbcfd2af83)
|
o  [public] First description of the throne room (898889b143fb)

Third, changesets pulled from a public server are public:

babar@Chessy ~/palace $ hg pull --quiet
babar@Chessy ~/palace $ hg log --graph
o  [public] Some bedroom description. (0a5b3d7e4e5f)
|
| @  [draft] Add a table in the kichen (bcd4d53319ec)
| |
| o  [draft] Add wall color (f9f14815935d)
|/
o  [public] We need a kitchen too. (2afbcfd2af83)
|
o  [public] First description of the throne room (898889b143fb)

Note

rebase preserves the phase of rebased changesets

babar@Chessy ~/palace $ hg rebase -s 2 -d 4
babar@Chessy ~/palace $ hg log --graph
@  [draft] Add a table in the kichen (139ead8a540f)
|
o  [draft] Add wall color (0d1feb1bca54)
|
o  [public] Some bedroom description. (0a5b3d7e4e5f)
|
o  [public] We need a kitchen too. (2afbcfd2af83)
|
o  [public] First description of the throne room (898889b143fb)

Finally, once pushed to the public server, changesets are set to the public (immutable) phase

babar@Chessy ~/palace $ hg push
pushing to http://hg.celesteville.com/palace
searching for changes
adding changesets
adding manifests
adding file changes
added 2 changesets with 2 changes to 2 files
babar@Chessy ~/palace $ hg log --graph

@  [public] Add a table in the kichen (139ead8a540f)
|
o  [public] Add wall color (0d1feb1bca54)
|
o  [public] Some bedroom description. (0a5b3d7e4e5f)
|
o  [public] We need a kitchen too. (2afbcfd2af83)
|
o  [public] First description of the throne room (898889b143fb)

To summarize:

  • Changesets exchanged with the outside are public and immutable.
  • Changesets committed locally are draft until exchanged with the outside.
  • As a user, you should not worry about phases. Phases move transparently.

Preventing premature exchange of history

credit: Richard Elzey, http://www.flickr.com/photos/elzey/3516256055/

The public phases prevent user from accidentally rewriting public history. It's a good step forward but phases can go further. Phases can prevent you from accidentally making history public in the first place.

For this purpose, a third phase is available, the secret phase. To explain it, I'll use the mq extension which is nicely integrated with this secret phase:

Our fellow user enables the mq extension

babar@Chessy ~/palace $ vim ~/.hgrc
babar@Chessy ~/palace $ cat ~/.hgrc
[ui]
username=Babar the King <babar@celesteville.com>
[extensions]
# enable the mq extension included with Mercurial
hgext.mq=
[mq]
# Enable secret phase integration.
# This integration is off by default for backward compatibility.
secret=true

New patches (not general commits) are now created as secret

babar@Chessy ~/palace $ echo A red carpet on the floor. >> throne-room
babar@Chessy ~/palace $ hg qnew -m 'add a carpet' carpet.diff
babar@Chessy ~/palace $ hg log --graph

@  [secret] add a carpet (3c1b19d5d3f5)
|
@  [public] Add a table in the kichen (139ead8a540f)
|
o  [public] Add wall color (0d1feb1bca54)
|

this secret changeset is excluded from outgoing and push:

babar@Chessy ~/palace $ hg outgoing
comparing with http://hg.celesteville.com/palace
searching for changes
no changes found (ignored 1 secret changesets)
babar@Chessy ~/palace $ hg push
pushing to http://hg.celesteville.com/palace
searching for changes
no changes found (ignored 1 secret changesets)

And other users do not see it:

celeste@Chessy ~/palace $ hg incoming ~babar/palace/
comparing with ~babar/palace
searching for changes
[public] Add wall color (0d1feb1bca54)
[public] Add a table in the kichen (139ead8a540f)

The mq integration take care of phase movement for the user. Changeset are made draft by qfinish

babar@Chessy ~/palace $ hg qfinish .
babar@Chessy ~/palace $ hg log --graph
@  [draft] add a carpet (2afbcfd2af83)
|
o  [public] Add a table in the kichen (139ead8a540f)
|
o  [public] Add wall color (0d1feb1bca54)
|

And changesets are made secret again by qimport

babar@Chessy ~/palace $ hg qimport -r 2afbcfd2af83
babar@Chessy ~/palace $ hg log --graph
@  [secret] add a carpet (2afbcfd2af83)
|
o  [public] Add a table in the kichen (139ead8a540f)
|
o  [public] Add wall color (0d1feb1bca54)
|

As expected, mq refuses to qimport public changesets

babar@Chessy ~/palace $ hg qimport -r 139ead8a540f
abort: revision 4 is not mutable

In the next part I'll details how to control phases movement.

blog entry of

Logilab.org - en