Chapter 3
A tour of Mercurial: merging work

We’ve now covered cloning a repository, making changes in a repository, and pulling or pushing changes from one repository into another. Our next step is merging changes from separate repositories.

3.1 Merging streams of work

Merging is a fundamental part of working with a distributed revision control tool.

Because merging is such a common thing to need to do, Mercurial makes it easy. Let’s walk through the process. We’ll begin by cloning yet another repository (see how often they spring up?) and making a change in it.

1  $ cd ..
2  $ hg clone hello my-new-hello
3  updating working directory
4  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
5  $ cd my-new-hello
6  $ sed -i '/printf/i∖∖tprintf("once more, hello.∖∖n");' hello.c
7  $ hg commit -m 'A new hello for a new day.'

We should now have two copies of hello.c with different contents. The histories of the two repositories have also diverged, as illustrated in figure 3.1.

1  $ cat hello.c
2  /⋆
3   ⋆ Placed in the public domain by Bryan O'Sullivan.  This program is
4   ⋆ not covered by patents in the United States or other countries.
5   ⋆/
6  
7  #include <stdio.h>
8  
9  int main(int argc, char ⋆⋆argv)
10  {
11   printf("once more, hello.n");
12   printf("hello, world!");
13   return 0;
14  }
15  $ cat ../my-hello/hello.c
16  /⋆
17   ⋆ Placed in the public domain by Bryan O'Sullivan.  This program is
18   ⋆ not covered by patents in the United States or other countries.
19   ⋆/
20  
21  #include <stdio.h>
22  
23  int main(int argc, char ⋆⋆argv)
24  {
25   printf("hello, world!");
26   printf("hello again!n");
27   return 0;
28  }

PIC

Figure 3.1: Divergent recent histories of the my-hello and my-new-hello repositories

We already know that pulling changes from our my-hello repository will have no effect on the working directory.

1  $ hg pull ../my-hello
2  pulling from ../my-hello
3  searching for changes
4  adding changesets
5  adding manifests
6  adding file changes
7  added 1 changesets with 1 changes to 1 files (+1 heads)
8  (run 'hg heads' to see heads, 'hg merge' to merge)

However, the hg pull” command says something about “heads”.

3.1.1 Head changesets

A head is a change that has no descendants, or children, as they’re also known. The tip revision is thus a head, because the newest revision in a repository doesn’t have any children, but a repository can contain more than one head.


PIC

Figure 3.2: Repository contents after pulling from my-hello into my-new-hello

In figure 3.2, you can see the effect of the pull from my-hello into my-new-hello. The history that was already present in my-new-hello is untouched, but a new revision has been added. By referring to figure 3.1, we can see that the changeset ID remains the same in the new repository, but the revision number has changed. (This, incidentally, is a fine example of why it’s not safe to use revision numbers when discussing changesets.) We can view the heads in a repository using the hg heads” command.

1  $ hg heads
2  changeset:   6:9f2d6836accf
3  tag:         tip
4  parent:      4:2278160e78d4
5  user:        Bryan O'Sullivan <bos@serpentine.com>
6  date:        Thu Aug 21 18:22:27 2008 +0000
7  summary:     Added an extra line of output
8  
9  changeset:   5:b332ec0ddfba
10  user:        Bryan O'Sullivan <bos@serpentine.com>
11  date:        Thu Aug 21 18:22:29 2008 +0000
12  summary:     A new hello for a new day.
13  

3.1.2 Performing the merge

What happens if we try to use the normal hg update” command to update to the new tip?

1  $ hg update
2  abort: crosses branches (use 'hg merge' or 'hg update -C')

Mercurial is telling us that the hg update” command won’t do a merge; it won’t update the working directory when it thinks we might be wanting to do a merge, unless we force it to do so. Instead, we use the hg merge” command to merge the two heads.

1  $ hg merge
2  merging hello.c
3  0 files updated, 1 files merged, 0 files removed, 0 files unresolved
4  (branch merge, don't forget to commit)

PIC

Figure 3.3: Working directory and repository during merge, and following commit

This updates the working directory so that it contains changes from both heads, which is reflected in both the output of hg parents” and the contents of hello.c.

1  $ hg parents
2  changeset:   5:b332ec0ddfba
3  user:        Bryan O'Sullivan <bos@serpentine.com>
4  date:        Thu Aug 21 18:22:29 2008 +0000
5  summary:     A new hello for a new day.
6  
7  changeset:   6:9f2d6836accf
8  tag:         tip
9  parent:      4:2278160e78d4
10  user:        Bryan O'Sullivan <bos@serpentine.com>
11  date:        Thu Aug 21 18:22:27 2008 +0000
12  summary:     Added an extra line of output
13  
14  $ cat hello.c
15  /⋆
16   ⋆ Placed in the public domain by Bryan O'Sullivan.  This program is
17   ⋆ not covered by patents in the United States or other countries.
18   ⋆/
19  
20  #include <stdio.h>
21  
22  int main(int argc, char ⋆⋆argv)
23  {
24   printf("once more, hello.n");
25   printf("hello, world!");
26   printf("hello again!n");
27   return 0;
28  }

3.1.3 Committing the results of the merge

Whenever we’ve done a merge, hg parents” will display two parents until we hg commit” the results of the merge.

1  $ hg commit -m 'Merged changes'

We now have a new tip revision; notice that it has both of our former heads as its parents. These are the same revisions that were previously displayed by hg parents”.

1  $ hg tip
2  changeset:   7:c35a687200ec
3  tag:         tip
4  parent:      5:b332ec0ddfba
5  parent:      6:9f2d6836accf
6  user:        Bryan O'Sullivan <bos@serpentine.com>
7  date:        Thu Aug 21 18:22:29 2008 +0000
8  summary:     Merged changes
9  

In figure 3.3, you can see a representation of what happens to the working directory during the merge, and how this affects the repository when the commit happens. During the merge, the working directory has two parent changesets, and these become the parents of the new changeset.

3.2 Merging conflicting changes

Most merges are simple affairs, but sometimes you’ll find yourself merging changes where each modifies the same portions of the same files. Unless both modifications are identical, this results in a conflict, where you have to decide how to reconcile the different changes into something coherent.


PIC

Figure 3.4: Conflicting changes to a document

Figure 3.4 illustrates an instance of two conflicting changes to a document. We started with a single version of the file; then we made some changes; while someone else made different changes to the same text. Our task in resolving the conflicting changes is to decide what the file should look like.

Mercurial doesn’t have a built-in facility for handling conflicts. Instead, it runs an external program called hgmerge. This is a shell script that is bundled with Mercurial; you can change it to behave however you please. What it does by default is try to find one of several different merging tools that are likely to be installed on your system. It first tries a few fully automatic merging tools; if these don’t succeed (because the resolution process requires human guidance) or aren’t present, the script tries a few different graphical merging tools.

It’s also possible to get Mercurial to run another program or script instead of hgmerge, by setting the HGMERGE environment variable to the name of your preferred program.

3.2.1 Using a graphical merge tool

My preferred graphical merge tool is kdiff3, which I’ll use to describe the features that are common to graphical file merging tools. You can see a screenshot of kdiff3 in action in figure 3.5. The kind of merge it is performing is called a three-way merge, because there are three different versions of the file of interest to us. The tool thus splits the upper portion of the window into three panes:

In the pane below these is the current result of the merge. Our task is to replace all of the red text, which indicates unresolved conflicts, with some sensible merger of the “ours” and “theirs” versions of the file.

All four of these panes are locked together; if we scroll vertically or horizontally in any of them, the others are updated to display the corresponding sections of their respective files.


PIC

Figure 3.5: Using kdiff3 to merge versions of a file

For each conflicting portion of the file, we can choose to resolve the conflict using some combination of text from the base version, ours, or theirs. We can also manually edit the merged file at any time, in case we need to make further modifications.

There are many file merging tools available, too many to cover here. They vary in which platforms they are available for, and in their particular strengths and weaknesses. Most are tuned for merging files containing plain text, while a few are aimed at specialised file formats (generally XML).

3.2.2 A worked example

In this example, we will reproduce the file modification history of figure 3.4 above. Let’s begin by creating a repository with a base version of our document.

1  $ cat > letter.txt <<EOF
2  > Greetings!
3  > I am Mariam Abacha, the wife of former
4  > Nigerian dictator Sani Abacha.
5  > EOF
6  $ hg add letter.txt
7  $ hg commit -m '419 scam, first draft'

We’ll clone the repository and make a change to the file.

1  $ cd ..
2  $ hg clone scam scam-cousin
3  updating working directory
4  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
5  $ cd scam-cousin
6  $ cat > letter.txt <<EOF
7  > Greetings!
8  > I am Shehu Musa Abacha, cousin to the former
9  > Nigerian dictator Sani Abacha.
10  > EOF
11  $ hg commit -m '419 scam, with cousin'

And another clone, to simulate someone else making a change to the file. (This hints at the idea that it’s not all that unusual to merge with yourself when you isolate tasks in separate repositories, and indeed to find and resolve conflicts while doing so.)

1  $ cd ..
2  $ hg clone scam scam-son
3  updating working directory
4  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
5  $ cd scam-son
6  $ cat > letter.txt <<EOF
7  > Greetings!
8  > I am Alhaji Abba Abacha, son of the former
9  > Nigerian dictator Sani Abacha.
10  > EOF
11  $ hg commit -m '419 scam, with son'

Having created two different versions of the file, we’ll set up an environment suitable for running our merge.

1  $ cd ..
2  $ hg clone scam-cousin scam-merge
3  updating working directory
4  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
5  $ cd scam-merge
6  $ hg pull -u ../scam-son
7  pulling from ../scam-son
8  searching for changes
9  adding changesets
10  adding manifests
11  adding file changes
12  added 1 changesets with 1 changes to 1 files (+1 heads)
13  not updating, since new heads added
14  (run 'hg heads' to see heads, 'hg merge' to merge)

In this example, I won’t use Mercurial’s normal hgmerge program to do the merge, because it would drop my nice automated example-running tool into a graphical user interface. Instead, I’ll set HGMERGE to tell Mercurial to use the non-interactive merge command. This is bundled with many Unix-like systems. If you’re following this example on your computer, don’t bother setting HGMERGE.

1  $ export HGMERGE=merge
2  $ hg merge
3  merging letter.txt
4  merge: warning: conflicts during merge
5  merging letter.txt failed!
6  0 files updated, 0 files merged, 0 files removed, 1 files unresolved
7  use 'hg resolve' to retry unresolved file merges
8  $ cat letter.txt
9  Greetings!
10  <<<<<<< /tmp/tour-merge-conflictyaYI6c/scam-merge/letter.txt
11  I am Shehu Musa Abacha, cousin to the former
12  =======
13  I am Alhaji Abba Abacha, son of the former
14  >>>>>>> /tmp/letter.txt~other.n_yttm
15  Nigerian dictator Sani Abacha.

Because merge can’t resolve the conflicting changes, it leaves merge markers inside the file that has conflicts, indicating which lines have conflicts, and whether they came from our version of the file or theirs.

Mercurial can tell from the way merge exits that it wasn’t able to merge successfully, so it tells us what commands we’ll need to run if we want to redo the merging operation. This could be useful if, for example, we were running a graphical merge tool and quit because we were confused or realised we had made a mistake.

If automatic or manual merges fail, there’s nothing to prevent us from “fixing up” the affected files ourselves, and committing the results of our merge:

1  $ cat > letter.txt <<EOF
2  > Greetings!
3  > I am Bryan O'Sullivan, no relation of the former
4  > Nigerian dictator Sani Abacha.
5  > EOF
6  $ hg resolve -m letter.txt
7  $ hg commit -m 'Send me your money'
8  $ hg tip
9  changeset:   3:b18b296054b6
10  tag:         tip
11  parent:      1:53ce87220c6a
12  parent:      2:84ea9064200c
13  user:        Bryan O'Sullivan <bos@serpentine.com>
14  date:        Thu Aug 21 18:22:28 2008 +0000
15  summary:     Send me your money
16  

3.3 Simplifying the pull-merge-commit sequence

The process of merging changes as outlined above is straightforward, but requires running three commands in sequence.

1  hg pull
2  hg merge
3  hg commit -m 'Merged remote changes'

In the case of the final commit, you also need to enter a commit message, which is almost always going to be a piece of uninteresting “boilerplate” text.

It would be nice to reduce the number of steps needed, if this were possible. Indeed, Mercurial is distributed with an extension called fetch that does just this.

Mercurial provides a flexible extension mechanism that lets people extend its functionality, while keeping the core of Mercurial small and easy to deal with. Some extensions add new commands that you can use from the command line, while others work “behind the scenes,” for example adding capabilities to the server.

The fetch extension adds a new command called, not surprisingly, hg fetch”. This extension acts as a combination of hg pull”, hg update” and hg merge”. It begins by pulling changes from another repository into the current repository. If it finds that the changes added a new head to the repository, it begins a merge, then commits the result of the merge with an automatically-generated commit message. If no new heads were added, it updates the working directory to the new tip changeset.

Enabling the fetch extension is easy. Edit your .hgrc, and either go to the [extensions] section or create an [extensions] section. Then add a line that simply reads “fetch ”.

1  [extensions]
2  fetch =

(Normally, on the right-hand side of the “=” would appear the location of the extension, but since the fetch extension is in the standard distribution, Mercurial knows where to search for it.)