#!/usr/bin/env bash
#
# Pull changes from a remote branch to the local GIT repository.
# Copyright (c) Petr Baudis, 2005.
#
# See `cg-branch-add` for some description.
#
# Takes the branch name as an argument, defaulting to "origin".
#
# OPTIONS
# -------
# -f::
#	Force the complete pull even if the heads are the same.

USAGE="cg-pull [-f] [BRANCH_NAME]"

. ${COGITO_LIB}cg-Xlib

force=
if [ "$1" = "-f" ]; then
	force=1
	shift
fi

name=$1


[ "$name" ] || { [ -s $_git/refs/heads/origin ] && name=origin; }
[ "$name" ] || die "where to pull from?"
uri=$(cat "$_git/branches/$name" 2>/dev/null) || die "unknown branch: $name"

rembranch=master
if echo "$uri" | grep -q '#'; then
	rembranch=$(echo $uri | cut -d '#' -f 2)
	uri=$(echo $uri | cut -d '#' -f 1)
fi

pull_progress() {
	percentage=""
	objects=0
	last_objects=0
	size=0

	while read line; do
		object=

		case "$line" in
		link*|symlink*|\
		"got "[a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9]*)
			# Convert the sha to an object path
			object=$(echo "$line" | sed 's,.* \([a-f0-9][a-f0-9]\),\1/,')
			;;

		[a-f0-9][a-f0-9]/[a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9]*)
			object="$line"
			# Estimate percentage done using the position of
			# the object subdir. It might not get all the way
			# up to 100% ...
			position=$(echo "$line" | cut -d/ -f 1)
			percentage=", $((0x$position * 100 / 0xff))% done"
			;;

		*)
			if [ "$last_objects" != "$objects" ]; then
				last_objects="$objects"
				echo;
			fi
			echo "$line"
			continue
			;;
		esac 

		object="$_git_objects/$object"
		size=$(($size + $(stat -c '%s' "$object" 2>/dev/null)))
		objects=$(($objects + 1));

		echo -ne "progress: $objects objects, $size bytes$percentage\r"
	done;
	[ "$last_objects" != "$objects" ] && echo
}

fetch_rsync () {
	redir=
	if [ "$1" = "-i" ]; then # ignore-errors
		redir="2>/dev/null"
		shift
	fi

	filter="cat"
	if [ "$1" = "-s" ]; then # subsequent
		# We already saw the MOTD, thank you very much.
		filter="grep -v ^MOTD:"
		shift
	fi

	rsync_flags_l=
	if [ "$1" = "-u" ]; then # update
		rsync_flags_l="--ignore-existing"
		shift
	fi

	appenduri=
	if [ "$1" = "-d" ]; then # directory
		appenduri="/." # CowboyNeal
		shift
	fi

	eval rsync $RSYNC_FLAGS $rsync_flags_l --whole-file -v -Lr \
		"$1$appenduri" "$2$appenduri" $redir | $filter
	return ${PIPESTATUS[0]}
}

pull_rsync () {
	fetch_rsync -s -u -d "$2/objects" "$_git_objects" | pull_progress
	return ${PIPESTATUS[0]}
}


fetch_http () {
	[ "$1" = "-i" ] && shift
	[ "$1" = "-s" ] && shift

	wget_flags="-nv"

	update=
	if [ "$1" = "-u" ]; then
		wget_flags="$wget_flags -nc"
		update=1
		shift
	fi

	directory=
	if [ "$1" = "-d" ]; then
		wget_flags="$wget_flags -r -l 1 -np -nd -P"
		directory=1
		shift
	else
		wget_flags="$wget_flags -O"
	fi

	src="$1"
	dest="$2"

	[ "$update" ] && ! [ "$directory" ] && [ -e "$dest" ] && return 1

	if echo "$dest" | grep -q '^\.git/'; then
		# wget is idiotic and convers . to _
		dest=$(echo "$dest" | sed 's#^.git/##')
		pushd $_git >/dev/null
	else
		pushd . >/dev/null
	fi

	wget $wget_flags "$dest" "$src" 2>&1 | grep -vF 'robots.txt' | grep -vF 'index.html'
	ret=${PIPESTATUS[0]}
	[ -d "$dest" ] && rm -f "$dest"/robots.txt "$dest"/index.html*
	popd >/dev/null
	return $ret
}

pull_http () {
	(git-http-pull -a -v "$(cat "$_git/refs/heads/$1")" "$2/" 2>&1 /dev/null) | pull_progress
	return ${PIPESTATUS[0]}
}


fetch_ssh () {
	[ "$1" = "-i" ] && shift
	[ "$1" = "-s" ] && shift

	scp_flags=

	update=
	if [ "$1" = "-u" ]; then
		update=1
		shift
	fi

	directory=
	if [ "$1" = "-d" ]; then
		scp_flags="$scp_flags -r"
		directory=1
		shift
	fi

	src=$(echo "$1" | sed 's#^git+ssh://\([^/]*\)\(/.*\)$#\1:\2#')
	dest="$2"

	[ "$update" ] && ! [ "$directory" ] && [ -e "$dest" ] && return 1
	[ "$update" ] && [ "$directory" ] &&
		echo "Warning: Unable to protect against overwriting $dest" 2>/dev/null

	echo scp $scp_flags "$dest" "$src"
	scp $scp_flags "$src" "$dest"
}

pull_ssh () {
	(git-ssh-pull -a -v "$(cat "$_git/refs/heads/$1")" "$2" 2>&1 /dev/null) | pull_progress
	return ${PIPESTATUS[0]}
}


fetch_local () {
	[ "$1" = "-i" ] && shift
	[ "$1" = "-s" ] && shift

	cp_flags_l="-va"
	if [ "$1" = "-u" ]; then
		cp_flags_l="$cp_flags_l -lu"
		shift
	fi

	cut_last=
	if [ "$1" = "-d" ]; then
		cut_last=1
		shift
	fi

	src="$1"
	dest="$2"
	[ "$cut_last" ] && dest=${dest%/*}

	cp $cp_flags_l "$src" "$dest"
}

pull_local () {
	(git-local-pull -a -l -v "$(cat "$_git/refs/heads/$1")" "$2" 2>&1 /dev/null) | pull_progress
	return ${PIPESTATUS[0]}
}

if echo "$uri" | grep -q "^http://"; then
	fetch=fetch_http
	pull=pull_http
elif echo "$uri" | grep -q "^git+ssh://"; then
	fetch=fetch_ssh
	pull=pull_ssh
elif echo "$uri" | grep -q ":"; then
	fetch=fetch_rsync
	pull=pull_rsync
else
	[ -d $uri/.git ] && uri=$uri/.git
	fetch=fetch_local
	pull=pull_local
fi


orig_head=
[ -s "$_git/refs/heads/$name" ] && orig_head=$(cat "$_git/refs/heads/$name")


mkdir -p $_git/refs/heads
rsyncerr=
$fetch -i "$uri/refs/heads/$rembranch" "$_git/refs/heads/$name" || rsyncerr=1
if [ "$rsyncerr" ]; then
	rsyncerr=
	$fetch -s "$uri/heads/$rembranch" "$_git/refs/heads/$name" || rsyncerr=1
fi
if [ "$rsyncerr" ] && [ "$rembranch" = "master" ]; then
	rsyncerr=
	$fetch -s "$uri/HEAD" "$_git/refs/heads/$name" || rsyncerr=1
fi
[ "$rsyncerr" ] && die "unable to get the head pointer of branch $rembranch"

new_head=$(cat "$_git/refs/heads/$name")
if [ ! "$force" ] && [ "$orig_head" = "$new_head" ]; then
	echo "Up to date."
	exit
fi

[ -d $_git_objects ] || mkdir -p $_git_objects
$pull "$name" "$uri" || die "objects pull failed"

# FIXME: Warn about conflicting tag names?
# XXX: We now throw stderr to /dev/null since not all repositories
# may have tags/ and users were confused by the harmless errors.
[ -d $_git/refs/tags ] || mkdir -p $_git/refs/tags
rsyncerr=
$fetch -i -s -u -d "$uri/refs/tags" "$_git/refs/tags" || rsyncerr=1
if [ "$rsyncerr" ]; then
	rsyncerr=
	$fetch -i -s -u -d "$uri/tags" "$_git/refs/tags" || rsyncerr=1
fi
[ "$rsyncerr" ] && echo "unable to get tags list (non-fatal)" >&2

# Now check if we have the objects pointed at by the tags; if they are
# tag objects, $pull didn't get them.
# XXX: This is the quick'n'dirty way.
(
	cd $_git/refs/tags
	for tag in *; do
		[ "$tag" = "*" ] && break
		tagid=$(cat $tag)
		tagfile=objects/${tagid:0:2}/${tagid:2}
		[ -s "../../$tagfile" ] && continue
		echo -n "Missing object of tag $tag... "
		if $fetch -i -s "$uri/$tagfile" "../../$tagfile" 2>/dev/null >&2; then
			echo "retrieved"
		else
			echo "different source (obsolete tag?)"
		fi
	done
)


if [ ! "$orig_head" ]; then
	echo "New branch: $new_head"

elif [ "$orig_head" != "$new_head" ]; then
	echo "Tree change: $orig_head:$new_head"
	git-diff-tree -r $(tree-id $orig_head) $(tree-id $new_head)

else
	echo "Up to date."
	exit
fi
