#!/usr/bin/env bash
#
# Merge a branch to the current tree.
# Copyright (c) Petr Baudis, 2005
#
# Takes a parameter identifying the branch to be merged.
#
# You have to examine the tree after the merge and then do `cg-commit`.
#
# Alternatively, it will just bring the 'HEAD' forward, if your current
# 'HEAD' is also the merge base.
#
# OPTIONS
# -------
# -b BASE_COMMIT::
#	Parameter specifies the base commit for the merge.
#
# -c::
#	Parameter specifies that you want to have tree merge never
#	autocomitted, but want to review and commit it manually.
#
# FILES
# -----
# $GIT_DIR/hooks/merge-pre::
#	If the file exists and is executable it will be executed right
#	before the merge itself happens. The script is passed those
#	arguments:
#		BRANCHNAME BASE CURHEAD MERGEDHEAD MERGETYPE
#	MERGETYPE is either "forward" or "tree". The merge is
#	cancelled if the script returns non-zero exit code.
#
# $GIT_DIR/hooks/merge-post::
#	If the file exists and is executable it will be executed after
#	the merge is done. The script is passed those arguments:
#		BRANCHNAME BASE CURHEAD MERGEDHEAD MERGETYPE STATUS
#	MERGETYPE is either "forward" or "tree". For "forward", the
#	STATUS is always "ok", while for "tree" the STATUS can be
#	"localchanges", "conflicts", "nocommit", or "ok".

USAGE="cg-merge [-c] [-b BASE_COMMIT] BRANCH_NAME"
_git_requires_root=1

. ${COGITO_LIB:-/usr/lib/cogito/}cg-Xlib


prehook()
{
	if [ -x $_git/hooks/merge-pre ]; then
		$_git/hooks/merge-pre "$branchname" "$base" "$head" "$branch" "$@" || die "merge cancelled by hook"
	fi
}

posthook()
{
	if [ -x $_git/hooks/merge-post ]; then
		$_git/hooks/merge-post "$branchname" "$base" "$head" "$branch" "$@"
	fi
}


head=$(commit-id) || exit 1


careful=
base=
while optparse; do
	if optparse -c; then
		careful=1
	elif optparse -b=; then
		base=$(commit-id "$OPTARG") || exit 1
	else
		optfail
	fi
done

branchname="${ARGS[0]}"
[ "$branchname" ] || { [ -s $_git/refs/heads/origin ] && branchname=origin; }
[ "$branchname" ] || die "what to merge?"
branch=$(commit-id "$branchname") || exit 1

[ "$base" ] || base=$(git-merge-base "$head" "$branch")
[ "$base" ] || die "unable to automatically determine merge base"


[ -s $_git/blocked ] && die "merge blocked: $(cat $_git/blocked)"

if [ -s "$_git/merging" ] && grep -q "$branch" $_git/merging; then
	echo "Branch already merged in the working tree." >&2
	echo 0
fi

if [ "$base" = "$branch" ]; then
	echo "Branch already fully merged." >&2
	exit 0
fi

if [ "$head" = "$base" ]; then
	# No need to do explicit merge with a merge commit; just bring
	# the HEAD forward.

	echo "Fast-forwarding $base -> $branch" >&2
	echo -e "\ton top of $head..." >&2

	prehook forward
	tree_timewarp "forward" "yes, rollback (or rather rollforth) the tree!" $base $branch
	posthook forward ok

	exit 0
fi


[ "$(git-diff-files -s)" ] && update_index

echo "Merging $base -> $branch" >&2
echo -e "\tto $head..." >&2


prehook tree

if ! git-read-tree -u -m $(tree-id $base) $(tree-id $head) $(tree-id $branch); then
	echo "cg-merge: git-read-tree failed (merge likely blocked by local changes)" >&2
	posthook tree localchanges
	exit 1
fi

echo $base >>$_git/merge-base
echo $branch >>$_git/merging
echo $branchname >>$_git/merging-sym

if ! git-merge-cache -o -q ${COGITO_LIB:-/usr/lib/cogito/}cg-Xmergefile -a || [ "$careful" ]; then
	# "Resolve" merges still in the cache (conflicts).
	# We will resolve only those caught by merge-cache;
	# that is "three-way conflicts". Others should still
	# be resolved manually on the lower level by the user.
	git-ls-files --unmerged | {
		stage1mode=
		stage1hash=
		stage1name=
		stage2seen=
		while read mode sha1 stage filename; do
			case $stage in
			1)
				stage1mode="$mode"
				stage1hash="$sha1"
				stage1name="$filename"
				continue
				;;
			2)
				stage2seen=
				[ "$stage1name" = "$filename" ] && stage2seen=1
				continue
				;;
			3)
				[ "$stage1name" = "$filename" ] || continue
				[ "$stage2seen" ] || continue
				stage2seen=
			esac
			git-update-cache --cacheinfo $stage1mode $stage1hash $stage1name
		done
	}

	[ ! "$careful" ] && cat >&2 <<__END__

	Conflicts during merge. Do cg-commit after resolving them.
__END__
	posthook tree conflicts
	exit 2
fi
git-checkout-cache -f -u -a

echo
readtree=
if ! cg-commit -C; then
	readtree=1
	echo "cg-merge: COMMIT FAILED, retry manually" >&2
	posthook tree nocommit
fi

[ "$readtree" ] && git-read-tree -m HEAD
update_index

posthook tree ok
