#!/bin/bash
#
# SPDX-License-Identifier: GPL-3.0-or-later

m4_include(lib/common.sh)

check_pkgbuild_validity() {
	# shellcheck source=contrib/makepkg/PKGBUILD.proto
	. ./PKGBUILD

	# skip when there are no sources available
	if (( ! ${#source[@]} )); then
		return
	fi

	# validate sources hash algo is at least > sha1
	local bad_algos=("cksums" "md5sums" "sha1sums")
	local good_hash_algo=false

	# from makepkg libmakepkg/util/schema.sh
	for integ in "${known_hash_algos[@]}"; do
		local sumname="${integ}sums"
		if [[ -n ${!sumname} ]] && ! in_array "${sumname}" "${bad_algos[@]}"; then
			good_hash_algo=true
			break
		fi
	done

	if ! $good_hash_algo; then
		die "PKGBUILD lacks a secure cryptographic checksum, insecure algorithms: ${bad_algos[*]}"
	fi
}

# Source makepkg.conf; fail if it is not found
if [[ -r '/etc/makepkg.conf' ]]; then
	# shellcheck source=config/makepkg/x86_64.conf
	source '/etc/makepkg.conf'
else
	die '/etc/makepkg.conf not found!'
fi

# Source user-specific makepkg.conf overrides
if [[ -r "${XDG_CONFIG_HOME:-$HOME/.config}/pacman/makepkg.conf" ]]; then
	# shellcheck source=/dev/null
	source "${XDG_CONFIG_HOME:-$HOME/.config}/pacman/makepkg.conf"
elif [[ -r "$HOME/.makepkg.conf" ]]; then
	# shellcheck source=/dev/null
	source "$HOME/.makepkg.conf"
fi

cmd=${0##*/}

if [[ ! -f PKGBUILD ]]; then
	die 'No PKGBUILD file'
fi

if [[ "$(git symbolic-ref --short HEAD)" != main ]]; then
	die 'must be run from the main branch'
fi

source=()
# shellcheck source=contrib/makepkg/PKGBUILD.proto
. ./PKGBUILD
pkgbase=${pkgbase:-$pkgname}

case "$cmd" in
	commitpkg)
		if (( $# == 0 )); then
			die 'Usage: commitpkg <reponame> [-f] [-s server] [-l limit] [-a arch] [commit message]'
		fi
		repo="$1"
		shift
		;;
	*pkg)
		repo="${cmd%pkg}"
		;;
	*)
		die 'Usage: commitpkg <reponame> [-f] [-s server] [-l limit] [-a arch] [commit message]'
		;;
esac


if (( ${#validpgpkeys[@]} != 0 )); then
	if [[ -d keys ]]; then
		for key in "${validpgpkeys[@]}"; do
			if [[ ! -f keys/pgp/$key.asc ]]; then
				export-pkgbuild-keys || die 'Failed to export valid PGP keys for source files'
			fi
		done
	else
		export-pkgbuild-keys || die 'Failed to export valid PGP keys for source files'
	fi

	svn add --parents --force keys/pgp/*
fi

# find files which should be under source control
needsversioning=()
for s in "${source[@]}"; do
	[[ $s != *://* ]] && needsversioning+=("$s")
done
for i in 'changelog' 'install'; do
	while read -r file; do
		# evaluate any bash variables used
		eval "file=\"$(sed "s/^\(['\"]\)\(.*\)\1\$/\2/" <<< "$file")\""
		needsversioning+=("$file")
	done < <(sed -n "s/^[[:space:]]*$i=//p" PKGBUILD)
done
for key in "${validpgpkeys[@]}"; do
	needsversioning+=("keys/pgp/$key.asc")
done

# assert that they really are controlled by git
if (( ${#needsversioning[*]} )); then
	for file in "${needsversioning[@]}"; do
		if ! git ls-files --error-unmatch "$file"; then
			die "%s is not under version control" "$file"
		fi
	done
fi

rsyncopts=(-e ssh -p '--chmod=ug=rw,o=r' -c -h -L --progress --partial -y)
archreleaseopts=()
while getopts ':l:a:s:f' flag; do
	case $flag in
		f) archreleaseopts+=('-f') ;;
		s) server=$OPTARG ;;
		l) rsyncopts+=("--bwlimit=$OPTARG") ;;
		a) commit_arch=$OPTARG ;;
		:) die "Option requires an argument -- '%s'" "$OPTARG" ;;
		\?) die "Invalid option -- '%s'" "$OPTARG" ;;
	esac
done
shift $(( OPTIND - 1 ))

# check packages for validity
for _arch in "${arch[@]}"; do
	if [[ -n $commit_arch && ${_arch} != "$commit_arch" ]]; then
		continue
	fi
	for _pkgname in "${pkgname[@]}"; do
		fullver=$(get_full_version "$_pkgname")

		if pkgfile=$(find_cached_package "$_pkgname" "$fullver" "$_arch"); then
			check_package_validity "$pkgfile"
		fi
	done

	fullver=$(get_full_version "$pkgbase")
	if pkgfile=$(find_cached_package "$pkgbase-debug" "$fullver" "$_arch"); then
		check_package_validity "$pkgfile"
	fi
done

# check for PKGBUILD standards
check_pkgbuild_validity

if [[ -z $server ]]; then
	server='repos.archlinux.org'
fi

if [[ -n $(git status --short --untracked-files=no) ]]; then
	stat_busy 'Staging files'
	for f in $(git ls-files --modified); do
		git add "$f"
	done
	for f in $(git ls-files --deleted); do
		git rm "$f"
	done
	stat_done

	msgtemplate="upgpkg: $pkgbase $(get_full_version)"
	if [[ -n $1 ]]; then
		stat_busy 'Committing changes'
		git commit -q -m "${msgtemplate}: ${1}" || die
		stat_done
	else
		[[ -z ${WORKDIR:-} ]] && setup_workdir
		msgfile=$(mktemp --tmpdir="${WORKDIR}" commitpkg.XXXXXXXXXX)
		echo "$msgtemplate" > "$msgfile"
		if [[ -n $GIT_EDITOR ]]; then
			$GIT_EDITOR "$msgfile" || die
		elif [[ -n $VISUAL ]]; then
			$VISUAL "$msgfile" || die
		elif [[ -n $EDITOR ]]; then
			$EDITOR "$msgfile" || die
		else
			vi "$msgfile" || die
		fi
		[[ -s $msgfile ]] || die
		stat_busy 'Committing changes'
		git commit -v -q -F "$msgfile" || die
		unlink "$msgfile"
		stat_done
	fi
fi

declare -a uploads
declare -a commit_arches
declare -a skip_arches

for _arch in "${arch[@]}"; do
	if [[ -n $commit_arch && ${_arch} != "$commit_arch" ]]; then
		skip_arches+=("$_arch")
		continue
	fi

	for _pkgname in "${pkgname[@]}"; do
		fullver=$(get_full_version "$_pkgname")
		if ! pkgfile=$(find_cached_package "$_pkgname" "$fullver" "${_arch}"); then
			warning "Skipping %s: failed to locate package file" "$_pkgname-$fullver-$_arch"
			skip_arches+=("$_arch")
			continue 2
		fi
		uploads+=("$pkgfile")
	done

	fullver=$(get_full_version "$pkgbase")
	if ! pkgfile=$(find_cached_package "$pkgbase-debug" "$fullver" "$_arch"); then
		continue
	fi
	if ! is_debug_package "$pkgfile"; then
		continue
	fi
	uploads+=("$pkgfile")
done

for pkgfile in "${uploads[@]}"; do
	sigfile="${pkgfile}.sig"
	if [[ ! -f $sigfile ]]; then
		msg "Signing package %s..." "${pkgfile}"
		if [[ -n $GPGKEY ]]; then
			SIGNWITHKEY=(-u "${GPGKEY}")
		fi
		gpg --detach-sign --use-agent --no-armor "${SIGNWITHKEY[@]}" "${pkgfile}" || die
	fi
	if ! gpg --verify "$sigfile" "$pkgfile" >/dev/null 2>&1; then
		die "Signature %s is incorrect!" "$sigfile"
	fi
	uploads+=("$sigfile")
done

for _arch in "${arch[@]}"; do
	if ! in_array "$_arch" "${skip_arches[@]}"; then
		commit_arches+=("$_arch")
	fi
done

if [[ ${#commit_arches[*]} -gt 0 ]]; then
	archrelease "${archreleaseopts[@]}" "${commit_arches[@]/#/$repo-}" || die
fi

if [[ ${#uploads[*]} -gt 0 ]]; then
	new_uploads=()

	# convert to absolute paths so rsync can work with colons (epoch)
	while read -r -d '' upload; do
		new_uploads+=("$upload")
	done < <(realpath -z "${uploads[@]}")

	uploads=("${new_uploads[@]}")
	unset new_uploads
	msg 'Uploading all package and signature files'
	rsync "${rsyncopts[@]}" "${uploads[@]}" "$server:staging/$repo/" || die
fi