#!/usr/bin/env bash

# Copyright (C) 2012-2013 Pacman Development Team <pacman-dev@archlinux.org>
# Copyright (C) 2012-2013, 2017 Luke Shumaker <lukeshu@parabola.nu>
#
# License: GNU GPLv2+
#
# This program 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, see <http://www.gnu.org/licenses/>.

. "$(librelib messages)"

# from makepkg
dir_is_empty() {
	(
		shopt -s dotglob nullglob
		files=("$1"/*)
		(( ${#files} == 0 ))
	)
}

# from makepkg 
cd_safe() {
	if ! cd "$1"; then
		error "Failed to change to directory %s" "$1"
		plain "Aborting..."
		exit $EXIT_FAILURE
	fi
}

# from makepkg
download_git_checkout() {
	local url=$1
	local ref=$2
	local dir=$3
	local name=$4
	local push=${5:-}

	if [[ ! -d "$dir/.git" ]] ; then
		msg2 "Cloning %s %s repo..." "${name}" "git"
		if ! git clone "$url" "$dir"; then
			error "Failure while downloading %s %s repo" "${name}" "git"
			plain "Aborting..."
			exit $EXIT_FAILURE
		fi
		cd_safe "$dir"
		if [[ -n $push ]]; then
			git config remote.origin.pushUrl "$push"
		fi
		git checkout "$ref"
	else
		cd_safe "$dir"
		# Make sure we are fetching the right repo
		if [[ "$url" != "$(git config --get remote.origin.url)"  ]] ; then
			if $FORCE; then
				git config remote.origin.url "$url"
			else
				error "%s is not a clone of %s" "$dir" "$url"
				plain "Aborting..."
				exit $EXIT_FAILURE
			fi
		fi
		if [[ -n $push ]] ; then
			if $FORCE; then
				git config remote.origin.pushUrl "$push"
			else
				local curpush
				if ! curpush="$(git config --get remote.origin.pushUrl)"; then
					error "%s does not have a %s configured" pushUrl "$name"
					plain "Aborting..."
					exit $EXIT_FAILURE
				elif [[ $curpush != "$push" ]]; then
					error "%s has %s configured, but it doesn't match %s" "$name" pushUrl "$push"
					plain "Aborting..."
					exit $EXIT_FAILURE
				fi
			fi
		fi
		msg2 "Updating %s %s repo..." "${name}" "git"
		if ! { git fetch --all -p && git checkout "$ref" && git pull origin "$ref"; } ; then
			# only warn on failure to allow offline builds
			warning "Failure while updating %s %s repo" "${name}" "git"
		fi
	fi
}

# from makepkg
download_git_bare() {
	local url=$1
	local dir=$2
	local name=$3
	local push=${4:-}

	if [[ ! -d "$dir" ]] || dir_is_empty "$dir" ; then
		msg2 "Cloning %s %s repo..." "${name}" "git"
		if ! git clone --mirror "$url" "$dir"; then
			error "Failure while downloading %s %s repo" "${name}" "git"
			plain "Aborting..."
			exit $EXIT_FAILURE
		fi
		if [[ -n $push ]]; then
			cd_safe "$dir"
			git config remote.origin.pushUrl "$push"
		fi
	else
		cd_safe "$dir"
		# Make sure we are fetching the right repo
		if [[ "$url" != "$(git config --get remote.origin.url)"  ]] ; then
			if $FORCE; then
				git config remote.origin.url "$url"
			else
				error "%s is not a clone of %s" "$dir" "$url"
				plain "Aborting..."
				exit $EXIT_FAILURE
			fi
		fi
		if [[ -n $push ]] ; then
			if $FORCE; then
				git config remote.origin.pushUrl "$push"
			else
				local curpush
				if ! curpush="$(git config --get remote.origin.pushUrl)"; then
					error "%s does not have a %s configured" pushUrl "$name"
					plain "Aborting..."
					exit $EXIT_FAILURE
				elif [[ $curpush != "$push" ]]; then
					error "%s has %s configured, but it doesn't match %s" "$name" pushUrl "$push"
					plain "Aborting..."
					exit $EXIT_FAILURE
				fi
			fi
		fi
		msg2 "Updating %s %s repo..." "${name}" "git"
		if ! git fetch --all -p; then
			# only warn on failure to allow offline builds
			warning "Failure while updating %s %s repo" "${name}" "git"
		fi
	fi
}

usage() {
	print 'Usage: %s [OPTIONS] [bare|checkout] URL DIRECTORY' "${0##*/}"
	print 'A URL-handler for git urls.  Capable of updating or cloning.'
	echo
	prose "Clones or pulls from the git URL, to a local DIRECTORY.  If
	       \`bare\` is specified, it will create a bare repository; if
	       \`checkout\` is specified, it will create or update a working tree."
	echo
	prose 'For a checkout, the tree to checkout is specified by appending
	       `#ANYTHING=TREE-ISH` to the URL.  For example, `#branch=stable`,
	       or `#tag=v12.3`.  Whatever is on the left side of the equal sign
	       is ignored, this is for compatibility with `makepkg` source
	       URLs.'
	echo
	prose "The URL may be prefixed with \`git+\`.  This is also for
	       compatibility with \`makepkg\` source URLs."
	echo
	prose "It does safety checks, figures out whether to clone or pull, and
	       other helpful things.  This exists because the same
	       \`download_git\` function from makepkg was being copied and
	       pasted again and again."
	echo
	print "Options:"
	flag '-f' \
		'Instead of checking to make sure configured URLs match, force
		 the update, and set the URLs.'
	flag "-p $(_ URL)" \
		'In addition to setting or checking `remotes.origin.url`, also
		 set or check `remotes.origin.pushUrl`'
	flag "-n $(_ NAME)" \
		'In messages, instead of using the basename of DIRECTORY as the
		 repository name, use NAME'
	flag '-h' 'Show this message'
}

FORCE=false
main() {
	local push=''
	local name=''
	while getopts 'fp:n:h' flag; do
		case "${flag}" in
			f) FORCE=true;;
			p) push=$OPTARG;;
			n) name=$OPTARG;;
			h) usage; exit $EXIT_SUCCESS;;
			*) usage >&2; exit $EXIT_INVALIDARGUMENT;;
		esac
	done
	shift $((OPTIND - 1))
	[[ $# == 3 ]] || { usage >&2; exit $EXIT_INVALIDARGUMENT; }
	local mode=$1
	local url=${2#git+}
	local dir=$3

	local urlmain=${url%%#*}
	local urlfrag=${url#*#}
	if [[ "$urlfrag" == "$urlmain" ]]; then
		urlfrag=''
	fi
	local ref=${urlfrag#*=}

	if [[ -z $ref ]]; then
		ref=master
	fi

	name=${name:-${dir##*/}}

	case "$mode" in
		checkout) download_git_checkout "$urlmain" "$ref" "$dir" "$name" "$push";;
		bare) download_git_bare "$urlmain" "$dir" "$name" "$push";;
		*) usage >&2; exit $EXIT_INVALIDARGUMENT;;
	esac
}

main "$@"
