#!/usr/bin/env bash
#
# Common code shared by the Cogito toolkit.
# Copyright (c) Petr Baudis, 2005
#
# This file provides a library containing common code shared with all the
# Cogito programs.

_cg_cmd=${0##*/}

_git=${GIT_DIR:-.git}
_git_objects=${GIT_OBJECT_DIRECTORY:-$_git/objects}


die () {
	echo $_cg_cmd: $@ >&2
	exit 1
}

usage() {
	die "usage: $USAGE"
}

pager () {
	local line
	# Invoke pager only if there's any actual output
	if read -r line; then
		{ echo "$line"; cat; } | LESS="R$LESS" ${PAGER:-less} $PAGER_FLAGS
	fi
}

mktemp () {
	if [ ! "$BROKEN_MKTEMP" ]; then
		$(which mktemp) "$@"
		return
	fi

	dirarg=
	if [ x"$1" = x"-d" ]; then
		dirarg="-d"
		shift
	fi
	prefix=
	if [ x"$1" = x"-t" ]; then
		prefix=${TMPDIR:-/tmp}/
		shift
	fi

	$(which mktemp) $dirarg $prefix"$1"
}

stat () {
	if [ "$1" != "-c" ] || [ "$2" != "%s" ]; then
		echo "INTERNAL ERROR: Unsupported stat call $@" >&2
		return 1
	fi
	if [ "$HAVE_STAT" ]; then
		$(which stat) "$@"
		return
	fi

	# It's always -c '%s' now.
	ls -l "$3" | awk '{ print $5 }'
}

showdate () {
	local secs=$1 tzhours=${2:0:3} tzmins=${2:0:1}${2:3} format="$3"
	# bash doesn't like leading zeros
	[ "${tzhours:1:1}" = 0 ] && tzhours=${2:0:1}${2:2:1}
	secs=$(($secs + $tzhours * 3600 + $tzmins * 60))
	if [ "$has_gnudate" ]; then
		[ "$format" ] || format=-R
		pdate=$(date -ud "1970-01-01 UTC + $secs sec" "$format")
	else
		[ "$format" ] || format=--
		pdate=$(date -u -r $secs "$format")
	fi
	echo "${pdate/+0000/$tz}"
}

# Usage: tree_timewarp [--no-head-update] DIRECTION_STR ROLLBACK_BOOL BASE BRANCH
tree_timewarp () {
	local no_head_update=
	if [ "$1" = "--no-head-update" ]; then
		no_head_update=1
		shift
	fi
	local dirstr=$1; shift
	local rollback=$1; shift
	local base=$1; shift
	local branch=$1; shift

	local patchfile=$(mktemp -t gituncommit.XXXXXX)
	if [ "$rollback" ]; then
		cg-diff >$patchfile
		[ -s "$patchfile" ] &&
			echo "Warning: uncommitted local changes, trying to bring them $dirstr" >&2
	else
		# XXX: This may be suboptimal, but it is also non-trivial to keep
		# the adds/removes properly.  So this is just a quick hack to get it
		# working without much fuss.
		cg-diff -r $branch >$patchfile
	fi

	git-read-tree -m "$branch" || die "$branch: bad commit"
	[ "$no_head_update" ] || echo "$branch" > $_git/HEAD

	# Kill gone files
	git-diff-tree -z -r $base $branch | xargs -0 bash -c '
		while [ "$1" ]; do
			header="$1"; shift
			file="$1"; shift

			# match ":100755 000000 14d43b1abf... 000000000... D"
			if echo "$header" | egrep "^:([^ ][^ ]* ){4}D" >/dev/null; then
				rm -- "$file"
			fi
		done
	' padding
	git-checkout-cache -f -a

	# FIXME: Can produce bogus "contains only garbage" messages.
	cat $patchfile | cg-patch
	rm $patchfile

	git-update-cache --refresh >/dev/null
}

update_index () {
	git-update-cache --refresh | sed 's/needs update$/locally modified/'
}


print_help () {
	which "cg-$1" >/dev/null 2>&1 || exit 1
	sed -n '/^USAGE=/,0s/.*"\(.*\)"/Usage: \1/p' < $(which cg-$1) 
	echo
	cat $(which cg-$1) | sed -n '3,/^$/s/^# *//p'
	exit
}

for option in "$@"; do
	[ "$option" = -- ] && break
	if [ "$option" = "-h" -o "$option" = "--help" ]; then
		print_help ${_cg_cmd##cg-}
	fi
done


ARGS=("$@")
ARGPOS=0

optshift() {
	unset ARGS[$ARGPOS]
	ARGS=("${ARGS[@]}")
	[ -z "$1" -o -n "${ARGS[$ARGPOS]}" ] ||
		die "option \`$1' requires an argument"
}

optfail() {
	die "unrecognized option \`${ARGS[$ARGPOS]}'"
}

optconflict() {
	die "conflicting option \`$CUROPT'"
}

optparse() {
	unset OPTARG
	if [ -z "$1" ]; then
		case ${ARGS[$ARGPOS]} in
		--)	optshift; return 1 ;;
		-*)	return 0 ;;
		*)	while (( ++ARGPOS < ${#ARGS[@]} )); do
				[[ "${ARGS[$ARGPOS]}" == -- ]] && return 1
				[[ "${ARGS[$ARGPOS]}" == -* ]] && return 0
			done;
			return 1 ;;
		esac
	fi

	CUROPT=${ARGS[$ARGPOS]}
	local match=${1%=} minmatch=${2:-1} opt=$CUROPT o=$CUROPT val
	[[ "$1" == *= ]] && val=$match
	case $match in
	--*)
		[ "$val" ] && o=${o%%=*}
		[ ${#o} -ge $((2 + $minmatch)) -a \
			"${match:0:${#o}}" = "$o" ] || return 1
		[[ -n "$val" && "$opt" == *=?* ]] \
			&& ARGS[$ARGPOS]=${opt#*=} \
			|| optshift $val ;;
	-?)
		[[ "$o" == $match* ]] || return 1
		[[ "$o" != -?-* || -n "$val" ]] || optfail
		ARGS[$ARGPOS]=${o#$match}
		[ "${ARGS[$ARGPOS]}" ] \
			&& { [ "$val" ] || ARGS[$ARGPOS]=-${ARGS[$ARGPOS]}; } \
			|| optshift $val ;;
	*)
		die "optparse cannot handle $1" ;;
	esac

	if [ "$val" ]; then
		OPTARG=${ARGS[$ARGPOS]}
		optshift
	fi
}


# Check if we have something to work on, unless the script can do w/o it.
if [ ! "$_git_repo_unneeded" ]; then
	if [ ! -d "$_git" ]; then
	       echo "There is no GIT repository here ($_git not found)" >&2
	       exit 1
	elif [ ! -x "$_git" ]; then
	       echo "You do not have permission to access this GIT repository" >&2
	       exit 1
	fi
fi


# Compatibility hacks:
# 2005-04-26
if [ -d $_git ] && [ -s $_git/remotes ] && [ ! -e $_git/branches ]; then
	stop=
	mkdir $_git/branches || stop=1
	[ "$stop" ] || cat $_git/remotes | while read branch location; do
		echo "$location" >$_git/branches/$branch
	done || stop=1
	[ "$stop" ] || rm $_git/remotes
fi
# 2005-04-26
if [ -d $_git ] && [ ! -d $_git/refs ]; then
	stop=
	mkdir $_git/refs || stop=1
	if [ ! "$stop" ]; then
		mv $_git/heads $_git/refs
		mv $_git/tags $_git/refs
		tgt=$(readlink $_git/HEAD)
		if [ "$tgt" ]; then
			rm $_git/HEAD
			ln -s "refs/$tgt" $_git/HEAD
		fi
	fi
fi


export BROKEN_MKTEMP=1
del=$($(which mktemp) -t 2>/dev/null) && { rm $del; export BROKEN_MKTEMP=; }
export HAVE_STAT=
which stat >/dev/null 2>&1 && export HAVE_STAT=1
has_gnudate=$(date -Rud "1970-01-01 UTC" 2>/dev/null)
