Skip to main content

aperion_shield/hooks/
templates.rs

1//! Bash sources for the git hooks installed by `--install-hooks`.
2//!
3//! Each script:
4//!
5//!  1. Identifies itself with the `APERION_SHIELD_HOOK_MARKER` line so
6//!     the installer can re-recognise (and safely overwrite) its own
7//!     hooks without clobbering user-authored ones.
8//!  2. Honours the documented bypass switches:
9//!       * `git commit --no-verify` / `git push --no-verify` (built into
10//!         git -- there's nothing for us to do, but we tell the user).
11//!       * `SHIELD_HOOKS_DISABLE=1`  (environment override; useful for
12//!         non-interactive automation that can't pass `--no-verify`).
13//!  3. Falls back gracefully (exit 0 with a stderr notice) when the
14//!     `aperion-shield` binary isn't on `PATH`. This matters when
15//!     teammates clone a repo where someone installed Shield hooks but
16//!     haven't installed Shield themselves yet -- we don't want git
17//!     operations to break for them.
18//!  4. Calls the appropriate engine mode (`--check-staged` /
19//!     `--check-pushed-refs`) and exits with whatever exit code Shield
20//!     returned. Shield's own exit-code policy is the source of truth
21//!     for what blocks the commit/push.
22//!
23//! Keeping the hook scripts thin (and deterministic) is on purpose -- if
24//! the user inspects `.git/hooks/pre-commit` they should be able to
25//! read it in under 30 seconds and trust what it does.
26
27/// Stable marker line that identifies an Aperion-installed hook. The
28/// installer matches on this line (not on whole-file checksum) so we
29/// can evolve the hook body across versions without losing the ability
30/// to recognise our own footprint.
31pub const APERION_HOOK_MARKER: &str = "# APERION-SHIELD-HOOK v1 -- managed by `aperion-shield --install-hooks`";
32
33/// Pre-commit hook. Runs the engine against staged changes (lines being
34/// ADDED or MODIFIED) and refuses the commit if any line trips a Block
35/// rule under the active shieldset.
36pub fn pre_commit_script() -> String {
37    format!(
38        r#"#!/bin/sh
39{marker}
40#
41# What this does:
42#   * Asks `aperion-shield --check-staged` to scan the lines being
43#     ADDED / MODIFIED in this commit.
44#   * Blocks the commit (exit 1) if any line trips a destructive rule
45#     (DROP DATABASE, rm -rf /, git push --force, etc.).
46#   * No-ops cleanly when `aperion-shield` isn't on $PATH.
47#
48# Bypass switches (in order of preference):
49#   git commit --no-verify        # skip all hooks for this commit
50#   SHIELD_HOOKS_DISABLE=1 git ...  # env override; works in CI
51#
52# To remove this hook entirely:
53#   aperion-shield --uninstall-hooks
54
55set -e
56
57if [ "${{SHIELD_HOOKS_DISABLE:-}}" = "1" ]; then
58    exit 0
59fi
60
61if ! command -v aperion-shield >/dev/null 2>&1; then
62    echo "[aperion-shield] binary not on \$PATH; skipping pre-commit guardrail" >&2
63    echo "[aperion-shield] install: brew install AperionAI/tap/aperion-shield" >&2
64    exit 0
65fi
66
67exec aperion-shield --check-staged
68"#,
69        marker = APERION_HOOK_MARKER,
70    )
71}
72
73/// Pre-push hook. Reads the standard git-supplied stdin describing the
74/// refs being pushed and refuses any force-push to a protected branch
75/// unless `--no-verify` is passed.
76pub fn pre_push_script() -> String {
77    format!(
78        r#"#!/bin/sh
79{marker}
80#
81# What this does:
82#   * Reads git's standard pre-push stdin (one `local_ref local_sha
83#     remote_ref remote_sha` line per ref being pushed).
84#   * Asks `aperion-shield --check-pushed-refs` whether any ref is a
85#     destructive force-push or branch-deletion of a protected branch
86#     (main, master, prod, release/*, by default).
87#   * Blocks the push (exit 1) if any ref is destructive.
88#   * No-ops cleanly when `aperion-shield` isn't on $PATH.
89#
90# Bypass switches:
91#   git push --no-verify
92#   SHIELD_HOOKS_DISABLE=1 git push ...
93#
94# To remove this hook entirely:
95#   aperion-shield --uninstall-hooks
96
97set -e
98
99if [ "${{SHIELD_HOOKS_DISABLE:-}}" = "1" ]; then
100    exit 0
101fi
102
103if ! command -v aperion-shield >/dev/null 2>&1; then
104    echo "[aperion-shield] binary not on \$PATH; skipping pre-push guardrail" >&2
105    exit 0
106fi
107
108# git supplies pre-push refs on stdin; pipe straight through.
109exec aperion-shield --check-pushed-refs
110"#,
111        marker = APERION_HOOK_MARKER,
112    )
113}