#!/usr/bin/env bash

# Copyright (C) 2012-2013 Pacman Development Team <pacman-dev@archlinux.org>
# Copyright (C) 2012-2013, 2017, 2024 Luke T. 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/>.

export TEXTDOMAIN='gitget'
. "$(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 'An 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.' \
		"-p $(_ URL)" 'In addition to setting or checking `remotes.origin.url`, also set or check `remotes.origin.pushUrl`' \
		"-n $(_ NAME)" 'In messages, instead of using the basename of DIRECTORY as the repository name, use NAME' \
		'-h, --help' 'Show this message'
}

FORCE=false
main() {
	local mode='uninitialized'
	local push=''
	local name=''
	local url='uninitialized'
	local dir='uninitialized'
	local args
	if ! args="$(getopt -n "${0##*/}" -o 'fp:n:h' -l 'help' -- "$@")"; then
		mode=errusage
	else
		eval "set -- $args"
		local flag
		while true; do
			flag=$1
			shift
			case "$flag" in
				-f) FORCE=true ;;
				-p)
					push=$1
					shift
					;;
				-n)
					name=$1
					shift
					;;
				-h | --help) mode=usage ;;
				--) break ;;
				*) panic 'unhandled flag: %q' "$flag" ;;
			esac
		done
		if [[ $mode == 'uninitialized' ]]; then
			if [[ $# != 3 ]]; then
				gnuerror 'expected 3 positional arguments, got %d' "$#"
				mode=errusage
			else
				mode=$1
				url=${2#git+}
				dir=$3
				if [[ $mode != checkout && $mode != bare ]]; then
					printf '%s: unknown mode: %q' "${0##*/}" "$mode" >&2
					mode=errusage
				fi
			fi
		fi
	fi
	case "$mode" in
		errusage)
			print "Try '%s --help' for more information." "${0##*/}" >&2
			return $EXIT_INVALIDARGUMENT
			;;
		usage)
			usage
			return $EXIT_SUCCESS
			;;
		checkout | bare) : ;;
		*) panic 'invalid mode: %q' "$mode" ;;
	esac

	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" ;;
		*) panic 'invalid mode: %q' "$mode" ;;
	esac
}

main "$@"
