== Merging Manually Using Your Favorite Editor and diff3 == Some people prefer editing conflicts within an editor, manually merging the areas that contain conflicts. To do this with [[Mercurial]], you can use a small wrapper-script around the GNU diff3 utility that is described here. === Wrapping GNU diff3(1) === The GNU diff3(1) utility can accept a list of three files, similar to the one [[Mercurial]] passed to a [[MergeProgram|merger program]] and generate a copy of the local file with [[Conflict|conflict]] markers. Typically, the output of diff3(1) looks like this: {{{ A <<<<<<< /path/file B - my local changes ||||||| /tmp/file~base.yx63PT B - the common base ======= B - changes made by others >>>>>>> /tmp/file~other.fdfgW2 C }}} To make diff3(1) generate a copy of the merged file with this sort of conflict markers and fire up an editor on this copy, you can save the following to for example {{{hg-diff3-merge}}} in your PATH and turn on its execute bit: {{{ #!/bin/sh if test $# -ne 3 ; then echo >&2 "usage: `basename $0` MYFILE OLDFILE YOURFILE" exit 1 fi # Keep a local copy of the filenames involved in the merge. LOCAL="$1" BASE="$2" OTHER="$3" cleanup() { if test -n "${TMPDIR}" && test -n "${WC}" ; then B=`dirname "${WC}"` TMPDIRPATH=`cd "${TMPDIR}"; pwd` WCPATH=`cd "${B}"; pwd` if test X"${B}" = X"${TMPDIR}" ; then /bin/rm -f "${WC}" fi fi } success() { if test -z "${WC}" || test -z "${LOCAL}" ; then err 1 "internal merge script error." fi # The merge was successful. Copy back the merged file on top of ${LOCAL} cp "${WC}" "${LOCAL}" && /bin/rm "${WC}" if test $? -ne 0 ; then err 1 "Failed to save merged file at ${LOCAL}" fi } err() { errcode=$1 shift echo >&2 "`basename $0`: error: $*" cleanup exit $errcode } # Since this script depends on manual edits being performed to the files being # merged, make sure that ${EDITOR} is truly set to something, even if this is # just plain good ol' vi(1). EDITOR="${EDITOR:-vi}" export EDITOR # First make sure $TMPDIR points to a meaningful directory. We will be using # this shell variable further down, so it's a good idea to make sure it isn't # empty later on. TMPDIR="${TMPDIR:-/var/tmp}" export TMPDIR # We will be using a temporary file with the diff3(1) output as the merge # buffer, until either the merge removes all conflict markers from the working # copy of the file or we fail somehow to complete the merge. WC=`mktemp "${TMPDIR}/hgmerge-XXXXXX"` if test $? -ne 0 ; then err 1 "Cannot create temporary file at ${TMPDIR}/hgmerge-XXXXXX" fi # We depend on diff3(1) being available to do the first pass of the merge, # adding conflict markers around the areas that should be edited. which diff3 >/dev/null 2>&1 if test $? -ne 0 ; then err 1 "No diff3(1) utility found in the current PATH." fi # First try to add conflict markers around the areas that need special # attention in the ${LOCAL} file. The output is not saved directly over the # file that is currently in-conflict, but is saved in the ${WC} temporary file # to allow editing of the conflict regions without diff3 -m "${LOCAL}" "${BASE}" "${OTHER}" > "${WC}" rc=$? if test $rc -eq 0 ; then # No conflicts found. Merge done. success exit 0 elif test $rc -gt 1 ; then err 1 "serious diff3 error, while trying to merhge ${LOCAL}" fi # In all other cases, diff3(1) has found conflicts, added the proper conflict # markers to the ${WC} file and we should now edit this file. Fire up an # editor with the ${WC} file and let the user manually resolve the conflicts. # When the editor exits successfully, there should be no conflict markers in # the ${WC} file, otherwise we consider this merge failed. ${EDITOR} "${WC}" if test $? -ne 0 ; then err 1 "merge error for ${LOCAL}" fi if grep '^<<<<<<<' "${WC}" >/dev/null 2>&1 || grep '^|||||||' "${WC}" >/dev/null 2>&1 || grep '^=======' "${WC}" >/dev/null 2>&1 || grep '^>>>>>>>' "${WC}" >/dev/null 2>&1 ; then err 1 "conflict markers still found in the working-copy. Merge aborted for ${LOCAL}" fi success exit 0 }}} === How the script works === This script tries first to automatically merge the files using the GNU diff3(1) utility program. If the automatic merge fails because there are conflicts, then an editor is launched on the output of diff3(1) to let you manually resolve the conflicts. When the editor is done modifying the ''resolved'' file, the script checks again to make sure that all conflict markers have been removed now. If there are still some conflict markers, the merge fails. Re-running the merge should be ok. === Enabling the script usage === The script must be enabled in your hgrc file (either ~/.hgrc or the local working copy .hg/hgrc) to point [[Mercurial]] at your hg-diff3-merge: {{{ [ui] merge = hg-diff3-merge }}} Or integrate it in your merge-tools configuration with proper priority: {{{ [merge-tools] hg-diff3-merge.priority = 100 }}} === A Simpler Version === {{{ #! /bin/sh # hgmerge.sh - invoke diff3 to merge files, save conflicts in mine.hgmerge # # author: Noel Burton-Krahn # created: Feb 28, 2007 # exit on error or undefined variables set -eu # command-line args mine="$1" orig="$2" theirs="$3" # where to save merged file merged="$mine".hgmerge rm -f "$merged" if diff3 -L mine -L original -L theirs -E -m "$mine" "$orig" "$theirs" > "$merged" then mv "$merged" "$mine" echo Merged "$mine" else echo Conflict saved in "'""$merged""'". Rename to $(basename "$mine") when fixed. exit 1 fi }}} === Version run under Windows 2000/XP === This script like pervious. It tested with diff3 from !GnuWin32, MinGW, Cygwin and with space on path to merged files. {{{ @echo off IF "%3"=="" GOTO err_arg_cnt IF NOT "%4"=="" GOTO err_arg_cnt IF NOT EXIST %1 GOTO err_file_not_exist IF NOT EXIST %2 GOTO err_file_not_exist IF NOT EXIST %3 GOTO err_file_not_exist SET my=%1 SET orig=%2 SET your=%3 SET merged=%my%.hgmerge IF EXIST %merged% (del /q %merged%) diff3 -L my -L orig -L your -E -m %my% %orig% %your% > %merged% IF ERRORLEVEL 1 ( echo C %my% move /y %merged% %my% EXIT 1 ) ELSE ( echo M %my% move /y %merged% %my% ) EXIT 0 :err_arg_cnt echo Wrong arg count! echo You run: echo ^> %0 %* echo Must: echo ^> %0 my orig your EXIT 1 :err_file_not_exist echo One of '%*' files not exist. EXIT 1 }}} === Python version === Finally, if the batch file doesn't work for you (for some reason it doesn't on my Vista 64 system with Mercurial 1.3), here is a Python version. It writes all conflicts and merge failures to a log file, which I find useful. {{{ #!/usr/bin/python import sys import subprocess import os.path if len(sys.argv) != 4: print "Usage: hgdiff3 " sys.exit(1) my = sys.argv[1] orig = sys.argv[2] your = sys.argv[3] merged = my + '.hgmerge' if not os.path.exists(my) or not os.path.exists(orig) or \ not os.path.exists(your): print "One or more of the merge files do not exist" sys.exit(1) if os.path.exists(merged): os.remove(merged) output = open(merged, 'w') p = subprocess.Popen(['diff3', '-m', '-L', 'local', '-L', 'base', '-L', 'other', my, orig, your], stdout=output) result = p.wait() output.close() if result == 0 or result == 1: os.remove(my) os.rename(merged, my) if result == 0: print "diff3 merged automatically" elif result == 1: print " Conflicts: " + my log = open('hgmerge.log', 'a') log.write( "Conflicts: " + my + "\n" ) log.close() else: print " Merge failed (binary file?): " + my log = open('hgmerge.log', 'a') log.write( "Failed: " + my + "\n" ) log.close() sys.exit(result) }}}