gitea icon indicating copy to clipboard operation
gitea copied to clipboard

Execute pre-receive git hook from a template directory for all repositories

Open xor-gate opened this issue 10 months ago • 6 comments

Description

Hi All,

I manage a gitea server for small company and want to enforce branch naming using server side git pre-receive hook. Now I have enabled git hook execution on server side in app.ini via DISABLE_GIT_HOOKS = false. If i debug print directly in /var/lib/gitea/data/gitea-repositories/<org>/<repo>/hooks/pre-receive.d/gitea then this script is executed. When I place gitea server wide scripts under /var/lib/gitea/custom/hooks/pre-receive.d/branch-policy.sh then this script is not executed. Is this even possible? I cant find anything in the documentation or in the code this path is valid to have a list of scripts to be executed by gitea. It is highly likely Gemini AI search was hallucinating this template/server side directory is available as feature.

It would be nice to have server wide hooks instead of patching every repository. Maybe I'm doing something wrong or don't understand it because of the lack of documentation (or not sure where to find it).

Thanks in advance!

Gitea Version

v1.24.0

Can you reproduce the bug on the Gitea demo site?

No

Log Gist

No response

Screenshots

No response

Git Version

v2.39.5

Operating System

Linux (Debian 12)

How are you running Gitea?

From downloads under systemd, no package from Debian

Database

PostgreSQL

xor-gate avatar Jun 18 '25 10:06 xor-gate

Currently, you can place the scripts under /var/lib/gitea/data/gitea-repositories/<org>/<repo>/hooks/pre-receive.d/ directly. It should work. And the file should have execute permission. And you can also edit the hooks in repository setting if DISABLE_GIT_HOOKS = false.

lunny avatar Jun 18 '25 17:06 lunny

Probably a symlink to a common folder will work, then there is only one script to maintain and just pointed from /var/lib/gitea/data/gitea-repositories/<org>/<repo>/hooks/pre-receive.d/my-hook.sh to /var/lib/gitea/custom/hooks/pre-receive.d/my-hook.sh. Will test this out. Thanks for pointing this direction. Seems a reasonable workaround.

xor-gate avatar Jun 18 '25 17:06 xor-gate

. When I place gitea server wide scripts under /var/lib/gitea/custom/hooks/pre-receive.d/branch-policy.sh then this script is not executed.

There is no such feature: Add Global GitHooks #28345

wxiaoguang avatar Jun 18 '25 17:06 wxiaoguang

I have mocked together a bash script to sync from a base dir to all the repositories as symlinks, can be revoked when scripts use a prefix like custom-:

#!/bin/bash
# Synchronize git-hooks from common path as symlinks into all gitea-repositories
###
#set -x
set -e

# Configuration
GITEA_WORKDIR="/var/lib/gitea"
GITEA_REPOSITORIES_DIR="${GITEA_WORKDIR}/data/gitea-repositories"
GITEA_CUSTOM_GIT_HOOKS_DIR="${GITEA_WORKDIR}/custom/git-hooks"

##
# Fetch a list of hooks in the gitea custom git-hooks .d folder
# NOTE returns absolute paths
# $1: git_hook_name (e.g "pre-receive")
##
function gitea_custom_git_hooks_get_list() {
	git_hook_name="$1"
	GITEA_TARGET_HOOKS_DIR="${GITEA_CUSTOM_GIT_HOOKS_DIR}/${git_hook_name}.d"

	# Search for all hooks in the gitea custom path
	for gitea_custom_hook_filepath in `find "${GITEA_TARGET_HOOKS_DIR}" -maxdepth 1 -type f`; do
		repository_hook_symlink_path="${repository_hooks_target_d_dir}/$(basename ${gitea_custom_hook_filepath})"
		echo "${gitea_custom_hook_filepath}"
	done
}

##
# Synchronize based on hookname
# $1: git_hook_name (e.g "pre-receive")
# $2: hooks_list (array of absolute filepaths)
##
function synchronize_hooks_list() {
	git_hook_name="$1"
	hooks_list=$2
	GITEA_TARGET_HOOKS_DIR="${GITEA_CUSTOM_GIT_HOOKS_DIR}/${git_hook_name}.d"

	# Search for .git repository directories in GITEA_REPOSITORIES_DIR basepath (max depth 2: org/repo.git)
	for repository_path in `find "$GITEA_REPOSITORIES_DIR" -maxdepth 2 -type d -name "*.git"`; do
		hooks_target_d_dir="${GITEA_TARGET_HOOKS_DIR}"
		repository_hooks_target_d_dir="${repository_path}/hooks/${git_hook_name}.d"

		# Create repository hooks target directory if it doesn't exist yet
		if [ ! -d "${repository_hooks_target_d_dir}" ]; then
			mkdir -p "${repository_hooks_target_d_dir}"
		fi

		# Loop over all source path hooks and check if they are already symlinked into the gitea git repo
		for hook_filepath in $hooks_list; do
			target_hook_filepath="${repository_hooks_target_d_dir}/$(basename $hook_filepath)"
			if [ ! -e "${target_hook_filepath}" ]; then
				echo "Register ${hook_filepath} -> ${target_hook_filepath}"
				ln -s "${hook_filepath}" "${target_hook_filepath}"
			fi
		done
	done
}

##
# Remove all registered adimec hooks in gitea-repositories
# $1: git_hook_name (e.g "pre-receive")
# $2: hooks_prefix (e.g "custom-")
# $3: operation (e.g "list", "unregister")
##
function all_registered_hooks_operation() {
	git_hook_name="$1"
	hooks_prefix="$2"
	operation="$3"
	hooks_remove_filter="^${hooks_prefix}"

	# Search for .git repository directories in GITEA_REPOSITORIES_DIR basepath (max depth 2: org/repo.git)
	for repository_path in `find "$GITEA_REPOSITORIES_DIR" -maxdepth 2 -type d -name "*.git"`; do
		repository_hooks_dir="${repository_path}/hooks/${git_hook_name}.d"
		for hook_filepath in `find "${repository_hooks_dir}" -maxdepth 1 -type l`; do
			hook_filename=$(basename ${hook_filepath})
			if [[ "${hook_filename}" =~ ${hooks_remove_filter} ]]; then
				if [[ "$operation" == "unregister" ]]; then
					echo "Unregister ${hook_filepath}"
					rm "${hook_filepath}"
				elif [[ "$operation" == "list" ]]; then
					echo "${hook_filepath}"
				fi
			fi
		done
	done
}

# Fetch hooks list for pre-receive
HOOKS_LIST=($(gitea_custom_git_hooks_get_list "pre-receive"))
for hook_filepath in $HOOKS_LIST; do
	echo "System wide hook found: ${hook_filepath}"
done

cli_operation="$1"

case "${cli_operation}" in
	sync|"")
		# Synchronize the hooks list
		synchronize_hooks_list "pre-receive" ${HOOKS_LIST}
		;;
	list)
		# List all registered hooks
		all_registered_hooks_operation "pre-receive" "custom-" "list"
		;;
	unregister)
		# Unregistered all hooks
		all_registered_hooks_operation "pre-receive" "custom-" "unregister"
		;;
esac

xor-gate avatar Jun 18 '25 18:06 xor-gate

I had a similar AI hallucination that found this issue 😂 Seems there might be a way to soft-enforce this, cronjob etc but would be a nice official feature!

adcleared avatar Nov 21 '25 02:11 adcleared

I had a similar AI hallucination that found this issue 😂 Seems there might be a way to soft-enforce this, cronjob etc but would be a nice official feature!

Luckily you where directed here! My current solution/workaround is a nightly systemd timer (cron job) which registers hooks from a template. It should indeed not be necessary because for all repositories at gitea hook is installed, the gitea generic hook calls Go code (correct me if i'm wrong) and we should be able to execute other scripts from another directory with same context. But somebody needs to dig deep into the Gitea code to see where it can be added in a good way. I have enough experience with programming and Go/Shell scripts. But have not yet found time/reason to add it as a gitea builtin feature.

xor-gate avatar Nov 21 '25 10:11 xor-gate