#!/bin/sh -efu
#
# Copyright (C) 2006-2008  Sergey Vlasov <vsu@altlinux.org>
#
# Merge branches listed in the branches-to-merge file.
#
# This file is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
#

TOP="$(readlink -ev .)"

. kernel-build-sh-functions

print_version()
{
	cat <<EOF
$PROG version $PROG_VERSION

Copyright (C) 2006-2008  Sergey Vlasov <vsu@altlinux.org>
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
EOF
	exit
}

show_help()
{
	cat <<EOF
$PROG - merge branches listed in the branches-to-merge file.

Usage: $PROG [options]

Options:
  -a, --auto                 merge all branches without asking;
  -l, --list                 just list branches with new commits;
  -v, --verbose              show also names of unchanged branches;
  -V, --version              print program version and exit;
  -h, --help                 show this text and exit.

EOF
	exit
}

TEMP=`getopt -n "$PROG" -o a,l,v,V,h -l auto,list,verbose,version,help -- "$@"` ||
	show_usage
eval set -- "$TEMP"

auto=
list_only=
verbose=
while :; do
	case "$1" in
		--) shift; break
			;;
		-a|--auto) auto=t
			;;
		-l|--list) list_only=t; auto=t
			;;
		-v|--verbose) verbose=-v
			;;
		-V|--version) print_version
			;;
		-h|--help) show_help
			;;
		*) fatal "Unrecognized option: $1"
			;;
	esac
	shift
done

[ $# = 0 ] || show_usage "Too many arguments"

if [ -z "$auto" ]; then
	[ -t 0 ] ||
		fatal "Interactive mode requires input from a terminal"
fi

[ -s branches-to-merge ] ||
	fatal "Cannot find 'branches-to-merge' file"

is_already_merged()
{
	local branch="$1" && shift

	local missing="$(git rev-list --max-count=1 HEAD.."$branch")"
	[ -z "$missing" ] || return 1
}

show_short_log()
{
	local branch="$1" && shift

	local short_name="${name#refs/heads/}"
	local max=15 count skipped

	echo "New commits in branch '$short_name':"
	git log --abbrev-commit --pretty=oneline --max-count="$max" \
		HEAD.."$branch" | sed -e 's/^/  /'
	count="$(git rev-list HEAD.."$branch" | wc -l)"
	if [ "$count" -gt "$max" ]; then
		skipped="$(( count - max ))"
		echo "  ... and $skipped more"
	fi
	echo
}

confirm_before_merge()
{
	local branch="$1" && shift

	echo
	show_short_log "$branch"
	while :; do
		printf "Merge (Y/N) or Log/Patches/Gitk/Quit ? "
		local reply
		read -r reply ||
			fatal "Error reading from terminal"
		case "$reply" in
			y|Y) return 0
				;;
			n|N) return 1
				;;
			l|L) git -p log HEAD.."$branch"
				;;
			p|P) git -p log -p HEAD.."$branch"
				;;
			g|G) gitk HEAD.."$branch" >/dev/null 2>&1 </dev/null &
				;;
			q|Q) fatal "Aborted by user"
				;;
		esac
	done
}

confirm_after_merge()
{
	echo
	while :; do
		printf "Accept (Y/N) or Diff/Gitk/Quit ? "
		local reply
		read -r reply ||
			fatal "Error reading from terminal"
		case "$reply" in
			y|Y) return 0
				;;
			n|N) return 1
				;;
			d|D) git -p diff -p --stat --summary -M "HEAD^1" HEAD
				;;
			g|G) gitk HEAD^1..HEAD >/dev/null 2>&1 </dev/null &
				;;
			q|Q) fatal "Aborted by user"
				;;
		esac
	done
}

for branch in $(cat branches-to-merge); do
	case "$branch" in
		""|\#*) continue;;
	esac
	real_branches="$(git ls-remote . "refs/heads/$branch" | cut -f2)"
	if [ -z "$real_branches" ]; then
		real_branches="$(git ls-remote . "refs/heads/$branch--*" |
			cut -f2)"
	fi
	[ -n "$real_branches" ] ||
		fatal "Branch '$branch' not found"
	for name in $real_branches; do
		short_name="${name#refs/heads/}"
		if is_already_merged "$name"; then
			verbose "No new commits in branch '$short_name'"
			continue
		fi
		if [ -n "$list_only" ]; then
			show_short_log "$name"
			continue
		fi
		if [ -z "$auto" ]; then
			confirm_before_merge "$name" || continue
		fi
		message "Merging '$short_name'"
		git pull . "$name" ||
			fatal "Unable to merge '$short_name' automatically"
		if [ -z "$auto" ]; then
			confirm_after_merge ||
				git reset --hard "HEAD^1"
		fi
	done
done
