#!/bin/sh
#
# ediff-merge -- Ediff wrapper for Mercurial
#
# 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 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() {
	# 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} 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 is *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
}

err() {
	errcode=$1
	shift
	echo >&2 "`basename $0`: error: $*"
	if test -z "${CLEANUP}" ; then
		cleanup
	fi
	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:-emacs}"
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}/hgmerge-XXXXXX"`
if test $? -ne 0 ; then
	err 1 "Cannot create backup file at ${TMPDIR}/hgmerge-XXXXXX"
fi

LABEL=`basename "${LOCAL}"`

# First try to add conflict markers around the areas that need special
# attention in the ${LOCAL} file.
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

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 -nw --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 '^<<<<<<<' "${LOCAL}" >/dev/null 2>&1 ; then
	err 1 "'^<<<<<<<' 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
if grep '^|||||||' "${LOCAL}" >/dev/null 2>&1 ; then
	err 1 "'^|||||||' 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
exit 0

