#!/usr/bin/env bash
#
# Make a log of changes in a GIT branch.
# Copyright (c) Petr Baudis, 2005.
# Copyright (c) David Woodhouse, 2005.
#
# Display log information for files or a range of commits. The output
# will automatically be displayed in a pager unless it is piped to
# a program.
#
# OPTIONS
# -------
# Arguments not interpreted as options will be interpreted as filenames;
# cg-log then displays only changes in those files.
#
# -c::
#	Add color to the output. Currently, the colors are:
#	- `author`:	'cyan'
#	- `committer`:	'magenta'
#	- `header`:	'green'
#	- `files`:	'blue'
#	- `signoff`:	'yellow'
#
# -f::
#	List affected files.
#
# -r FROM_ID[:TO_ID]::
#	Limit the log information to a set of revisions using either
#	'-r FROM_ID[:TO_ID]' or '-r FROM_ID -r TO_ID'. In both cases the
#	option expects IDs which resolve to commits and will include the
#	specified IDs. If 'TO_ID' is omitted all commits from 'FROM_ID'
#	to the initial commit is shown. If no revisions is specified,
#	the log information starting from 'HEAD' will be shown.
#
# -m::
#	End the log listing at the merge base of the -r arguments
#	(defaulting to master and origin).
#
# -uUSERNAME::
#	List only commits where author or committer contains 'USERNAME'.
#	The search for 'USERNAME' is case-insensitive.
#
# ENVIRONMENT VARIABLES
# ---------------------
# PAGER::
#	The pager to display log information in, defaults to `less`.
#
# PAGER_FLAGS::
#	Flags to pass to the pager, defaults to `--R`
#
# EXAMPLE USAGE
# -------------
# To show a log of changes between two releases tagged as 'releasetag-0.9'
# and 'releasetag-0.10' do:
#
#	$ cg-log -r releasetag-0.9:releasetag-0.10

USAGE="cg-log [-c] [-f] [m] [-uUSERNAME] [-r FROM_ID[:TO_ID] FILE..."

. ${COGITO_LIB}cg-Xlib
# Try to fix the annoying "Broken pipe" output. May not help, but apparently
# at least somewhere it does. Bash is broken.
trap exit SIGPIPE

colheader=
colauthor=
colcommitter=
colfiles=
colsignoff=
coldefault=
list_files=
log_start=
log_end=
user=
mergebase=

while [ "$1" ]; do
	case "$1" in
	-c)
		# See terminfo(5), "Color Handling"
		colheader="$(tput setaf 2)"    # Green
		colauthor="$(tput setaf 6)"    # Cyan
		colcommitter="$(tput setaf 5)" # Magenta
		colfiles="$(tput setaf 4)"     # Blue
		colsignoff="$(tput setaf 3)"   # Yellow
		coldefault="$(tput op)"        # Restore default
		;;
	-f)
		list_files=1
		;;
	-u*)
		user="${1#-u}"
		;;
	-r)
		shift
		if echo "$1" | grep -q ':'; then
			log_end=$(echo "$1" | cut -d : -f 2)
			[ "$log_end" ] || log_end="HEAD"
			log_start=$(echo "$1" | cut -d : -f 1)
		elif [ -z "$log_start" ]; then
			log_start="$1"
		else
			log_end="$1"
		fi
		;;
	-m)
		mergebase=1
		;;
	*)
		break
		;;
	esac
	shift
done

list_commit_files()
{
	tree1="$1"
	tree2="$2"
	line=
	sep="    * $colfiles"
	# List all files for for the initial commit
	if [ -z $tree2 ]; then
		list_cmd="git-ls-tree $tree1"
	else
		list_cmd="git-diff-tree -r $tree1 $tree2"
	fi
	echo
	$list_cmd | cut -f 2- | while read file; do
		echo -n "$sep"
		sep=", "
		if [ $(echo "$line$sep$file" | wc -c) -le 75 ]; then
			line="$line$sep$file"
			echo -n "$file"
		else
			line="      $file"
			echo "$coldefault"
			echo -n "      $colfiles$file"
		fi
	done
	echo "$coldefault:"
}

if [ "$mergebase" ]; then
	[ "$log_start" ] || log_start="master"
	[ "$log_end" ] || log_end="origin"
	log_start=$(git-merge-base $(commit-id $log_start) $(commit-id $log_end))
fi

if [ "$log_end" ]; then
	id1="$(commit-id $log_start)" || exit 1
	id2="$(commit-id $log_end)" || exit 1
	revls="git-rev-tree $id2 ^$id1"
	revsort="sort -rn"
	revfmt="git-rev-tree"
else
	id1="$(commit-id $log_start)" || exit 1
	revls="git-rev-list $id1"
	revsort="cat"
	revfmt="git-rev-list"
fi

$revls | $revsort | while read time commit parents; do
	trap exit SIGPIPE
	tree1=
	tree2=
	[ "$revfmt" = "git-rev-list" ] && commit="$time"
	if [ $# -ne 0 ]; then
		parent=$(git-cat-file commit $commit | sed -n '2s/parent //p;2Q')
		diff_ops=
		[ "$parent" ] || diff_ops=--root
		[ "$(git-diff-tree -r $diff_ops $commit $parent "$@")" ] || continue
	fi
	if [ "$user" ]; then
		git-cat-file commit $commit | grep -e '^author ' -e '^committer ' | grep -qi "$user" || continue
	fi
	echo $colheader""commit ${commit%:*} $coldefault;
	git-cat-file commit $commit | \
		while read key rest; do
			trap exit SIGPIPE
			case "$key" in
			"author"|"committer")
				if [ "$key" = "author" ]; then
					color="$colauthor"
				else
					color="$colcommitter"
				fi

				date=(${rest#*> })
				pdate="$(showdate $date)"
				if [ "$pdate" ]; then
					echo -n $color$key $rest | sed "s/>.*/> $pdate/"
					echo $coldefault
				else
					echo $color$key $rest $coldefault
				fi
				;;
			"tree"|"parent")
				if [ -z $tree1 ]; then
					tree1=$rest
				elif [ -z $tree2 ]; then
					tree2=$rest
				fi
				echo $colheader$key $rest $coldefault
				;;
			"")
				if [ -n "$list_files" ]; then
					list_commit_files "$tree1" "$tree2"
				fi
				echo; sed -re '
					/ *Signed-off-by:.*/Is//'$colsignoff'&'$coldefault'/
					/ *Acked-by:.*/Is//'$colsignoff'&'$coldefault'/
					s/./    &/
				'
				;;
			*)
				echo $colheader$key $rest $coldefault
				;;
			esac

		done
	echo
done | LESS="R$LESS" ${PAGER:-less} ${PAGER_FLAGS}
