#!/bin/sh
# gitgud-prereceive — invoked by pre-receive in each bare repo.
#
# Reads `<old> <new> <ref>` lines from stdin and POSTs them to the
# Phoenix node for branch-protection evaluation. If Phoenix says DENY,
# we print the reason on stderr (visible to the pushing client) and
# exit non-zero so git aborts the push.
#
# **Fail-closed**: if the protection backend can't give an answer, the
# push is REJECTED. Better to refuse than to silently bypass branch
# protection.
#
# `GITGUD_PREHOOK_FAIL_OPEN=1` is an emergency override, and it covers
# exactly one case: the backend is *unreachable* — curl missing, the
# connection refused or timed out, or the node answering 5xx. It never
# covers an answer the backend gave on purpose: a 4xx (a missing or
# wrong hook secret is a 403, an unknown repository a 404) is a
# misconfiguration or someone probing, and letting the push through on
# it would turn a broken install into one without branch protection.
# Nor does it cover a hook with no GITGUD_HOOK_SECRET at all, which the
# backend could only ever refuse.
set -eu
GITGUD_PREHOOK_URL="${GITGUD_PREHOOK_URL:-http://127.0.0.1:4000/_hooks/pre-receive}"
REPO_PATH="${GIT_DIR:-$(pwd)}"
REPO_PATH="$(cd "$REPO_PATH" && pwd)"
STDIN_PAYLOAD="$(cat)"
# The backend is unreachable: refuse, unless the operator opted into
# fail-open, in which case say so as loudly as a hook can.
unreachable() {
echo "gitgud: $1" >&2
if [ "${GITGUD_PREHOOK_FAIL_OPEN:-}" = "1" ]; then
echo "gitgud: WARNING: GITGUD_PREHOOK_FAIL_OPEN=1 — allowing push WITHOUT branch protection" >&2
exit 0
fi
echo "gitgud: refusing push (branch protection cannot be evaluated)" >&2
exit 1
}
if ! command -v curl >/dev/null 2>&1; then
unreachable "curl not installed; cannot reach protection backend"
fi
if [ -z "${GITGUD_HOOK_SECRET:-}" ]; then
echo "gitgud: GITGUD_HOOK_SECRET is not set in the hook's environment;" >&2
echo "gitgud: the protection backend would refuse this hook, so the push is refused" >&2
exit 1
fi
# The node's hook secret (GitGudWeb.Plugs.HookAuth), inherited from the
# git process that runs this hook. Fed to curl on stdin rather than as
# an argument, so it doesn't show up in `ps`.
#
# No `-f`: an HTTP error status has to stay distinguishable from a
# connection that never happened. curl exits non-zero only for the
# latter; the status code comes back as the last line of the output.
set +e
RESP=$(printf 'x-gitgud-hook-token: %s\n' "${GITGUD_HOOK_SECRET}" | curl -s -X POST \
--connect-timeout 10 \
-H @- \
-H "content-type: text/plain" \
-H "x-repo-path: ${REPO_PATH}" \
--data-binary "${STDIN_PAYLOAD}" \
-w '\n%{http_code}' \
"${GITGUD_PREHOOK_URL}" 2>/dev/null)
CURL_EXIT=$?
set -e
if [ "$CURL_EXIT" -ne 0 ]; then
unreachable "protection backend unreachable at ${GITGUD_PREHOOK_URL} (curl exit ${CURL_EXIT})"
fi
HTTP_CODE=$(printf '%s' "$RESP" | tail -n 1)
BODY=$(printf '%s' "$RESP" | sed '$d')
case "$HTTP_CODE" in
2??) ;;
5??)
unreachable "protection backend at ${GITGUD_PREHOOK_URL} failed (HTTP ${HTTP_CODE})"
;;
*)
echo "gitgud: protection backend refused the hook (HTTP ${HTTP_CODE}); push refused" >&2
if [ "$HTTP_CODE" = "403" ]; then
echo "gitgud: check that GITGUD_HOOK_SECRET matches the node's" >&2
fi
exit 1
;;
esac
STATUS=$(printf '%s' "$BODY" | head -n1 | awk '{print $1}')
REASON=$(printf '%s' "$BODY" | tail -n +2)
if [ "$STATUS" = "OK" ]; then
exit 0
fi
echo "gitgud: push denied" >&2
printf '%s\n' "$REASON" >&2
exit 1
neiam
/gitgud
Git Gud
public · Issues · Pulls · Labels · Forks · Compare · Actions success · Badges · Packages
⭐
1
Log in to mark this repository.
3.5 KiB · text
History
3d64eb5