#!/usr/bin/env bash # hito pre-receive hook. The bare repository runs this hook before it moves a # ref. The hook checks each candidate master revision in an isolated directory. # A failed check rejects the push, so an unverified revision cannot deploy. # # deploy/setup.sh installs this hook and its trusted hito-ci helper. The helper # is outside the received tree. A pushed revision cannot weaken this check. # # Style: Google Shell Style Guide. set -euo pipefail readonly DEPLOY_BRANCH="master" readonly BARE="/srv/git/hito.git" readonly SHARED="/srv/hito/shared" readonly CI_HELPER="${BARE}/hooks/hito-ci" readonly ZERO="0000000000000000000000000000000000000000" export OPAMROOT="/srv/git/.opam" export OPAMSWITCH="${SHARED}" err() { printf ' hito-verify: %s\n' "$*" >&2 } fail() { err "REJECTED: $*" exit 1 } verify_master() { local old="$1" new="$2" local stage buildlog if [[ "${new}" == "${ZERO}" ]]; then fail "master cannot be deleted" fi if [[ "${old}" != "${ZERO}" ]] \ && ! git --git-dir="${BARE}" merge-base --is-ancestor "${old}" "${new}"; then fail "master must advance by fast-forward" fi if [[ ! -x "${CI_HELPER}" ]]; then fail "missing trusted CI helper; run deploy/setup.sh install" fi stage="$(mktemp -d /tmp/hito-verify.XXXXXX)" trap 'rm -rf "${stage}"' RETURN buildlog="${stage}/verify.log" err "verifying candidate master ${new:0:12}" git --git-dir="${BARE}" archive "${new}" | tar -x -C "${stage}" \ || fail "archive candidate master" eval "$(opam env --switch="${SHARED}" --set-switch)" ( cd "${stage}" opam install . --deps-only --with-test --with-dev-setup -y >"${buildlog}" 2>&1 BUILD_DIR="_build-ci" sh "${CI_HELPER}" >>"${buildlog}" 2>&1 ) || { tail -40 "${buildlog}" >&2 || true fail "verification failed for ${new:0:12}" } err "verified candidate master ${new:0:12}" } main() { local old new ref while read -r old new ref; do if [[ "${ref}" == "refs/heads/${DEPLOY_BRANCH}" ]]; then verify_master "${old}" "${new}" fi done } main "$@"