Euler source The code that runs the solver shell and this site, version 4.34.2 Repository on GitHub

Generated by scripts/setup/githooks.sh — do not edit directly.

306 lines of Bash Raw file Open on GitHub

#!/usr/bin/env bash
# Generated by scripts/setup/githooks.sh — do not edit directly.
# Source of truth: scripts/setup/hooks/pre-push.template
#
# Native pre-push hook. Runs once per push:
#   1. Encryption-at-rest audit: every file under solutions/private that this
#      push would add to the remote is stored as gitfilter ciphertext, read
#      straight from the object store so a plaintext leak can never be pushed.
#      Inlined below (not sourced from a helper script) so the push gate stays
#      self-contained.
#   2. No compiled binaries (ELF blobs) anywhere in the tracked tree.
#   3. flake8 on packages + solutions.
#   4. mypy on packages + solutions.
#   5. shellcheck on every tracked shell script (scripts + hook templates).
#   6. the unit tests — only when the push changes packages/, scripts/ or tests/.
#   7. audit/ (sensitive run logs) is gitignored whenever it exists.
#
# Output contract: every check announces itself, prints its own detail on
# failure, and the hook always ends with a single PASS/FAIL verdict that
# names exactly which checks failed — so a buried error can't be masked by a
# later tool's "Success" line.
set -uo pipefail

# Resolve the working tree at runtime so the hook operates on the checkout
# actually being pushed — including linked worktrees. VENV stays absolute: the
# virtualenv lives only in the main checkout.
REPO_ROOT="$(git rev-parse --show-toplevel)"
VENV="__VENV__"

cd "$REPO_ROOT" || exit 1

# ── Reporting helpers ────────────────────────────────────────────────────
FAILURES=()                       # human-readable names of checks that failed

section() { printf '\n── %s ──\n' "$1" >&2; }
note()    { printf '   %s\n' "$1" >&2; }
fail()    { FAILURES+=("$1"); printf '   ✗ %s\n' "$1" >&2; }
pass()    { printf '   ✓ %s\n' "$1" >&2; }
skip()    { printf '   – %s\n' "$1" >&2; }

print_verdict() {
    printf '\n' >&2
    if (( ${#FAILURES[@]} == 0 )); then
        # "pushing" only when there is something to push. Git runs this hook with an
        # EMPTY stdin when it has already settled the push itself — a non-fast-forward
        # is decided before the hook runs — so claiming a push is about to happen
        # printed a cheerful verdict directly above git's rejection.
        if (( ${#PUSH_REFS[@]} == 0 )); then
            printf '✓ pre-push: checks passed, but git offered no refs — this push delivers nothing.\n' >&2
        else
            printf '✓ pre-push: all checks passed — pushing.\n' >&2
        fi
        return 0
    fi
    printf '════════════════════════════════════════════════════════════\n' >&2
    printf '✗ pre-push BLOCKED — %d check(s) failed:\n' "${#FAILURES[@]}" >&2
    local f
    for f in "${FAILURES[@]}"; do
        printf '    • %s\n' "$f" >&2
    done
    printf '  (scroll up to the matching "── ... ──" section for details)\n' >&2
    printf '════════════════════════════════════════════════════════════\n' >&2
    return 1
}

# ── The refs being pushed ────────────────────────────────────────────────
# Git passes one line per ref on stdin:
#   <local ref> <local sha> <remote ref> <remote sha>
# Read it up front so the audit below can scope itself to the blobs this push
# would actually add to the remote. An all-zero local sha is a ref deletion
# (nothing to audit); an all-zero remote sha is a new remote ref, for which
# "new" means every commit not already reachable from some other ref on this
# remote.
#
# NO lines at all is its own case: git offers none when it has already settled the
# push without the hook's help — a non-fast-forward, which git decides up front and
# rejects on its own. Nothing then reaches the remote, so the blob audits below have
# nothing to read and say so; reporting '0 blobs checked ✓' invited the reader to
# think a push had been audited and cleared when it had not been audited at all.
REMOTE="${1:-origin}"
PUSH_REFS=()
while read -r _local_ref local_sha _remote_ref remote_sha; do
    [[ -z "${local_sha}" ]] && continue
    [[ "${local_sha}" =~ ^0+$ ]] && continue
    PUSH_REFS+=("${local_sha} ${remote_sha}")
done

# Emit the blobs added or modified by this push under the given pathspec (all
# paths when none is given), as a NUL-separated stream of alternating
# "<raw meta>" and "<path>" records (the `git diff-tree -z` raw format). `-m`
# diffs merges against each parent so an evil merge cannot smuggle a blob past
# the audit; duplicates across refs and parents are deduplicated by the caller.
pushed_blobs() {
    local entry local_sha remote_sha
    for entry in "${PUSH_REFS[@]}"; do
        read -r local_sha remote_sha <<<"${entry}"
        if [[ "${remote_sha}" =~ ^0+$ ]]; then
            git rev-list "${local_sha}" --not --remotes="${REMOTE}" \
                | git diff-tree -r -m --stdin --root --no-commit-id -z \
                      --diff-filter=AM -- "$@"
        else
            git diff-tree -r -m --no-commit-id -z --diff-filter=AM \
                "${remote_sha}" "${local_sha}" -- "$@"
        fi
    done
}

# Emit the paths this push changes under the given pathspec, one per line.
#
# Sibling of pushed_blobs, and deliberately not a --name-only mode of it: this asks a
# different question. pushed_blobs audits blob CONTENT, so it wants added/modified
# blobs only (--diff-filter=AM); this asks whether a push touches an area at all, and
# a deletion touches it — a module removed out from under an import is exactly the
# change whose tests must run.
pushed_paths() {
    local entry local_sha remote_sha
    for entry in "${PUSH_REFS[@]}"; do
        read -r local_sha remote_sha <<<"${entry}"
        if [[ "${remote_sha}" =~ ^0+$ ]]; then
            git rev-list "${local_sha}" --not --remotes="${REMOTE}" \
                | git diff-tree -r -m --stdin --root --no-commit-id --name-only -- "$@"
        else
            git diff-tree -r -m --no-commit-id --name-only \
                "${remote_sha}" "${local_sha}" -- "$@"
        fi
    done
}

# Populate the associative array named by $1 with "<blob sha>" → "<path>" for
# every blob this push adds or modifies under the pathspec in the remaining
# args. Keying by sha deduplicates the same object arriving via several refs or
# merge parents. Gitlinks (mode 160000) are skipped: a submodule entry is a
# commit, not a blob, and its object need not even be present here.
collect_pushed_blobs() {
    local -n _cpb_out="$1"; shift
    local meta path mode sha
    while IFS= read -r -d '' meta && IFS= read -r -d '' path; do
        # 'git diff-tree -r -z' raw meta: ":<src mode> <dst mode> <src sha> <dst sha> <status>"
        read -r _ mode _ sha _ <<<"${meta}"
        [[ "${mode}" == 160000 ]] && continue
        [[ "${sha}" =~ ^0+$ ]] && continue
        _cpb_out["${sha}"]="${path}"
    done < <(pushed_blobs "$@")
}

# Print the first $2 bytes of blob $1 as hex, read straight from the object
# store so no smudge filter runs. head closes the pipe early (SIGPIPE on
# cat-file); that's expected and harmless here — the hook runs without `set -e`.
blob_head_hex() {
    git cat-file -p "$1" | head -c "$2" | xxd -p
}

# ── 1. Encryption-at-rest audit (solutions/private) ──────────────────────
# Inlined (rather than sourced from a helper script) so the push gate is
# self-contained and survives that helper being changed or removed. Reads each
# blob this push adds under solutions/private straight from the object store (so
# NO smudge filter runs) and checks it begins with the gitfilter MAGIC. The magic
# is read from config.gitfilter_magic, keeping it in lockstep with
# packages/solver/solver/crypto/gitfilter.py; a blob lacking it is plaintext that slipped past
# the clean filter — block the push.
section "Encryption-at-rest audit (solutions/private, blobs in this push)"
magic_hex=$("$VENV/bin/python" -c 'from solver.crypto import wire; print(wire.MAGIC.hex())')
if (( ${#PUSH_REFS[@]} == 0 )); then
    skip "no refs offered by git — this push adds no blobs to audit"
elif [[ -z "$magic_hex" ]]; then
    fail "could not read config.gitfilter_magic (is the venv intact?)"
else
    mlen=$(( ${#magic_hex} / 2 ))
    total=0
    encrypted=0
    plaintext_files=()
    declare -A private_blobs=()
    collect_pushed_blobs private_blobs solutions/private
    for sha in "${!private_blobs[@]}"; do
        head_hex=$(blob_head_hex "$sha" "$mlen")
        total=$(( total + 1 ))
        if [[ "$head_hex" == "$magic_hex" ]]; then
            encrypted=$(( encrypted + 1 ))
        else
            plaintext_files+=("${private_blobs[$sha]}")
        fi
    done
    if (( ${#plaintext_files[@]} == 0 )); then
        pass "$encrypted encrypted, 0 plaintext, $total total (magic=0x$magic_hex)"
    else
        note "plaintext blobs under solutions/private in this push:"
        for p in "${plaintext_files[@]}"; do note "  !! $p"; done
        note "the encryption filter was not working when these were committed. Fix and re-commit:"
        note "  solver shell:  git-filter install    (verifies key access first)"
        note "  then:          git add --renormalize -- solutions/private && git commit"
        fail "files under solutions/private in this push are not all encrypted at rest"
    fi
fi

# ── 2. Compiled binaries in this push ────────────────────────────────────
# Backstop to the pre-commit check: read every blob this push adds — any path,
# not just solutions/private — from the object store and flag any that begins
# with the ELF magic. Build artifacts (e.g. the p<NNNN>_s<K>_c runner
# executables) are gitignored and must never reach the remote.
section "compiled binaries (blobs in this push)"
ELF_MAGIC_HEX='7f454c46'
pushed_bins=()
declare -A all_blobs=()
if (( ${#PUSH_REFS[@]} == 0 )); then
    skip "no refs offered by git — this push adds no blobs to audit"
else
    collect_pushed_blobs all_blobs
    for sha in "${!all_blobs[@]}"; do
        if [[ "$(blob_head_hex "$sha" 4)" == "$ELF_MAGIC_HEX" ]]; then
            pushed_bins+=("${all_blobs[$sha]}")
        fi
    done
    if (( ${#pushed_bins[@]} == 0 )); then
        pass "no compiled binaries in this push (${#all_blobs[@]} blob(s) checked)"
    else
        note "compiled binaries (ELF) in this push:"
        for b in "${pushed_bins[@]}"; do note "  !! $b"; done
        fail "compiled binaries are in this push — run 'git rm --cached' on them before pushing"
    fi
fi

# ── 3. flake8 on packages + solutions ──────────────────────────────────────
section "flake8 (solutions, packages)"
if "$VENV/bin/flake8" solutions packages; then
    pass "flake8 clean"
else
    fail "flake8 reported issues"
fi

# ── 4. mypy on packages + solutions + scripts + tests ──────────────────────
# `scripts` holds tracked Python that ships and runs (scripts/setup/euler-smtp.py is
# installed to /usr/local/bin and run as a service), so it is checked with the rest; so is
# `tests`, whose calls into the packages go stale when a signature changes.
section "mypy (solutions, packages, scripts, tests)"
if "$VENV/bin/mypy" solutions packages scripts tests; then
    pass "mypy clean"
else
    fail "mypy reported type errors"
fi

# ── 5. shellcheck on every tracked shell script ──────────────────────────
# The whole-tree backstop to pre-commit's staged-only lane: that one only sees the
# scripts you touched, so a script edited around the hook (--no-verify, a rebase, a
# merge) reaches the remote unlinted otherwise. Whole-tree like the flake8/mypy lanes
# above, and about as quick as a single git command — every script is parsed
# independently, nothing is resolved across files.
#
# The hook templates are in scope: they are shell, and only become .git/hooks/* once
# githooks.sh substitutes __VENV__ — which means a broken template is discovered at
# `githooks.sh install` time, i.e. as a broken hook, unless it is linted here.
section "shellcheck (tracked shell scripts)"
mapfile -t -d '' tracked_sh < <(git ls-files -z -- '*.sh' '*.template')
if (( ${#tracked_sh[@]} == 0 )); then
    skip "no shell scripts tracked"
elif ! command -v shellcheck > /dev/null 2>&1; then
    note "install it with: make install-system"
    fail "shellcheck is not installed"
elif shellcheck -- "${tracked_sh[@]}"; then
    pass "shellcheck clean (${#tracked_sh[@]} script(s))"
else
    fail "shellcheck reported issues"
fi

# ── 6. unit tests, when this push touches the code they cover ────────────
# The suite takes ~10s and covers packages/ + scripts/. A solutions-only push cannot
# break it, and the collaborator pushing their problem should not wait on it — so the
# trigger is scoped to what this push changes, like the audits above.
#
# tests/ is in the trigger with the code it covers: a push editing only a test is the
# case most certain to want the suite run, and skipping it would let a test be pushed
# without ever having been run.
#
# This is the gate that was missing: nothing in the commit or push path ran the tests,
# so `PullRequestTest` sat red from 18f5da8b until a human happened to run them. flake8
# and mypy could not have caught it — the code type-checked perfectly and simply did
# something the tests said it should not.
section "unit tests (only when anything changes under packages/, scripts/, or tests/)"
if (( ${#PUSH_REFS[@]} == 0 )); then
    skip "no refs offered by git — nothing to test"
elif [[ -z "$(pushed_paths packages scripts tests)" ]]; then
    skip "this push changes nothing under packages/, scripts/ or tests/"
# `-t .` imports the tests package first, so its pinned identity is in place before any
# command registers (tests/__init__.py): the result must not depend on this terminal's groups.
elif "$VENV/bin/python" -m unittest discover -s tests -t . -q; then
    pass "unit tests pass"
else
    fail "unit tests failed"
fi

# ── 7. audit/ is gitignored whenever it exists ───────────────────────────
# The push-side twin of pre-commit's check: audit/ holds run logs that name
# collaborators and the host's layout, safe only while .gitignore covers it.
section "audit/ folder (gitignored)"
if [[ ! -e audit ]]; then
    pass "no audit/ folder in this checkout"
elif ! git check-ignore --no-index -q audit/; then
    fail "audit/ exists but is not gitignored — restore '/audit/' in .gitignore"
elif [[ -n "$(git ls-files -- audit)" ]]; then
    fail "files under audit/ are tracked — 'git rm -r --cached audit' before pushing"
else
    pass "audit/ is gitignored"
fi

# ── Verdict ──────────────────────────────────────────────────────────────
print_verdict
exit $?