Differences between revisions 22 and 29 (spanning 7 versions)
Revision 22 as of 2012-11-21 20:15:18
Size: 4883
Editor: 70
Comment: updated emacs.args quoting, because mercurial 2.4 quotes the file names
Revision 29 as of 2015-08-18 22:49:33
Size: 6277
Editor: mpm
Comment:
Deletions are marked like this. Additions are marked like this.
Line 2: Line 2:
<<Include(A:dated)>>
<<Include(A:style)>>
Line 4: Line 7:

Note that emacs comes with a [[http://www.emacswiki.org/emacs/VersionControl|version control]] wrapper that works with mercurial and other systems. See particularly its special dired mode for operations on the whole repository, rather than individual files. C-x v m does a merge.
Line 32: Line 37:
{{{#!/bin/sh {{{
 
#!/bin/sh
Line 34: Line 40:
   set -e # bail out quickly on failure  set -e # bail out quickly on failure
Line 36: Line 42:
   LOCAL="$1"
   BASE="$2"
   OTHER="$3"
 LOCAL="$1"
 BASE="$2"
 OTHER="$3"
Line 40: Line 46:
   BACKUP="$LOCAL.orig"  BACKUP="$LOCAL.orig"
Line 42: Line 48:
   Restore ()
   {
     cp "$BACKUP" "$LOCAL"
   }
 Restore ()
 {
   cp "$BACKUP" "$LOCAL"
 }
Line 47: Line 53:
   ExitOK ()
   {
     exit $?
   }
 ExitOK ()
 {
   exit $?
 }
Line 52: Line 58:
   # Back up our file
   cp "$LOCAL" "$BACKUP"
 # Back up our file
 cp "$LOCAL" "$BACKUP"
Line 55: Line 61:
   # Attempt to do a non-interactive merge
   if which merge > /dev/null 2>&1 ; then
       if merge "$LOCAL" "$BASE" "$OTHER" 2> /dev/null; then
           # success!
           ExitOK
       fi
       Restore
   elif which diff3 > /dev/null 2>&1 ; then
       if diff3 -m "$BACKUP" "$BASE" "$OTHER" > "$LOCAL" ; then
           # success
           ExitOK
       fi
       Restore
   fi

   if emacs -q --no-site-file --eval "(ediff-merge-with-ancestor \"$BACKUP\" \"$OTHER\" \"$BASE\" nil \"$LOCAL\")"
   then
 # Attempt to do a non-interactive merge
 if which merge > /dev/null 2>&1 ; then
   if merge "$LOCAL" "$BASE" "$OTHER" 2> /dev/null; then
       # success!
Line 74: Line 67:
   Restore
 elif which diff3 > /dev/null 2>&1 ; then
   if diff3 -m "$BACKUP" "$BASE" "$OTHER" > "$LOCAL" ; then
       # success
       ExitOK
   fi
   Restore
 fi
Line 75: Line 76:
   echo "emacs-merge: failed to merge files"
   exit 1
 if emacs -q --no-site-file --eval "(ediff-merge-with-ancestor \"$BACKUP\" \"$OTHER\" \"$BASE\" nil \"$LOCAL\")"
 then
   ExitOK
 fi
Line 78: Line 81:
   # End of file  echo "emacs-merge: failed to merge files"
 exit 1

 # End of file
Line 80: Line 86:
Line 89: Line 96:
   [ui]
   merge = emacs-merge
[ui]
merge = emacs-merge
Line 92: Line 99:
Line 94: Line 102:

==== With emacs 24 or later ====
This works on a Linux system under a shell running in emacs. Windows users will have to adjust.

1. In `.emacs.d/init_bash.sh` include
{{{
export EDITOR=emacsclient
export HGMERGE=emacsclient
}}}

The first line is not necessary for merges, but will help with, e.g., commit messages.

See the emacs help for the `shell` command on alternative ways to set the environment variables. If your shell is not bash, you will need to change the name of the startup file.

2. In `~/.hgrc`:
{{{
[merge-tools]
emacsclient.args = --eval "(ediff-merge-with-ancestor \"$local\" \"$other\" \"$base\" nil \"$outp\
ut\")"
}}}

3. Start the emacs server. You can do this by executing `start-server` once you have started emacs, or you can put `(start-server)` in your emacs initialization file.

4. In the shell, do an hg merge. This will launch you into an ediff session. Adjust the C buffer until it is OK and then save it and exit the ediff session. The C-x # command doesn't seem to work in its usual way for emacsclient.

==== With emacs 23 or earlier ====
Line 104: Line 138:
   [ui]
   merge = emacsclient-merge
[ui]
merge = emacsclient-merge
Line 107: Line 141:
{{{#!/bin/bash
Line 109: Line 142:
    if [ $# -lt 1 ]; then
      echo 1>&2 "Usage: $0 local other base output"
      exit 1
    fi
{{{
 #!/bin/bash
Line 114: Line 145:
    local=$1
    other=$2
    base=$3
    output=$4
 if [ $# -lt 1 ]; then
    echo 1>&2 "Usage: $0 local other base output"
    exit 1
 fi
Line 119: Line 150:
    OUTPUT=`emacsclient --no-wait --eval "(ediff-merge-with-ancestor \"$local\" \"$other\" \"$base\" nil \"$output\")" 2>&1`
    echo $OUTPUT | grep -v "Ediff Control Panel"
 local=$1
 other=$2
 base=$3
 output=$4
Line 122: Line 155:
    if echo "$OUTPUT" | grep -q '^*ERROR*'; then
        exit 1
    fi
 OUTPUT=`emacsclient --no-wait --eval "(ediff-merge-with-ancestor \"$local\" \"$other\" \"$base\" nil \"$output\")" 2>&1`
 echo $OUTPUT | grep -v "Ediff Control Panel"

 if echo "$OUTPUT" | grep -q '^*ERROR*'; then
    exit 1
 fi
Line 126: Line 162:
Q (28 Jun, 2011): This currently does not work. Seems that others are having the same problem (https://identi.ca/notice/71698204). The error message is: *ERROR*: The merge buffer file ~/path must not be a directory Q (28 Jun, 2011): This currently does not work. Seems that others are having the same problem (https://identi.ca/notice/71698204). The error message is: `*ERROR*: The merge buffer file ~/path must not be a directory`
Line 128: Line 164:
A (Nov 2011): Then upgrade to a newer version of emacs. This is fixed in Emacs 24, which has been in pretesting for months, and a stable release is expected in early 2012. A (July, 2013): The issue appears to be with how the arguments are passed to the merge. I found that the following `.hgrc` stanza fixed the issue:
{{{
[ui]
merge = emacsclient-merge
[merge-tools]
emacsclient-merge.args=$local $other $base $output
}}}

Note:

This page appears to contain material that is no longer relevant. Please help improve this page by updating its content.

{i} This page does not meet our wiki style guidelines. Please help improve this page by cleaning up its formatting.

Using Emacs as a merge program

Emacs is bundled with an elisp program called Ediff whose purpose is to help developers to visually apply patches. One of the ediff commands is well-suited to three-way merging and can be used as a merge program with Mercurial.

Note that emacs comes with a version control wrapper that works with mercurial and other systems. See particularly its special dired mode for operations on the whole repository, rather than individual files. C-x v m does a merge.

Mercurial 1.0 and later

Add the following to your ~/.hgrc file (see MergeToolConfiguration):

[ui]
merge = emacs

[merge-tools]
emacs.args = -q --eval "(ediff-merge-with-ancestor \""$local"\" \""$other"\" \""$base"\" nil \""$output"\")"

If you want to use the plain emerge tool in emacs, the following line works instead in the merge-tools section:

emacs.args = -q --eval "(emerge-files-with-ancestor nil \""$local"\" \""$other"\" \""$base"\" \"$output\" nil 'kill-emacs)"

A more elaborate version that puts the ediff control window inside the main frame:

emacs.args = -q --eval "(require 'ediff)" --eval "(setq ediff-window-setup-function 'ediff-setup-windows-plain)" --eval "(add-hook 'ediff-quit-hook 'save-buffers-kill-emacs)" --eval "(ediff-merge-with-ancestor \""$local"\" \""$other"\" \""$base"\" nil \""$output"\")"

Mercurial 0.9.5 and earlier: Wrap Emacs+Ediff call in a script

Dump the following content into a file in your PATH (don't forget to turn on the execute bit):

 #!/bin/sh

 set -e # bail out quickly on failure

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

 BACKUP="$LOCAL.orig"

 Restore ()
 {
   cp "$BACKUP" "$LOCAL"
 }

 ExitOK ()
 {
   exit $?
 }

 # Back up our file
 cp "$LOCAL" "$BACKUP"

 # Attempt to do a non-interactive merge
 if which merge > /dev/null 2>&1 ; then
   if merge "$LOCAL" "$BASE" "$OTHER" 2> /dev/null; then
       # success!
       ExitOK
   fi
   Restore
 elif which diff3 > /dev/null 2>&1 ; then
   if diff3 -m "$BACKUP" "$BASE" "$OTHER" > "$LOCAL" ; then
       # success
       ExitOK
   fi
   Restore
 fi

 if emacs -q --no-site-file --eval "(ediff-merge-with-ancestor \"$BACKUP\" \"$OTHER\" \"$BASE\" nil \"$LOCAL\")"
 then
   ExitOK
 fi

 echo "emacs-merge: failed to merge files"
 exit 1

 # End of file

Considering Mercurial is now able to premerge before running the merge tool and even does it by default (see hgrc(5)), I feel that attempts of merge with merge and diff3 are not needed anymore.

0.1. How the script works

This script tries first to automatically merge the files using the RCS merge program or the diff3 program. If the automatic merger fails merging the files because of a conflict or, neither merge nor diff3 are available on the system, then emacs is launched to let the developer resolve the conflicts.

0.2. Enabling the script usage

Don't forget to add an entry in your hgrc file (either ~/.hgrc or the local working copy .hg/hgrc) to point Mercurial at your merge command (let's call it emacs-merge)

[ui]
merge = emacs-merge

All Mercurial versions: using emacsclient

You may want to use 'emacsclient' to reuse an already running Emacs session.

1. With emacs 24 or later

This works on a Linux system under a shell running in emacs. Windows users will have to adjust.

1. In .emacs.d/init_bash.sh include

export EDITOR=emacsclient
export HGMERGE=emacsclient

The first line is not necessary for merges, but will help with, e.g., commit messages.

See the emacs help for the shell command on alternative ways to set the environment variables. If your shell is not bash, you will need to change the name of the startup file.

2. In ~/.hgrc:

[merge-tools]
emacsclient.args = --eval "(ediff-merge-with-ancestor \"$local\" \"$other\" \"$base\" nil \"$outp\
ut\")"

3. Start the emacs server. You can do this by executing start-server once you have started emacs, or you can put (start-server) in your emacs initialization file.

4. In the shell, do an hg merge. This will launch you into an ediff session. Adjust the C buffer until it is OK and then save it and exit the ediff session. The C-x # command doesn't seem to work in its usual way for emacsclient.

2. With emacs 23 or earlier

Until the trunk was patched (on 02 Oct 2010), emacsclient did not relay errors from the server process properly (it always exited with status 0), so hg had no way to know if an error occurred, and would happily commit the merge, possibly without you ever noticing something is amiss.

As a workaround if the fix isn't in the version of emacs you use, you'll need a wrapper script like the one below to supply the error handling.

  • (Emacs Bug #6963 has been resolved in the trunk on 02 Oct 2010, so you'll need Emacs 24, at least. (Check etc/NEWS for "If emacsclient shuts down as ... its exit status is 1")

(save it as e. g. 'emacsclient-merge' and configure it like this)

[ui]
merge = emacsclient-merge

 #!/bin/bash

 if [ $# -lt 1 ]; then
    echo 1>&2 "Usage: $0 local other base output"
    exit 1
 fi

 local=$1
 other=$2
 base=$3
 output=$4

 OUTPUT=`emacsclient --no-wait --eval "(ediff-merge-with-ancestor \"$local\" \"$other\" \"$base\" nil \"$output\")" 2>&1`
 echo $OUTPUT | grep -v "Ediff Control Panel"

 if echo "$OUTPUT" | grep -q '^*ERROR*'; then
    exit 1
 fi

Q (28 Jun, 2011): This currently does not work. Seems that others are having the same problem (https://identi.ca/notice/71698204). The error message is: *ERROR*: The merge buffer file ~/path must not be a directory

A (July, 2013): The issue appears to be with how the arguments are passed to the merge. I found that the following .hgrc stanza fixed the issue:

[ui]
merge = emacsclient-merge
[merge-tools]
emacsclient-merge.args=$local $other $base $output

MergingWithEmacs (last edited 2015-08-18 22:49:33 by mpm)