#!/bin/sh
#
# Copyright (c) 2026 The NetBSD Foundation, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#

#
# update <ref> <old-value> <new-value>
#
#	This git hook is run by git-receive-pack(1) while applying
#	updates from a client running `git push', once for each ref
#	that is updated.  It receives as arguments the ref, the value
#	of the ref before the update, and the value of the ref after
#	the update.
#
#	If this hook fails, the specified ref update does not happen
#	and the push fails.  However, any previous ref updates -- even
#	from the same push -- are not rolled back.
#
#	See the githooks(5) man page for details.
#
# WHAT THIS HOOK IMPLEMENTATION DOES
#
#	If the repository is configured as an hg<->git bridge, this
#	hook uses git-cinnabar to push changes to the hg upstream.
#
#	- If GIT_NAMESPACE is empty, this hook pushes refs/heads/* to
#	  the upstream `origin'.
#
#	- If GIT_NAMESPACE is `draft', this hook pushes refs/heads/* as
#	  refs/heads/topics/* to the upstream `draft'.  For example, if
#	  you push to refs/heads/trunk/riastradh-pr65432-fixstuff, it
#	  will be forwarded to the `draft' upstream as
#	  refs/heads/topics/trunk/riastradh-pr65432-fixstuff.
#
#	Tags cannot currently be pushed this way -- they will be
#	rejected.
#

DEBUG=$(git config --type bool --default false netbsd.debug)
if $DEBUG; then
	echo BEGIN update $$ "$@"
	trap 'echo END update $$' EXIT HUP INT TERM
fi

set -Ceu
if $DEBUG; then
	set -x
fi

ref=$1
old=$2
new=$3

if $DEBUG; then
	i=0
	while [ "$i" -lt "${GIT_PUSH_OPTION_COUNT:-0}" ]; do
		eval echo [update] GIT_PUSH_OPTION_$i=\$GIT_PUSH_OPTION_$i
		i=$((i + 1))
	done
fi

if ! $(git config --type bool --default false netbsd.cinnabridge); then
	exit 0
fi

case ${GIT_NAMESPACE-} in
'')
	remote=origin
	prefix=
	;;
draft)
	remote=draft
	prefix=topics/
	export GIT_CINNABAR_EXPERIMENTS=branch # XXX
	;;
*)	echo 'forbidden namespace' >&2
	exit 1
	;;
esac

# Allow only refs/heads/* to be updated.
#
# In the future, we should consider:
#
#       refs/tags/*                     git cinnabar tag
#	refs/namespaces/dev/$DEV/*      developer namespace
#
case $ref in
refs/heads/*)
	ref=refs/heads/${prefix}${ref#refs/heads/}
	;;
*)	echo 'only head updates are allowed' >&2
	exit 1
	;;
esac

# Lock the cinnabar bridge so pushes don't happen concurrently.  If the
# system crashes in the middle, no big deal -- the lock will be broken,
# but no concurrent pushes will be happening anyway.  The lock will be
# released when this process exits (specifically, when all file
# descriptors referencing this open of the lock file are closed).
#
# This doesn't make batch ref updates atomic -- it only ensures that
# each ref update is forwarded on to Mercurial in serial and no
# concurrent pushes attempts to update cinnabar metadata
# simultaneously.  It's possible that metadata updates by git-cinnabar
# are already safe but let's play it safer.
#
# If the lock can't be acquired immediately, print message to say where
# the holdup is coming from.
#
# Make sure to preserve file permissions according to
# core.sharedRepository so in a group-shared repository, other members
# of the group can also take the lock.
umask=$(umask)
sharedrepo=$(git config --default false core.sharedRepository | tr A-Z a-z)
case $sharedrepo in
false|umask|0)
	;;
group|true|1)
	umask 002
	;;
0???)
	umask $sharedrepo
	;;
*)
	echo invalid core.sharedRepository setting >&2
	;;
esac
exec 3>>$GIT_DIR/cinnabridge.lock
umask $umask
if ! flock -n 3 3<&3; then
	echo 'waiting for Mercurial bridge lock...' >&2
	flock 3 3<&3
	echo 'Mercurial bridge lock acquired' >&2
fi

# Push back to the origin via git-cinnabar.  Set the environment
# variable TNFREPO_HG_GIT_PUSH while doing this so that any hg hooks
# know this is coming from a git-push and won't try to recursively come
# back to the git repo to git-fetch.
TNFREPO_HG_GIT_PUSH= \
git push "$remote" "$new:$ref" || exit $?
