help-gnu-emacs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: Ediff and merge


From: Giorgos Keramidas
Subject: Re: Ediff and merge
Date: Sat, 16 May 2009 07:30:07 +0300
User-agent: Gnus/5.13 (Gnus v5.13) Emacs/23.0.93 (berkeley-unix)

On Fri, 15 May 2009 21:34:23 +0200, Rasmus Pank Roulund <rasmus.pank@gmail.com> 
wrote:
> Hello,
>
> I have a simple and probably simple question on ediff and merge.  I
> use SVN. Sometimes conflicts happens. I try to resolve them with
> Ediff. I use the 3files mode and use "a" and "b" to select whatever I
> want to keep.

ediff is a fine tool for merging conflicts in 3-way mode.  I use it all
the time for subversion, mercurial and perforce merges, and it has
slowly become one of the tools I consider indispensable when working
with version-controlled files.

The recent releases of Subversion support a config option to integrate
your own 3-way merge tool with the `svn merge' command.  That's what I
use locally to integrate ediff with subversion merging.

You first have to set the following option in the `config' file of your
`~/.subversion' directory.  Open the file and look for the section
called [helpers].  You can add the `merge-tool-cmd' option at that
section:

    [helpers]
    merge-tool-cmd = svn-ediff-merge

Then save the following script as `svn-ediff-merge', make it executable
and save it somewhere in your PATH so svn(1) can find it:

,-----------------------------------------------------------------------
| #!/bin/sh
| #
| # svn-merge -- merge wrapper around ediff-mode for Subversion
| #
| # Copyright (c) 2006-2009 Giorgos Keramidas <keramida@FreeBSD.org>
| # All rights reserved.
| #
| # Redistribution and use in source and binary forms, with or without
| # modification, are permitted provided that the following conditions
| # are met:
| # 1. Redistributions of source code must retain the above copyright
| #    notice, this list of conditions and the following disclaimer.
| # 2. Redistributions in binary form must reproduce the above copyright
| #    notice, this list of conditions and the following disclaimer in the
| #    documentation and/or other materials provided with the distribution.
| #
| # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
| # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
| # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
| # ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
| # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
| # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
| # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
| # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
| # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
| # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
| # SUCH DAMAGE.
|
| if test $# -ne 4 ; then
|         echo >&2 "usage: `basename $0` BASE OTHER LOCAL MERGED"
|         exit 1
| fi
|
| #
| # Subversion calls the program specified as `merge-tool-cmd' with four
| # filename arguments:
| #
| #   BASE  = base revision; the common ancestor of $other and $local
| #   OTHER = parent branch version
| #   LOCAL = locally modified version
| #   WC    = working copy; the file where merge results should be saved
| #
| # We have to take care to save these filename arguments, and then run
| # ediff-mode with the right options.  When the merge is finished, the
| # working copy of the file at ${WC} should contain the merge results.
| #
|
| BASE="$1"
| OTHER="$2"
| LOCAL="$3"
| WC="$4"
|
| cleanup() {
|         # We failed.  If ${RESTORE} is set, then we are supposed to have
|         # the pathname of a ${BACKUP} copy, and we should restore ${BACKUP}
|         # to ${LOCAL} and ${LOCAL} to ${WC} before dying.
|
|         CLEANUP=true            # Make sure we don't recurse forever.
|
|         test -z "${RESTORE}"            && return
|         test X"${RESTORE}" = X'yes'     || return
|
|         if test -z "${BACKUP}" || test -z "${LOCAL}" ; then
|                 err 1 "internal merge script error."
|         fi
|
|         cat "${BACKUP}" > "${LOCAL}" && rm "${BACKUP}"
|         if test $? -ne 0 ; then
|                 err 1 "Cannot restore ${LOCAL} -- workspace may be *unclean*"
|         fi
|
|         return 0
| }
|
| success() {
|         if test -z "${BACKUP}" || test -z "${LOCAL}" ; then
|                 err 1 "internal merge script error."
|         fi
|
|         # The merge was successful.  Remove the backup copy of ${LOCAL}.
|         if test -n "${BACKUP}" ; then
|                 /bin/rm -f "${BACKUP}"
|         fi
|
|         # When the merge is successful, ediff has left the merge results
|         # into $LOCAL.  Copy them over $WC to let `svn merge' know that
|         # we are done.
|         cat "${LOCAL}" > "${WC}"
|         if test $? -ne 0 ; then
|                 err 1 "Cannot restore ${WC} -- workspace is *unclean*"
|         fi
| }
|
| err() {
|         errcode=$1
|         shift
|         echo >&2 "`basename $0`: error: $*"
|         if test -z "${CLEANUP}" ; then
|                 cleanup
|         fi
|         exit $errcode
| }
|
| # Set $EMACS_PROGRAM in the environment of the merge script to the name of
| # the Emacs binary you want to use.  If unset, it defaults to `emacs' and it
| # also affects the default $EDITOR below.
| EMACS_PROGRAM="${EMACS_PROGRAM:-emacs}"
| export EMACS_PROGRAM
|
| # 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:-${EMACS_PROGRAM}}"
| 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 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
|
| # 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.
| BACKUP=`mktemp "${TMPDIR}/svnmerge.$$.XXXXXX"`
| if test $? -ne 0 ; then
|         err 1 "Cannot create backup file at ${TMPDIR}/hgmerge.$$.XXXXXX"
| fi
|
| # Save a backup copy of the $LOCAL file version.
| cp "${LOCAL}" "${BACKUP}" && RESTORE='yes'
| rc=$?
| if test $rc -ne 0 ; then
|         err 1 "Cannot create backup file at ${BACKUP}"
| fi
|
| # If the remote and the local file have no differences, then there's
| # nothing to merge.  Accept both :)
| if cmp "${LOCAL}" "${OTHER}" > /dev/null 2>&1 ; then
|         success
|         exit 0
| fi
|
| LABEL=`basename "${LOCAL}"`
| diff3 -m \
|     -L "${LABEL}" -L "${LABEL}.base" -L "${LABEL}.other" \
|     "${BACKUP}" "${BASE}" "${OTHER}" > "${LOCAL}"
| 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 merge ${LOCAL}"
| fi
|
| # In all other cases, diff3(1) has found conflicts, added the proper
| # conflict markers to the ${LOCAL} file.  Revert from the ${BACKUP}
| # copy of the file, and spawn an emacs+ediff merge session.
| #
| # Editing the ${LOCAL} file "pollutes" the workspace area, but the filename
| # shown in the editor buffer _really_ reflects the workspace path of the
| # ${LOCAL} file, which is very helpful with editors (i.e. it lets the
| # editor autopick the right 'mode' for editing the file, and so on).
| cat "${BACKUP}" > "${LOCAL}" && \
| ${EMACS_PROGRAM} --eval "(ediff-merge-with-ancestor \"$BACKUP\" \"$OTHER\" 
\"$BASE\" nil \"$LOCAL\")"
| if test $? -ne 0 ; then
|         err 1 "merge error for ${LOCAL}"
| fi
|
| # When the editor exits, there should be no conflict markers in the
| # ${LOCAL} copy of the filefile, otherwise we consider the merge failed.
| if grep '^>>>> ORIGINAL' "${LOCAL}" >/dev/null 2>&1 ; then
|         err 1 "'^>>>> ORIGINAL' conflict markers" \
|             "still found in the working-copy." \
|             "Merge aborted for ${LOCAL}"
| fi
| if grep '^==== THEIRS' "${LOCAL}" >/dev/null 2>&1 ; then
|         err 1 "'^==== THEIRS' conflict markers" \
|             "still found in the working-copy." \
|             "Merge aborted for ${LOCAL}"
| fi
| if grep '^==== YOURS' "${LOCAL}" >/dev/null 2>&1 ; then
|         err 1 "'^==== YOURS' conflict markers" \
|             "still found in the working-copy." \
|             "Merge aborted for ${LOCAL}"
| fi
| if grep '^<<<<' "${LOCAL}" >/dev/null 2>&1 ; then
|         err 1 "'^<<<<' conflict markers" \
|             "still found in the working-copy." \
|             "Merge aborted for ${LOCAL}"
| fi
|
| success                         # The merge has completed successfully.
| exit 0
`-----------------------------------------------------------------------

Now whenever `svn merge' prompts you for a conflict, you should be able
to type `l' (the shortcut for `launch external merge tool') to fire up
an Emacs instance in ediff's 3-way merge mode.

When you save the merged buffer, the script will take care of saving the
merged file in your workspace.  Then you can type `r' (resolved) at the
svn merge prompt, and you are done.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]