#!/bin/sh

set -Ceu -o pipefail

: ${reporoot:=/repo}
: ${tmpreporoot:=/repo-tmp}
: ${bundleroot:=/repo-bundles}
: ${https_name:=}
: ${DRY_RUN:=false}

case $DRY_RUN in
true|false)
	;;
*)	printf 'Invalid dry run: %s\n' "$DRY_RUN" >&2
	exit 1
	;;
esac

run()
{
	local cmdline arg qarg

	if $DRY_RUN; then
		cmdline='run:'
		for arg; do
			case $arg in
			''|*[^[:alnum:]+,-./:=_@]*)
				;;
			*)
				cmdline="$cmdline $arg"
				continue
				;;
			esac
			qarg=$(printf '%s' "$arg" | sed -e "s/'/'\\\''/g")
			cmdline="$cmdline '$qarg'"
		done
		echo "$cmdline"
	else
		"$@"
	fi
}

bundlepath()
{
	local repo bundlename

	repo=$1
	bundlename=$2

	if false; then			# mercurial>=7.0
		echo "${bundleroot}/${repo}/${bundlename}"
	else
		echo "${repo}/.hg/bundle-cache/${bundlename}"
	fi
}

manifest()
{
	local path

	path=$1

	if $DRY_RUN; then
		sed -e 's,^,manifest: ,'
	else
		cat >>$path
	fi
}

make_bundle()
{
	local repo="$1"
	local repodir="${reporoot}/${repo}"
	local bundlespec="$2"
	local bundlename="$3"
	local complevel="$4"
	local extraspec="$5"
	local bundlepath="$(bundlepath "$repo" "$bundlename")"

	run mkdir -p "$(dirname "${bundlepath}")"
	run hg bundle -R "${tmpreporoot}/${repo}" \
	    -t "${bundlespec}" --all --hidden \
	    "${bundlepath}.tmp" \
	    --config experimental.bundlecomplevel="${complevel}" \
	    --config server.bundle2.stream=yes \
	    --config server.uncompressed=yes \
	    --config experimental.evolution=all \
	    --config experimental.bundle-phases=yes
	run mv "${bundlepath}.tmp" "${bundlepath}"
	if [ -z "${https_name}" ]; then
		manifest "${repodir}/.hg/clonebundles.manifest.tmp" <<EOF
peer-bundle-cache://${bundlename} BUNDLESPEC=${bundlespec}${extraspec:+ $extraspec}
EOF
	else
		manifest "${repodir}/.hg/clonebundles.manifest.tmp" <<EOF
https://${https_name}/${repo}/${bundlename} BUNDLESPEC=${bundlespec}${extraspec:+ $extraspec}
EOF
	fi

	live_bundles="$live_bundles $bundlename"
}

make_streambundle()
{
	local repo="$1"
	local repodir="${reporoot}/${repo}"
	local bundlespec="$2"
	local bundlename="$3"
	local extraspec=""
	local bundlepath="$(bundlepath "$repo" "$bundlename")"

	run mkdir -p "$(dirname "$bundlepath")"
	run hg bundle -R "${tmpreporoot}/${repo}" \
	    -t "${bundlespec}" --all \
	    "${bundlepath}.tmp" \
	    --config server.bundle2.stream=yes \
	    --config server.uncompressed=yes \
	    --config experimental.evolution=all \
	    --config experimental.evolution.bundle-obsmarker=true
	run mv "$bundlepath.tmp" "$bundlepath"
	if $DRY_RUN; then
		local fullbundlespec="$bundlespec"
	else
		local fullbundlespec="$(hg debugbundle --spec "$bundlepath")"
	fi
	if [ "$fullbundlespec" != "$fullbundlespec" ]; then
		printf '%s: Warning: bundlespec mismatch: %s != %s\n' \
		       "$repo" "$fullbundlespec" "$bundlespec" >&2
	fi
	if [ -z "${https_name}" ]; then
		manifest "${repodir}/.hg/clonebundles.manifest.tmp" <<EOF
peer-bundle-cache://${bundlename} BUNDLESPEC=${fullbundlespec}${extraspec:+ $extraspec}
EOF
	else
		manifest "${repodir}/.hg/clonebundles.manifest.tmp" <<EOF
https://${https_name}/${repo}/${bundlename} BUNDLESPEC=${fullbundlespec}${extraspec:+ $extraspec}
EOF
	fi

	live_bundles="$live_bundles $bundlename"
}

prepare_clone()
{
	local src_repo="$1"
	local tmp_repo="$2"
	local revset="$3"

	run rm -rf "$tmp_repo"

	local real_src_repo="$(readlink -f $(hg root -R "${src_repo}" \
	    -T "{storepath}/../.."))"
	run hg clone --config phases.publish=false -U "${real_src_repo}" "${tmp_repo}"
	run hg debugstrip --no-backup --hidden -R "${tmp_repo}" \
	    -r "!(${revset})" 2>/dev/null || true
	run cp "${src_repo}/.hg/hgrc" "${tmp_repo}/.hg/hgrc"
	run rm -rf "${tmp_repo}/.hg/cache"
	run hg debugupdatecache -R "${tmp_repo}"
}

process_repo()
{
	local repo="$1"
	local repofilter="$2"
	local repodir="${reporoot}/${repo}"
	local tmprepodir="${tmpreporoot}/${repo}"
	local current_state="$(hg log --hidden -R ${repodir} \
	    -r "${repofilter}" -T '{node} {phase}\n' | sort | cksum -a sha256)"
	local extant_bundlepath

	if grep -q "/${current_state}\..*\.hg" \
	    "${repodir}/.hg/clonebundles.manifest" 2>/dev/null; then
		printf '# %s already has bundles for %s\n' \
		    "$repodir" "$current_state"
		return 0
	fi

	# Clean any temporary files left over from interrupted past
	# runs.  Caller is responsible for serializing clonebundle
	# construction so we don't see the temporary work of a parallel
	# run.
	#
	for extant_bundlepath in "$(bundlepath "$repo" .)"/*.tmp; do
		if [ -e "$extant_bundlepath" -o -h "$extant_bundlepath" ]; then
			run rm -f "$extant_bundlepath"
		fi
	done

	prepare_clone "${repodir}" "${tmprepodir}" "${repofilter}"
	if ! $DRY_RUN; then
		current_state2=$(hg log --hidden -R ${tmprepodir} \
		    -T '{node} {phase}\n' | sort | cksum -a sha256)
		if [ "$current_state2" != "$current_state" ]; then
			printf 'Clean state mismatch: %s != %s\n' \
			    "$current_state2" "$current_state"
		fi
	fi

	run rm -f ${repodir}/.hg/clonebundles.manifest.tmp

	live_bundles=

	make_bundle "${repo}" zstd-v2 "${current_state}.zstd-9.hg" 9 ""
	#make_bundle "${repo}" zstd-v2 "${current_state}.zstd-22.hg" 22 REQUIREDRAM=3GB
	make_streambundle "${repo}" "none-streamv2" "${current_state}.stream.hg"

	run mv "${repodir}/.hg/clonebundles.manifest.tmp" \
	    "${repodir}/.hg/clonebundles.manifest"
	run rm -rf "${tmpreporoot}/${repo}"

	# Delete any old bundles that are no longer referenced by the
	# manifest.
	#
	for extant_bundlepath in "$(bundlepath "$repo" .)"/*.hg; do
		if [ -e "$extant_bundlepath" -o -h "$extant_bundlepath" ]; then
			case " $live_bundles " in
			*" ${extant_bundlepath##*/} "*)
				;;
			*)	run rm -f "$extant_bundlepath"
				;;
			esac
		fi
	done
}

process_repo "$1" "$2"
