#!/usr/bin/env bash
#
# Make a diff between two GIT trees.
# Copyright (c) Petr Baudis, 2005
#
# Outputs a diff for converting the first tree to the second one.
# By default compares the current working tree to the state at the
# last commit. The output will automatically be displayed in a pager
# unless it is piped to a program.
#
# OPTIONS
# -------
# -c::
#	Colorize the diff output
#
# -p::
#	Show diff to the parent of the current commit (or the commit
#	specified by the -r parameter).
#
# -s::
#	Summarize the diff by showing a histogram for removed and added
#	lines (similar to the output of diffstat(1)) and information
#	about added and renamed files and mode changes.
#
# -r FROM_ID[..TO_ID]::
#	Specify the revisions to diff using either '-r rev1..rev2' or
#	'-r rev1 -r rev2'. If no revision is specified, the current
#	working tree is implied. Note that no revision is different from
#	empty revision which means '-r rev..' compares between 'rev' and
#	'HEAD', while '-r rev' compares between 'rev' and working tree.
#
# -m::
#	Base the diff at the merge base of the -r arguments (defaulting
#	to HEAD and origin).
#
# ENVIRONMENT VARIABLES
# ---------------------
# PAGER::
#	The pager to display log information in, defaults to `less`.
#
# PAGER_FLAGS::
#	Flags to pass to the pager.
#
# CG_LESS::
#	This is what the $LESS environment variable value will be set
#	to before invoking $PAGER. It defaults to $LESS concatenated
#	with the `R` flag to allow displaying of colorized output.
#
# NOTES
# -----
# The ':' is equivalent to '..' in revisions range specification (to make
# things more comfortable to SVN users). See cogito(7) for more details
# about revision specification.

USAGE="cg-diff [-c] [-m] [-s] [-p] [-r FROM_ID[..TO_ID]] [FILE]..."

. ${COGITO_LIB}cg-Xlib || exit 1


# TODO: Make cg-log use this too.
setup_colors()
{
	local C="diffhdr=1;36"
	C="$C:diffhdradd=1;32:diffadd=32"
	C="$C:diffhdrmod=1;35:diffmod=35"
	C="$C:diffhdrrem=1;31:diffrem=31"
	C="$C:diffhunk=36:diffctx=34"
	C="$C:default=0"
	[ -n "$COGITO_COLORS" ] && C="$C:$COGITO_COLORS"

	C=${C//=/=\'$'\e'[}
	C=col${C//:/m\'; col}m\'
	#coldefault=$(tput op)
	eval "$C"
}

colorize()
{
	if [ "$opt_summary" ]; then
		git-apply --summary --stat
	elif [ "$opt_color" ]; then
		gawk '
		{ if (/^(diff --git) /)
		    print "'$coldiffhdr'" $0 "'$coldefault'"
		  else if (/^\+\+\+/)
		    print "'$coldiffhdradd'" $0 "'$coldefault'"
		  else if (/^---/)
		    print "'$coldiffhdrrem'" $0 "'$coldefault'"
		  else if (/^(\+|new( file)? mode )/)
		    print "'$coldiffadd'" $0 "'$coldefault'"
		  else if (/^(-|(deleted file|old) mode )/)
		    print "'$coldiffrem'" $0 "'$coldefault'"
		  else if (/^@@ \-[0-9]+(,[0-9]+)? \+[0-9]+(,[0-9]+)? @@/)
		    print gensub(/^(@@[^@]*@@)([ \t]*)(.*)/,
		         "'$coldiffhunk'" "\\1" "'$coldefault'" \
			 "\\2" \
			 "'$coldiffctx'" "\\3" "'$coldefault'", "")
		  else
		    print
		}'
	else
		cat
	fi
}


id1=" "
id2=" "
parent=
opt_color=
mergebase=
opt_summary=

while optparse; do
	if optparse -c; then
		opt_color=1
		setup_colors
	elif optparse -p; then
		parent=1
	elif optparse -s; then
		opt_summary=1
	elif optparse -r=; then
		if echo "$OPTARG" | fgrep -q '..'; then
			id2=${OPTARG#*..}
			[ "$id2" ] || log_end="HEAD"
			id1=${OPTARG%..*}
		elif echo "$OPTARG" | grep -q ':'; then
			id2=${OPTARG#*:}
			[ "$id2" ] || log_end="HEAD"
			id1=${OPTARG%:*}
		elif [ "$id1" = " " ]; then
			id1="$OPTARG"
		else
			id2="$OPTARG"
		fi
	elif optparse -m; then
		mergebase=1
	else
		optfail
	fi
done

if [ "$parent" ]; then
	id2="$id1"
	id="$id2"; [ "$id" = " " ] && id=""

	ids=$(cg-object-id -p "$id")
	[ "$(echo "$ids" | wc -l)" -gt 1 ] && \
		echo "Warning: Choosing the first parent of a merge commit. This may not be what you want." >&2
	id1=$(echo "$ids" | head -n 1) || exit 1

	[ "$id1" ] || exit 1
fi

if [ "$mergebase" ]; then
	[ "$id1" != " " ] || id1="HEAD"
	[ "$id2" != " " ] || id2="origin"
	id1=$(cg-object-id -c "$id1") || exit 1
	id2=$(cg-object-id -c "$id2") || exit 1
	conservative_merge_base $id1 $id2 || exit 1
	[ "$_cg_base_conservative" ] &&
		echo -e "Warning: Multiple merge bases, picking the most conservative one\a" >&2
	id1=$_cg_baselist
fi


filter=$(mktemp -t gitdiff.XXXXXX)
[ "$_git_relpath" -a ! "$ARGS" ] && echo "$_git_relpath" >>$filter
for file in "${ARGS[@]}"; do
	echo "${_git_relpath}$file" >>$filter
done

if [ "$id2" = " " ]; then
	if [ "$id1" != " " ]; then
		tree=$(cg-object-id -t "$id1") || exit 1
	else
		tree=$(cg-object-id -t) || exit 1
	fi

	# Ensure to only diff modified files
	git-update-index --refresh >/dev/null

	# FIXME: Update ret based on what did we match. And take "$@"
	# to account after all.
	#ret=
	# xargs on FreeBSD is hardcoded to --no-run-if-empty :/
	if [ -s "$filter" ]; then
		cat $filter | path_xargs git-diff-index -r -p $tree | colorize | pager
	else
		git-diff-index -r -p $tree | colorize | pager
	fi

	rm $filter

	#[ "$ret" ] && die "no files matched"
	#exit $ret
	exit 0
fi


id1=$(cg-object-id -t "$id1") || exit 1
id2=$(cg-object-id -t "$id2") || exit 1

[ "$id1" = "$id2" ] && exit 0

# xargs on FreeBSD is hardcoded to --no-run-if-empty :/
if [ -s "$filter" ]; then
	cat $filter | path_xargs git-diff-tree -r -p $id1 $id2 | colorize | pager
else
	git-diff-tree -r -p $id1 $id2 | colorize | pager
fi

rm $filter
exit 0
