Skip to content

Instantly share code, notes, and snippets.

@Konfekt
Last active December 11, 2023 10:01
Show Gist options
  • Save Konfekt/d9e86763b0f3febd7b2f7ca589f6c482 to your computer and use it in GitHub Desktop.
Save Konfekt/d9e86763b0f3febd7b2f7ca589f6c482 to your computer and use it in GitHub Desktop.
use global git hooks as fallback to local hooks
#!/usr/bin/env bash
#
# Adaption of https://github.com/majutsushi/etc/commit/e62904088c698e064c17522d54dff91b629ee253#diff-53b7e445a85984949f551c277d4cc4ee9682287cb234e075e6d352be887e7494
# with https://github.com/pivotal-cf/git-hooks-core/blob/master/.base-hook
#
# This script is meant to be put into a directory pointed to by core.hooksPath
# in Git 2.9.
# Then for each hook you want to support, create a symlink "hookname -> multihook"
# and optionally a directory "hookname.d" where you can put all scripts for
# that hook
#
# This script linked to HOOKNAME in core.hooksPath executes,
# in ascending order of priority:
#
# - ./HOOKNAME.d/*
# - $GIT_CUSTOM_HOOKS_DIR/HOOKNAME.d/*
# - $GIT_DIR/hooks/HOOKNAME.d/*
# - $GIT_DIR/hooks/HOOKNAME
#
# The scripts found in those directories will be merged and
# executed in alphabetic order, with hooks in higher-priority directories
# overriding hooks in lower-priority directories.
set -eEu -o pipefail
shopt -s inherit_errexit
# optionally debug output by supplying TRACE=1
[[ "${TRACE:-0}" == "1" ]] && set -o xtrace
IFS=$'\n\t'
PS4='+\t '
error_handler() { echo "Error: In ${BASH_SOURCE[0]} Line ${1} exited with Status ${2}"; }
trap 'error_handler ${LINENO} $?' ERR
STDIN=$(cat)
[[ -n "${GIT_DIR:-}" ]] && git_dir="$GIT_DIR"
unset $(git rev-parse --local-env-vars || true)
GIT_DIR="$(git rev-parse --git-dir 2>/dev/null)" || GIT_DIR="$git_dir"
export GIT_DIR
[[ -n "${git_dir:-}" ]] && unset git_dir
# Array of the supported hook directories in ascending order of priority
declare -a HOOKDIRS
HOOKDIRS+=("$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")
[[ -n "${GIT_CUSTOM_HOOKS_DIR:-}" ]] && HOOKDIRS+=("$GIT_CUSTOM_HOOKS_DIR")
HOOKDIRS+=("$GIT_DIR/hooks")
# The Git name of the hook to execute
HOOKNAME=$(basename "${BASH_SOURCE[0]}")
# run first hooks in $GIT_DIR as usual
# similar to https://github.com/pivotal-cf/git-hooks-core/blob/master/.base-hook
hookscript="${GIT_DIR}/hooks/${HOOKNAME}"
if [[ -f "$hookscript" && -x "$hookscript" ]]; then
# echo "##### GITHOOK: ${hookscript}"
echo "$STDIN" | "$hookscript" "$@" || exit $?
fi
# Associative array of the hook scripts to run
# - Key is the basename of the file
# - Value is the full path to the file
declare -A TO_RUN
# Assemble the array of scripts to run. Since the keys are the basenames of
# the scripts, scripts with the same name in higher-priority directories will
# override scripts in lower-priority directories. This allows replacing or
# disabling standard hooks without explicit support from the scripts.
for dir in "${HOOKDIRS[@]}"; do
hookdir="${dir}/${HOOKNAME}.d"
if [[ -d "$hookdir" ]]; then
for hook in "$hookdir"/*; do
hookname="$(basename "$hook")"
TO_RUN[$hookname]="$hook"
done
fi
done
# Iterate over the script array in alphabetic order, running all the scripts
# that have the executable bit set.
for key in "${!TO_RUN[@]}"; do
echo "$key"
done | sort | while read -r hookname; do
hookscript="${TO_RUN["$hookname"]}"
if [[ -f "$hookscript" && -x "$hookscript" ]]; then
# echo "##### GITHOOK: ${hookscript}"
echo "$STDIN" | "$hookscript" "$@" || exit $?
fi
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment