About Me

My photo
DC, United States
Ph D candidate at Princeton University, visiting graduate student at the University of Maryland

Friday, April 29, 2011

Using Emacs ediff as a graphical merge tool for Subversion

Version Control with SubversionRecently, we upgraded our Subversion repository from 1.4 to 1.6. Subversion 1.5 and 1.6 introduce a lot of badly needed features for managing branches, better merge support, and conflict resolution. The current version of Version Control with Subversion details many of the improvements that svn 1.5 has introduced, and how to use them. 

One feature which caught my attention was the interactive conflict resolution, particularly the ability to open a graphical merge tool and fix the syntactic conflicts in the file. At home, I use Ubuntu and started playing with all manner of shiny GUI tools; both Subversion GUI front ends and graphical merge tools. Of these, I was immediately impressed by RabbitVCS, RappidSVN and Meld, all of which were available through the standard Ubuntu repositories.

Now, I am quite comfortable with Subversion on the command line, so you might be wondering why I decided to start poking my nose around for graphical clients. I was inspired to look at GUI svn clients and merge tools because a new lab mate of mine said she had worked with TortoiseSVN on windoze in the past, and that the command line Subversion client intimidated her. So, having discovered these tools which, I thought seemed common enough and pretty neat for GUI tools, I started looking into installing them on our 64 bit RHEL machines in the lab. I don't have administrative privileges, but via some NFS magic from our sysadmins I can install packages from source to an NFS mounted directory that will be available on all the lab machines.

After a nontrivial prerequisite rundown, I determined that the above mentioned packages would require me to build an absurd number of prerequisites from source. This, I decided, would be prohibitively painful and a waste of my time.

Just then, 'Aha!' It hit me: Emacs already has a sufficiently advanced interface to most popular version control systems, including Subversion, and has really powerful diff and merge tools which I could use as my fancy graphical merge client. Now the interface with Subversion works off the shelf and I need only configure subversion to use the powerful Emacs merge tools. This can be done via the merge-tool-cmd option in your ~/.subversion/config file or via the SVN_MERGE environment variable.

Now, you'll also need a wrapper script because there is no way to configure subversion to pass command line switches that take arguments to the merge tool. Subversion calls the specified merge tool as: <merge-tool> base theirs mine target where base is the base revision, theirs is the new file pulled from the repository, mine is your working copy, and target is the target file on which the svn update or svn merge command was run. Now, as mentioned before, you may need to specify command line options which take arguments, or specify the files in a different order, or specify the files as command line option arguments, as is the case when using Emacs as your merge tool. Following Version Control with Subversion, I wrote my wrapper script in python. The script is listed below:

As you can see, I am calling the ediff-merge-files-with-ancestor Emacs lisp function as I fire up Emacs, and to this function I am passing the four file names provided by subversion. There are a few subtleties here.  The first is that I am passing the -Q flag (often -q in Emacs < 23.x) which disables my startup file from being evaluated. If you want emacs to evaluate your init file on startup remove this flag from the cmd list. The next subtlety is that I am executing Emacs through the os.execvp function which uses your PATH variable to find the executable. The first argument of this function is the executable I want to run and the second argument is a list of command line options where the first item in the list is the executable name. An alternative to this would be to use the os.shell method, but if you do this you need to be careful because of shell word splitting and special characters. Using  os.execvp I can pass the arguments as strings directly to the program I am calling. (Make sure you don't have any errant spaces though, since there is no word splitting to consume them before passing the arguments to the executable file.) As you can see, the key element of the above script is the construction of the argument that follows the --eval option. This causes Emacs to fire up it's powerful ediff-merge-files-with-ancestor tool on the offending file(s) in conflict. This function's signature is:

It is apparent that the order of the arguments passed to the wrapper script from Subversion need rearranging, and note that since the last two arguments are optional, nil is passed as the startup-hooks actual argument so we may specify the merge-buffer-file argument.

Below is an action shot of the end result. If you are new to ediff make sure that the ediff Emacs frame has focus and hit '?' for a quick help menu, or 'E' to read the ediff manual.