#!/bin/sh
# hooks/post-receive - notify conductors of pushed commits
#
# Install into a bare repository as hooks/post-receive, chmod +x.
# Reads "<old> <new> <ref>" per pushed ref on stdin. Branches and tags are
# forwarded; a pipeline decides what to build with an only: refs rule.
#
# Expects conductor.conf in the parent directory with lines:
#   <trigger_url> <secret>
# Blank lines and lines starting with # are ignored.

set -eu

ZERO="0000000000000000000000000000000000000000"

DIRSELF=$(dirname $(realpath $0))
CONFIG="${DIRSELF}/../conductor.conf"

log() { printf '[conductor] %s\n' "$*" >&2; }

if [ ! -f "${CONFIG}" ]; then
  log "no conductor.conf found; not triggering"
  exit 0
fi

if command -v curl >/dev/null 2>&1; then
  http_client=curl
elif command -v wget >/dev/null 2>&1; then
  http_client=wget
else
  log "neither curl nor wget is available; not triggering"
  exit 0
fi

trigger() {
  _url="$1"
  _secret="$2"
  _payload="$3"
  _ref="$4"

  if [ -n "${_secret}" ]; then
    digest=$(printf '%s' "${_payload}" | openssl dgst -sha256 -hmac "${_secret}" | sed 's/^.*[= ]//')
    signature="sha256=${digest}"
  else
    signature=""
    log "warning: no secret for ${_url}, sending unsigned"
  fi

  if [ "${http_client}" = curl ]; then
    response=$(curl -fsS -m 10 -X POST "${_url}" \
      -H 'Content-Type: application/json' \
      -H "X-Hub-Signature-256: ${signature}" \
      -d "${_payload}" 2>&1) || {
      log "trigger failed for ${_ref}: ${response}"
      return
    }
  else
    response=$(wget -qO- --timeout=10 \
      --header='Content-Type: application/json' \
      --header="X-Hub-Signature-256: ${signature}" \
      --post-data="${_payload}" "${_url}" 2>&1) || {
      log "trigger failed for ${_ref}: ${response}"
      return
    }
  fi

  log "${response}"
}

while read -r old new ref; do
  if [ "${new}" = "${ZERO}" ]; then
    log "skipping ${ref}, deleted"
    continue
  fi

  case "${ref}" in
    refs/heads/*|refs/tags/*) ;;
    *) log "skipping ${ref}"; continue ;;
  esac

  # An annotated tag names a tag object, and the conductor wants the commit.
  sha=$(git rev-parse --verify --quiet "${new}^{commit}") || {
    log "skipping ${ref}, not a commit"
    continue
  }

  if [ "${old}" = "${ZERO}" ]; then
    base=""
  else
    base=$(git rev-parse --verify --quiet "${old}^{commit}" || true)
  fi

  payload=$(printf '{"sha":"%s","base":"%s","ref":"%s","actor":"%s"}' \
    "${sha}" "${base}" "${ref}" "${USER:-git}")

  log "triggering ${ref} at $(echo "${sha}" | cut -c1-12)"

  while IFS= read -r line; do
    case "${line}" in
      ''|\#*) continue ;;
    esac

    url=$(printf '%s' "${line}" | awk '{print $1}')
    secret=$(printf '%s' "${line}" | awk '{print $2}')

    [ -z "${url}" ] && continue

    trigger "${url}" "${secret}" "${payload}" "${ref}" "${new}"
  done < "${CONFIG}"
done

exit 0
