#!/usr/pkg/bin/bash
#
# Merge two revisions of a file
# Copyright (c) Linus Torvalds, 2005
#
# This is the git per-file merge script, called with
#
#   $1 - original file SHA1 (or empty)
#   $2 - file in branch1 SHA1 (or empty)
#   $3 - file in branch2 SHA1 (or empty)
#   $4 - pathname in repository
#   $5 - original file mode (or empty)
#   $6 - file in branch1 mode (or empty)
#   $7 - file in branch2 mode (or empty)
#
# We are designed to merge $3 _to_ $2, so we will give it
# a preference.
#
# FIXME: Make at least some effort to protect against checking in
# the conflicts.
#
# FIXME: What if a symlink replaces a regular file? What if symlinks conflict?
# What about directory-regularfile conflicts?

. "${COGITO_LIB:-/usr/pkg/lib/cogito/}"cg-Xlib || exit 1

id0="${ARGS[0]}"
id1="${ARGS[1]}"
id2="${ARGS[2]}"
file="${ARGS[3]}"
mode0="${ARGS[4]}"
mode1="${ARGS[5]}"
mode2="${ARGS[6]}"

error()
{
	echo "MERGE ERROR: $@" >&2
	return 1
}

warning()
{
	echo "MERGE WARNING: $@" >&2
}

# Returns stuff in $_pmxmc
# PlusMinus+XModeChar ;-)
pmxmc()
{
	if [ "$1" = "755" ]; then
		_pmxmc=+
	else
		_pmxmc=-
	fi
}

# Set ways[] to the symbolic names of the particular ways
load_unescaped_sym_ways()
{
	ways=("merge base" "$_git_head" "$(cat "$_git/merging-sym")")
}
load_sym_ways()
{
	load_unescaped_sym_ways
	ways[0]="merge/base"
	ways=("${ways[@]//\//~}")
}


case "${id0:-.}${id1:-.}${id2:-.}" in
#
# Deleted in both or deleted in one and unchanged in the other
#
"$id0.." | "$id0.$id0" | "$id0$id0.")
	#echo "Removing $file"
	if test -f "$file"; then
		rm -f -- "$file"
	fi &&
		exec git-update-index --remove -- "$file"
	;;

#
# Deleted in one and changed in the other
#
"$id0$id1." | "$id0.$id2")
	#echo "Removing $file"
	filev="$file"
	load_sym_ways
	if [ "$id1" ]; then
		num=1; mode="$mode1"; id="$id1"
	else
		num=2; mode="$mode2"; id="$id2"
	fi
	while [ -e "$filev~${ways[0]}" ] || [ -e "$filev~${ways[$num]}" ]; do
		filev="$filev~"
	done
	error "File $file removed in one branch but modified in the other!"
	error "The original version saved as '$filev~${ways[0]}', the modified one as '$filev~${ways[$num]}'."
	git-update-index --add --cacheinfo "$mode0" "$id0" "$file" &&
		git-checkout-index -u -f -- "$file" &&
		mv "$file" "$filev~${ways[0]}" ||
		error "Cannot create '$filev~${ways[0]}'"
	git-update-index --add --cacheinfo "$mode" "$id" "$file" &&
		git-checkout-index -u -f -- "$file" &&
		mv "$file" "$filev~${ways[$num]}" ||
		error "Cannot create '$filev~${ways[$num]}'"
	;;
#
# Added in one.
#
".$id1." | "..$id2" )
	#echo "Adding $file"
	git-update-index --add --cacheinfo "$mode1$mode2" "$id1$id2" "$file" &&
		exec git-checkout-index -u -f -- "$file"
	;;

#
# Added same in both (check for same permissions).
#
".$id2$id1")
	#echo "Adding $file"
	git-update-index --add --cacheinfo "$mode1" "$id1" "$file" &&
		git-checkout-index -u -f -- "$file"
	ret=$?
	if [ "$mode1" != "$mode2" ]; then
		pmxmc "$mode2"; chmod ${_pmxmc}x "$file"
		error "$file: added in both branches, permissions conflict $mode1->$mode2 (defaulting to $mode2)"
		exit 1
	fi
	exit $ret
	;;

#
# Added in both (different in each).
#
".$id1$id2")
	#echo "Adding $file"
	filev="$file"
	load_sym_ways
	while [ -e "$filev~${ways[1]}" ] || [ -e "$filev~${ways[2]}" ]; do
		filev="$filev~"
	done
	error "File $file added in both branches, but different in each!"
	error "Conflicting versions saved as '$filev~${ways[1]}' and '$filev~${ways[2]}'."
	git-update-index --add --cacheinfo "$mode1" "$id1" "$file" &&
		git-checkout-index -u -f -- "$file" &&
		mv "$file" "$filev~${ways[1]}" ||
		error "Cannot create '$filev~${ways[1]}'"
	git-update-index --add --cacheinfo "$mode2" "$id2" "$file" &&
		git-checkout-index -u -f -- "$file" &&
		mv "$file" "$filev~${ways[2]}" ||
		error "Cannot create '$filev~${ways[2]}'"
	exit 1
	;;

#
# Modified in both, but differently.
#
"$id0$id1$id2")
	echo "... Auto-merging $file"
	orig=$(git-unpack-file $id0)
	src2=$(git-unpack-file $id2)
	merged=$(mktemp .mergeresult-XXXXXX)

	load_unescaped_sym_ways
	# We reset the index to the first branch, making
	# git-diff-file useful
	git-update-index --add --cacheinfo "$mode1" "$id1" "$file"
		git-checkout-index -u -f -- "$file" &&
		{ diff3 -m -E -L "${ways[1]}" -L "${ways[0]}" -L "${ways[2]}" "$file" "$orig" "$src2" >"$merged"; }
	ret=$?
	rm -f -- "$orig" "$src2"
	cat "$merged" >"$file"
	rm "$merged"

	if [ "$mode1" != "$mode2" ]; then
		if [ "$mode0" = "$mode1" ]; then
			moded="$mode2"
		else
			moded="$mode1"
		fi
		pmxmc "$moded"; chmod ${_pmxmc}x "$file"
		error "Permissions conflict: $mode0->$mode1,$mode2 (defaulting to $moded)"
		ret=1
	fi

	if [ $ret -ne 0 ]; then
		echo "    CONFLICTS during merge." >&2
		exit 1
	fi
	exec git-update-index -- "$file"
	;;

*)
	error "$file: Not handling case: ${id0:-empty} -> ${id1:-empty} -> ${id2:-empty} (this would be a Cogito bug, please report it)"
	;;
esac
exit 1
