#!/usr/bin/env zsh set -eu for cmd in git sops openssl; do if ! command -v "${cmd}" >/dev/null 2>&1; then echo "pre-commit: required command missing: ${cmd}" >&2 exit 1 fi done if [ -z "${SOPS_SYNC_HMAC_KEY:-}" ]; then echo "pre-commit: SOPS_SYNC_HMAC_KEY is required" >&2 exit 1 fi repo_root=$(git rev-parse --show-toplevel) regex_script="${repo_root}/scripts/lib/sops-path-regexes" lib_script="${repo_root}/scripts/lib/sops-sync-lib" if [ ! -x "${regex_script}" ]; then echo "pre-commit: missing helper ${regex_script}" >&2 exit 1 fi if [ ! -f "${lib_script}" ]; then echo "pre-commit: missing helper ${lib_script}" >&2 exit 1 fi . "${lib_script}" regexes=("${(@f)$("${regex_script}")}") typeset -A candidates while IFS= read -r staged_path; do [ -n "${staged_path}" ] || continue candidate_enc="" if [[ "${staged_path}" == *.enc.yaml ]]; then candidate_enc="${staged_path}" elif [[ "${staged_path}" == *.yaml ]]; then if git ls-files --error-unmatch -- "${staged_path}" >/dev/null 2>&1; then continue fi candidate_enc=$(plain_to_enc "${staged_path}") || continue fi [ -n "${candidate_enc}" ] || continue if matches_any_rule "${candidate_enc}" "${regexes[@]}"; then candidates["${candidate_enc}"]=1 fi done < <(git diff --cached --name-only --diff-filter=ACMR) for enc_path in "${(@k)candidates}"; do plain_path=$(enc_to_plain "${enc_path}") || continue sidecar_path=$(hmac_sidecar_for_enc "${enc_path}") if [ ! -f "${plain_path}" ]; then continue fi if has_unstaged_changes "${plain_path}"; then echo "pre-commit: plaintext file has unstaged changes: ${plain_path}" >&2 echo "pre-commit: stage or revert the plaintext change before committing" >&2 exit 1 fi current_hmac=$(compute_hmac_for_file "${plain_path}") tracked_hmac="" if [ -f "${sidecar_path}" ]; then tracked_hmac=$(read_sidecar "${sidecar_path}" || true) fi if [ "${current_hmac}" = "${tracked_hmac}" ] && [ -f "${enc_path}" ]; then continue fi encrypt_plain_to_enc "${plain_path}" "${enc_path}" write_sidecar "${sidecar_path}" "${current_hmac}" git add "${enc_path}" "${sidecar_path}" done for enc_path in "${(@k)candidates}"; do sidecar_path=$(hmac_sidecar_for_enc "${enc_path}") if ! git cat-file -e ":${enc_path}" 2>/dev/null; then echo "pre-commit: staged encrypted file missing: ${enc_path}" >&2 exit 1 fi if ! git cat-file -e ":${sidecar_path}" 2>/dev/null; then echo "pre-commit: staged sync sidecar missing: ${sidecar_path}" >&2 exit 1 fi tmp_enc=$(mktemp) trap 'rm -f "${tmp_enc}"' EXIT INT TERM git show ":${enc_path}" > "${tmp_enc}" staged_hmac=$(git show ":${sidecar_path}" | tr -d '\r\n') computed_hmac=$(decrypt_enc_to_plain "${tmp_enc}" | compute_hmac_for_stdin) rm -f "${tmp_enc}" trap - EXIT INT TERM if [ "${computed_hmac}" != "${staged_hmac}" ]; then echo "pre-commit: staged encrypted file and sidecar are out of sync: ${enc_path}" >&2 exit 1 fi done