Using Darcs with p4merge

p4merge is a really great tool for doing visual three-way merges. In my experience, this sort of program is a far less error-prone way to merge conflicts than the CVS-style insertion of conflict markers.

The p4merge program is available at no cost, although it is not open source. You can download it here

To use p4merge with darcs on a mac, download p4merge and install it in the default location (/Applications/p4merge.app). Then, create the file ~/.darcs/defaults and add this line to it:

ALL external-merge /Applications/p4merge.app/Contents/MacOS/p4merge %a %1 %2 %o

Now, let's try merging a conflict. First, we need to create some test repositories:

mkdir ancestor
cd ancestor
darcs init
echo -e '1\n2\n3\n' > test
darcs add test
darcs record -am 'init'

The lines above create a repository in ancestor containing a file test with three lines, numbered 1 to 3.

Next, we will want to create two branches of this ancestor.

cd ..
darcs get --repo-name=local  ancestor
darcs get --repo-name=remote ancestor

Now, we will make a conflicting change on each of the branches, by changing the second line of the file:

cd remote
echo -e '1\nremote\n3\n' > test
darcs record -am 'patch in remote'

cd ../local
echo -e '1\nlocal\n3\n' > test
darcs record -am 'patch in local'

Now, for the merge. We will attempt to pull the conflicting patch from remote into local:

darcs pull -a ../remote

Darcs will print the following back at us and launch p4merge.

We have conflicts in the following files:
./test
Merging file ./test by hand.
Running command
   '/Applications/p4merge.app/Contents/MacOS/p4merge
     /private/tmp/ancestor/./test
     /private/tmp/version1/./test
     /private/tmp/version2/./test
     /private/tmp/merged/./test'

You should see something like this:

The p4merge program shows you the local repository (top left pane), remote repository (top right pane), and common ancestor (top center pane). In the bottom pane is the currently chosen merging of the three files. The p4merge program will start up with its best guess at the correct merge, which usually involves simply including the union of all three of the top panes.

A few things to note:

  1. If you quit p4merge without saving, the effect will be a merger patch which undoes both the local and remote changes. That is, you will wind up with a repository which contains both patches, as well as a merger patch which undoes the changes from both of the conflicting patches.

  2. If you “save” the merge and quit p4merge, you will wind up with the same situation as above (both patches, plus a merger which undoes both), but additionally your local repository will be left in a state matching the bottom pane of the merge window. This modification is unrecorded; it is then up to you to darcs record it.

  3. The darcs process will remain running in the background while we deal with p4merge. If you go to that window and kill darcs (with Control-C), the operation which led to the conflict will be aborted. In this case, you will wind up with the repository in the same state it was in before executing the darcs pull. This is often quite useful.

So, now we know how to judge the effect of quitting/saving/killing on the final merge state. Typically we will want to adjust the contents of the bottom pane, save, and quit (option 2 above).

To modify the contents of the bottom pane, you will want to click and shift-click on the three colored icons next to each conflict. Each colored icon represents the state of one of the three files being merged. Confusingly, however, they do not appear in the same order as the files in the top three panes do – note that the position of the yellow (ancestor) and purple (local) icons is swapped.

By clicking on one of the three colored icons, you cause that conflict to be resolved by discarding the other two files' changes and accepting the changes in the chosen file. For example, if we click the purple button, we get only the local changes:

By clicking that button again, we get none of the three files – that is, we get only the lines which are common to all three merge inputs:

Finally, by shift-clicking, we can cause the contents of more than one file to be unioned together to create the merged file. For example, here we have chosen to keep the ancestor line (2) which was deleted, and to also include the lines added by the remote repository, none, discarding the local changes:

When you have the merged file in the state you want, hit save (Command-S) and quit (Command-Q).

If we type darcs changes, we will see the following:

Thu Nov 22 11:42:23 PST 2007  adam@megacz.com
  * patch in remote

Thu Nov 22 11:42:23 PST 2007  adam@megacz.com
  * patch in local

Thu Nov 22 11:42:15 PST 2007  adam@megacz.com
  * init

So clearly we got both patches. If we examine the “patch in remote” patch more closely, however:

darcs changes --patches='patch in remote' -v
Thu Nov 22 11:42:23 PST 2007  adam@megacz.com
  * patch in remote

    merger 0.0 (
    hunk ./test 2
    -2
    +local
    hunk ./test 2
    -2
    +remote
    )

We can see that this patch includes not just patch in remote, but also contains a merger indicating that two conflicting patches were harmonized.

If we do a darcs what, we will find that:

darcs what
{
hunk ./test 3
+remote
}

The current state of the working copy reflects the difference between the common ancestor and the desired merge. This means that:

  1. If we darcs record, we will create a patch which depends on the merger and which leaves the repository in the state shown by the desired merge.

  2. If we darcs revert, we will wind up with a repository containing both conflicting patches, and containing a merger which undoes the changes embodied by both conflicting patches. This is generally not what we want.

That's about it. Enjoy!