Merging with tkdiff and Mercurial

I first encountered Mercurial last year when my project (openInstaller) decided to switch from TeamWare to Mercurial because everyone else was doing it :)  I had many years of TeamWare experience and was reluctant to switch.  Well, I got over it, and now I see why everyone was all fired up.  It's a pretty sweet system.  But that's not the point of this article.

Merging sucks

I personally hate to merge.  I avoid it whenever possible, because in a complex merge, you'll usually make a mistake or two.  I've also done my share of silent mismerges.  Usually, if I have a large-ish change and have to merge it with another large-ish change, I'll do it manually (e.g. take the parent repository, and manually re-introduce my changes into the files in conflict by hand).  Mercurial and TeamWare both support the concept of merging, but mercurial does it better under the covers (heh, sounds like a great bumper sticker).  The problem is, the stock version of Mercurial on my Solaris Express (and probably openSolaris) doesn't find any GUI utilities like tkdiff or kdiff3 because they aren't installed on the stock version of the OS.

If I install tkdiff, things get better, but I'm faced with a ghastly set of default color choices:

I am blinded by black-on-white (or anything-on-white for that matter).  I prefer the "blackout windows" color themes, where the majority of your screen's pixels are black (or close to black) most of the time.  Easier on my eyes, anyway.  So, using this .tkdiffrc:


# This file was generated by TkDiff 4.1.3
# Mon Mar 26 12:14:03 EDT 2007

set prefsFileVersion {4.1.3}

# Automatically center current diff region
define autocenter {1}

# Automatically select the nearest diff region while scrolling
define autoselect {0}

# Tag options for characters in line view
define bytetag {-background blue -foreground white}

# Tag options for changed diff region
define chgtag {-background blue}

# Color change bars to match the diff map
define colorcbs {1}

# Tag options for the current diff region
define currtag {-background #333300}

# Tag options for deleted diff region
define deltag {-background #660000 -font {Courier -12 bold}}

# diff command
define diffcmd {diff}

# Tag options for diff regions
define difftag {-background #222222}

# Program for editing files
define editor {}

# Windows-style toolbar buttons
define fancyButtons {0}

# Text window size
define geometry {80x30}

# Ignore blanks when diffing
define ignoreblanks {1}

# Ignore blanks option
define ignoreblanksopt {-w}

# Tag options for diff region inline differences
define inlinetag {-background DodgerBlue -font {Courier -12 bold}}

# Tag options for inserted diff region
define instag {-foreground green -font {Courier -12 bold}}

# Tag options for overlap diff region
define overlaptag {-background yellow}

# Show change bars
define showcbs {1}

# Show inline diffs (byte comparisons)
define showinline1 {0}

# Show inline diffs (recursive matching algorithm)
define showinline2 {0}

# Show current line comparison window
define showlineview {1}

# Show line numbers
define showln {1}

# Show graphical map of diffs
define showmap {1}

# Synchronize scrollbars
define syncscroll {1}

# Tab stops
define tabstops {8}

# Highlight change bars
define tagcbs {0}

# Highlight line numbers
define tagln {1}

# Highlight file contents
define tagtext {1}

# Text widget options
define textopt {-background black -foreground gray -font {Courier -14} -wrap non
e}

# Directory for scratch files
define tmpdir {/tmp}

# Use icons instead of labels in the toolbar
define toolbarIcons {1

 I get a nice-looking mostly-black window.  Additions are represented with green, changes with blue, and deletions with red.

 

To install tkdiff on recent versions of Solaris, I had to:

1. Download it, extract it, and ensure that the 'tkdiff' binary was on my $PATH.

2. Solaris, for some bizarre does not include wish (The Tk windowing shell which tkdiff requires).  Oh, my mistake, it does include it, but the command is called wish8.3!  I brought this up earlier, but no satisfactory answer has come my way.  So, I simply created a symlink:


[jhf@spirit]:bin$> pwd
/home/jhf/bin

[jhf@spirit]:bin$> ls -l wish
lrwxrwxrwx 1 jhf other 20 Nov 9 2006 wish -> /usr/sfw/bin/wish8.3


Problem solved!
 

 

Comments:

Posting a question from a blog reader..

> Hi James,
> I saw your blog from July 25th, 2007 entitled, "Merging with tkdiff and
> Mercurial" and I had some questions. Based on a shell script that I
> wrote and seeing the Mercurial documentation, I know that Mercurial
> passes 3 arguments (local, base, other) into whatever merge tool that is
> specified by the "merge = " property in the .hgrc file.
>
> I know that tkdiff takes in 2 arguments, however. I know that I can
> write a shell script that Mercurial can run, which will execute tkdiff
> and supply it with 2 out of the 3 arguments. I am just wondering when
> there is a 3 way merge conflict, how did you configure tkdiff with
> mercurial to handle it? What 2 arguments are you passing tkdiff for the
> merge?
>
> I am new to mercurial (2 weeks) so I apologize if the answer to this is
> obvious. Any help/suggestions you can provide would be greatly
> appreciated since I can't run the default Mercurial merge tool on my x86
> box.

Posted by James Falkner on June 27, 2008 at 05:08 AM EDT #

Response: tkdiff actually can take more than 2 args, and when you supply more than 2, it will do the 3-way merge. To supply the 3rd file (the ancestor of the two files you wish to merge), use -a <file>. Here's a portion of the 'tkdiff --help' output:

Additional optional parameters:
-a ANCESTORFILE
-o MERGEOUTPUTFILE
-L LEFT_FILE_LABEL [-L RIGHT_FILE_LABEL]

So, to do the three way merge you need to pass something like this:

tkdiff -a ${BASE} -o /tmp/out.result ${LOCAL} {OTHER}

One thing I found annoying is that if you screw up the merge and want to start over, there's no way to do it with the stock hgmerge program. So, here's the one I use (this is what I specify with merge = in my .hgrc):

---------------------------------------------------
#!/bin/ksh
#
# hgmerge - default merge helper for Mercurial
#
# This tries to find a way to do three-way merge on the current system.
# The result ought to end up in $1. Script is run in root directory of
# repository.
#
# Environment variables set by Mercurial:
# HG_FILE name of file within repo
# HG_MY_NODE revision being merged
# HG_OTHER_NODE revision being merged

cleanup() {
rm -rf ${HGTMP}
}

failure() {
echo "merge failed" 1>&2
cleanup
exit 1
}

if [ $# != 3 ] ; then
echo "usage: ${0} local base other"
failure
fi

LOCAL="$1"
BASE="$2"
OTHER="$3"

if [ ! -r ${LOCAL} -o ! -r ${BASE} -o ! -r ${OTHER} ] ;then
echo "Can't read input files:"
echo "${LOCAL}"
echo "${BASE}"
echo "${OTHER}"
fi

HGTMP="${TMPDIR-/tmp}/hgmerge.$RANDOM"

mkdir -p ${HGTMP}
if [ ! -d ${HGTMP} ] ; then
echo "Cannot make temp dir ${HGTMP}"
failure
fi

# Ask if the merge was successful
ask_if_merged() {
while true; do
echo "`basename $LOCAL` seems unchanged."
echo "Was the merge successful? [y/n]"
read answer
case "$answer" in
y\*|Y\*) return 0;;
n\*|N\*) return 1;;
esac
done
}

trap "failure" 1 2 3 6 15 # HUP INT QUIT ABRT TERM

#FILEMERGE=`which filemerge`
TKDIFF=`which tkdiff`
if [ -z "$TKDIFF" ] ; then
echo "Can't find tkdiff on your PATH"
failure
fi

# Backup the file
cp ${LOCAL} ${HGTMP}/local.backup

${TKDIFF} -L "My Changes to `basename ${HG_FILE}`" -L "Their Changes" -a ${BASE}
-o ${HGTMP}/merge.result ${HGTMP}/local.backup ${OTHER}

if [ $? != 0 ] ; then
# something bad happened
failure
fi

diff -bw ${HGTMP}/merge.result ${HGTMP}/local.backup > /dev/null 2>&1
if [ $? = 0 ] ; then
ask_if_merged
if [ $? != 0 ] ; then
failure
fi
fi

# OK, merge file is the one we want
cp ${HGTMP}/merge.result ${LOCAL}
echo "Merge result saved at ${LOCAL}"
cleanup
exit 0

Posted by James Falkner on June 27, 2008 at 05:12 AM EDT #

Post a Comment:
  • HTML Syntax: NOT allowed
About

bytor

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today