Friday, January 29, 2010

Using meld as your external merge tool for subversion (svn)

updated 2/19/2010
 
I don't know about you, but I am not a fan of SVN's built-in merge tool. Nor do I much care for GNU diff3, which subversion's tool seems to mimic. The diffs are presented inline in a simple text editor. It's simple, it works, but it's not exactly user-friendly.

So maybe you've used Meld. Meld provides a great, easy-to-use GUI for doing merges. If you're familiar with it, then you know what I'm talking about. If you're not, check it out Meld's sourceforge page, which includes screenshots. The key for me is the side-by-side view, but there are other great features like single clicks to move changes between files, syntax highlighting, and line numbering, to name a few.

So the question is, how can I use Meld to manage all my svn merge conflicts? Luckily, subversion allows you to call upon an external editor, as they explain in the subversion book (Using External Differencing and Merge Tools in Chapter 7).

Despite the examples provided in the subversion book, I wasn't quite sure how to get this to work. I was even more confused after following some examples I found on the web, not understanding the problems that arose. So that's why I'm writing this blog entry. I hope it helps.

Hit the jump for the complete solution:


Subversion gives you two ways to specify an external command to svn merge: via the --diff3-cmd run-time argument or through the merge-tool-cmd setting in your custom config file (~/.subversion/config). The former will work for that one call, the latter for all calls to svn merge. Since I prefer to use meld everytime, I use the config file approach. This is also the only method I have tried, so I can't guarantee it works with the command line argument.

Now, as I noted above, the examples I found on the web (having Googled for varitions of "use meld for svn merge") led me astray. They seemed to rely upon using Meld as the diff3 replacement, which may NOT be what you want. Subversion only calls the merge-tool command when there is a real conflict to resolve. Conversely, the diff3 command always gets called to determine the differences between files and identify conflicts

I have found that subversion's default diff3 utility does a good job identifying conflicts and a fine job of resolving trivial merges (i.e. no conflicting changes). This will save you work! Personally, I only want to call upon meld when I have conflicts to resolve. So, I only use it as my merge-tool-cmd.

So, let's open up that config file and set to work. First thing you'll notice is that the subversion developers have left plenty of helpful comments. In particular:

~/.subversion/config:
### Set merge-tool-cmd to the command used to invoke your external
### merging tool of choice. Subversion will pass 4 arguments to
### the specified command: base theirs mine merged
merge-tool-cmd = merge-tool-cmd = /home/user/scripts/svn-merge-meld

So this should be pretty straightforward. When svn merge finds a conflict, it will run the command specified, passing in the 4 arguments listed. Only one hitch: Meld can take at most 3 arguments. This isn't a big deal, though, because as the book recommends, we simply have to write a wrapper script.

Following the example set in the subversion book, I opted to write my script in python, which I'm loving more the more I use it.

/home/user/svn-diff3-meld:
#!/usr/bin/env python
# svn merge-tool python wrapper for meld
import os, sys
import subprocess
import shutil

try:
   # path to meld
   meld = "/usr/bin/meld"

   # file paths
   base   = sys.argv[-4]
   theirs = sys.argv[-3]
   mine   = sys.argv[-2]
   merged = sys.argv[-1]

   # Replace 'merged' file with a copy of 'mine'
   # (because we can't open 4 files)
   shutil.copy(mine, merged)

   # the call to meld
   # work right-to-left (result is far left file)
   cmd = [meld, merged, theirs, base]

   # Call meld, making sure it exits correctly
   subprocess.check_call(cmd)
except:
   print "Oh noes, an error!"
   sys.exit(-1)

Note, that we replaced the 'merged' file with a copy of 'mine' (your working copy). I choose to do this for a number of reasons, but primarily because the 'merged' file contains output from subversion's internal diff3 tool, which has bits of the other 3 files in it for conflict resolution. I don't want to deal with that stuff, and it will make Meld's job harder anyway.

So how do you actually use this? Once you have the script in place (don't forget to make it executable!), and you've added the entry to your subversion config file, just run a merge.

When subversion detects a conflict, you'll get prompted to respond to it, as usual:

Conflict discovered in 'MyClass.cpp'.
Select: (p) postpone, (df) diff-full, (e) edit,
        (h) help for more options:

Selecting h will show you:

(p)  postpone    - mark the conflict to be resolved later
  (df) diff-full   - show all changes made to merged file
  (e)  edit        - change merged file in an editor
  (r)  resolved    - accept merged version of file
  (mf) mine-full   - accept my version of entire file (ignore their changes)
  (tf) theirs-full - accept their version of entire file (lose my changes)
  (l)  launch      - launch external tool to resolve conflict
  (h)  help        - show this list

Select: (p) postpone, (df) diff-full, (e) edit,
        (h) help for more options:

You want option l, which will launch your script. This ends up opening Meld with 3 files. In order from left to right, they are:
  • merged: this starts out as a copy of 'mine' (your working copy), and this is where you want the final results of the merge to end up.
  • theirs : this is "later" or "right"revision version of the file you're merging with
  • base : the base or "left" revision version of the file you're merging with
Now, I don't usually find the base revision particularly helpful, so I don't bother having meld open it. The modified command looks like:

# the call to meld
   # work right-to-left (result is far left file)
   cmd = [meld, merged, theirs]

All that's left to do is resolve the conflicts. Keep in mind that the left-hand file is where the final results of the merge are stored, so make the changes you want in that file. When you're done, make sure you save the left-hand file and exit.

There's no need to save the other file(s), and even though they're temporary files, you may be better off not saving any changes you made to them. If you were to quit meld, and then relaunch it from the command line, what you'll find is that the script copies over the 'merged' file again. This allows you start over on this merge if something got screwed up. However, subversion doesn't replace the temporary files, so if you have saved changes to them, those changes will still persist when you re-open them. You could easily modify the script to prevent this (open copies of the files instead), if you prefer to have the modifications stick around.

So, when you're finally done, you save your changes and you exit out of meld, subversion will promopt you again:

Select: (p) postpone, (df) diff-full, (e) edit, (r) resolved,
        (h) help for more options:

If you are, indeed, satisfied with how you resolved the merge conflict, just select r. If you want to start over, just hit l to launch Meld again.

And that's it!

----------------------

I'll do my best to address any questions, suggestions, or critiques. Just leave a comment! :)

14 comments:

  1. The new version of Meld will have a merge-result-file argument:
    http://berikontech.blogspot.com/2010/11/merging-svn-using-meld.html

    ReplyDelete
  2. In subversion 1.6, they added another command line argument, so counting from the end now fails ):

    I used this code instead, and it worked:

    # file paths
    base = sys.argv[1]
    theirs = sys.argv[2]
    mine = sys.argv[3]
    merged = sys.argv[4]

    Since version 1.3 (2009 vintage), meld accepts 4 arguments for doing just this. The link above utilises that but has broken python indentation.. basically it's the same as above with no copy command and:

    cmd = [meld, mine, base, theirs, merged]

    ReplyDelete
    Replies
    1. I'm running Meld 1.6 on ArchLinux, I can see no reference of it supporting 4 way diff (local,base,remote,merged) as you say. Official Meld web site neither mention it. Downloading the sources as I'm writing this to check...

      Delete
  3. I'm not sure why you need that python script, all I did was make a short bash script and that seemed to work fine. My file is on the left, their file is on the right, and whatever I save my file as, it will accept as the merged version:

    #!/bin/bash
    /usr/bin/meld $3 $2 &
    exit 0

    ReplyDelete
  4. I wrote this tutorial right around the same time was was learning python, so it was a lovely excuse to practice. That said, I knew bash less than I knew python, so I went with python. I'll have to test your solution timbee and see how it works.

    As for meld being updated to take 4 arguments, that sounds perfect! Our Fedora core environment was under strict configuration control, so upgrading wasn't an option then.

    Thanks everyone for the great comments.

    ReplyDelete
  5. Is the meld can be used as comandline tool instead of launching GUI ?

    ReplyDelete
    Replies
    1. @mak: for command line, use vimdiff, it supports 4 windows for base,local,remote and merged.

      Delete
  6. Thanks a lot, that saved me a great deal of work.

    ReplyDelete
  7. I followed timbee's advice in the comments but added a $1 at the end to get the base file and I get "the external merge tool exited with exit code 255" after launching from an svn conflict. Any ideas what's up?

    ReplyDelete
    Replies
    1. I also tried the python script mentioned here and timbees script without the $1 parameter and get the same error for both "exit code 255". Anybody else see and solve this problem?

      Delete
    2. This comment has been removed by the author.

      Delete
  8. Ok, I figured out the problem. In the subversion config, one must set the merge-tool-cmd = /home/usrname/scripts/svn-merge-meld. That is you must fill out the absolute path, using ~/ doesn't work. Also using Ubuntu 11.04 I needed to "sudo chmod +x" the wrapper script.

    ReplyDelete
    Replies
    1. this is the cause of "exit code 255". I DuckDuckGo'ed it for a while without a hit, and finally stumbled on your comment. Thank you so much - 5 years later.

      Delete
  9. Interesting ideas . For my two cents , if someone want to merge some PDF files , my husband discovered a tool here http://www.altomerge.com/.

    ReplyDelete