#!/bin/sh # # p4-merge -- merge wrapper around ediff-mode for Perforce # # Copyright (c) 2006-2009 Giorgos Keramidas # 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. # # $GKer: bin/p4-merge d40830e3e543 2009/07/11 07:21:00 keramida $ if test $# -ne 4 ; then echo >&2 "usage: `basename $0` BASE OTHER LOCAL MERGED" exit 1 fi # # Perforce calls its P4MERGE utility with 4 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 exec # gpyfm with the right options. We can't ignore $WC here, because gpyfm # assumes that after overwriting $LOCAL it's done, but Perforce may use # temporary files for these four, and lose the merged/resulting file. # 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 `p4 resolve' 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-no-gui}" 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}/p4merge.$$.XXXXXX"` if test $? -ne 0 ; then err 1 "Cannot create backup file at ${TMPDIR}/hgmerge.$$.XXXXXX" fi LABEL=`basename "${LOCAL}"` # 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 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