Skip to content

Commit aba5ff6

Browse files
Merge branch 'main' into ci/audit-and-deny
2 parents 7fedb4d + 46cb605 commit aba5ff6

28 files changed

Lines changed: 4332 additions & 449 deletions

.githooks/pre-commit

Lines changed: 71 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,94 @@
11
#!/usr/bin/env bash
2-
# Git pre-commit hook: runs cargo fmt --check and cargo clippy on staged Rust files.
2+
# .githooks/pre-commit
33
#
4-
# Security & Quality Controls:
5-
# - Strict mode: set -euo pipefail
6-
# - Runs fmt check and clippy (-D warnings) against staged files
7-
# - Bypassing is permitted via standard `git commit --no-verify` (Git native flag) or SKIP_PRE_COMMIT=1
4+
# Runs `cargo fmt --check` and `cargo clippy --all-targets -- -D warnings`
5+
# against the workspace whenever Rust source files are staged.
6+
#
7+
# Install once with:
8+
# ./scripts/install_git_hooks.sh
9+
#
10+
# Bypass options (in order of preference):
11+
# git commit --no-verify # standard Git flag, no logging
12+
# SKIP_PRE_COMMIT=1 git commit # env-variable bypass, warning is logged
13+
#
14+
# The env-variable bypass exists for automated tooling (e.g. release scripts)
15+
# that need to commit without running the full check suite. It intentionally
16+
# prints a warning to stderr so the bypass is visible in CI logs.
17+
#
18+
# Clippy config drift: the hook honours clippy.toml / .clippy.toml when
19+
# present, and the workspace-level [lints.clippy] table in Cargo.toml.
20+
# Any lint configuration change is therefore automatically picked up on
21+
# the next commit — no hook update required.
822

923
set -euo pipefail
1024

11-
echo "==> Running pre-commit hooks..."
12-
13-
# Check if hook bypass environment variable is explicitly set
14-
if [ "${SKIP_PRE_COMMIT:-0}" = "1" ] || [ "${NO_VERIFY:-0}" = "1" ]; then
15-
echo "[pre-commit] WARNING: Pre-commit checks bypassed via environment variable."
25+
# ── Bypass: SKIP_PRE_COMMIT env-var ─────────────────────────────────────────
26+
if [ "${SKIP_PRE_COMMIT:-0}" = "1" ]; then
27+
echo "[pre-commit] WARNING: checks bypassed via SKIP_PRE_COMMIT=1." >&2
1628
exit 0
1729
fi
1830

19-
# Retrieve staged .rs files (Added, Copied, Modified, Renamed)
20-
STAGED_RS_FILES=$(git diff --cached --name-only --diff-filter=ACMR | grep '\.rs$' || true)
31+
# ── Detect staged Rust files ─────────────────────────────────────────────────
32+
# diff-filter ACMR: Added, Copied, Modified, Renamed — excludes Deleted (D)
33+
# so we never try to check files that no longer exist on disk.
34+
STAGED_RS=$(git diff --cached --name-only --diff-filter=ACMR | grep '\.rs$' || true)
2135

22-
if [ -z "$STAGED_RS_FILES" ]; then
23-
echo "[pre-commit] No staged Rust files detected. Skipping fmt and clippy checks."
36+
if [ -z "$STAGED_RS" ]; then
37+
# No Rust files staged — nothing to check.
2438
exit 0
2539
fi
2640

27-
echo "[pre-commit] Staged Rust files detected:"
28-
echo "$STAGED_RS_FILES" | sed 's/^/ - /'
41+
echo "[pre-commit] Staged Rust files:"
42+
echo "$STAGED_RS" | sed 's/^/ /'
2943

30-
# Read staged files into array safely
31-
mapfile -t FILES_ARRAY <<< "$STAGED_RS_FILES"
44+
# Resolve the repo root so cargo commands work regardless of where the user
45+
# ran `git commit` from (e.g. a subdirectory).
46+
REPO_ROOT=$(git rev-parse --show-toplevel)
3247

33-
# 1. Run cargo fmt --check against staged files
34-
echo "[pre-commit] Running cargo fmt check..."
35-
if ! cargo fmt --check -- "${FILES_ARRAY[@]}"; then
48+
# ── 1. cargo fmt --check ─────────────────────────────────────────────────────
49+
# Passes the list of staged files so only those files are checked. This is
50+
# faster than a full workspace fmt and avoids failing on pre-existing
51+
# formatting issues in files the commit does not touch.
52+
echo "[pre-commit] cargo fmt --check ..."
53+
54+
# Build the file list as an array to handle paths with spaces safely.
55+
mapfile -t STAGED_ARRAY <<< "$STAGED_RS"
56+
# Prefix each path with the repo root so paths are absolute.
57+
ABS_FILES=()
58+
for f in "${STAGED_ARRAY[@]}"; do
59+
ABS_FILES+=("$REPO_ROOT/$f")
60+
done
61+
62+
if ! cargo fmt --manifest-path "$REPO_ROOT/Cargo.toml" --check -- "${ABS_FILES[@]}" 2>&1; then
3663
echo ""
37-
echo "❌ Error: Code formatting check failed on staged files."
38-
echo "💡 Fix by running: cargo fmt"
64+
echo "[pre-commit] ❌ Formatting check failed." >&2
65+
echo "[pre-commit] Run \`cargo fmt\` to fix, then re-stage and commit." >&2
66+
echo "[pre-commit] To bypass (not recommended): git commit --no-verify" >&2
3967
exit 1
4068
fi
69+
echo "[pre-commit] ✅ fmt OK"
70+
71+
# ── 2. cargo clippy --all-targets ────────────────────────────────────────────
72+
# We run clippy across all targets (lib, tests, benches, examples) rather than
73+
# only the staged files because:
74+
# a) Clippy cannot accept individual file paths — it works at crate granularity.
75+
# b) A change in one file can introduce a lint in a test that imports it.
76+
#
77+
# -D warnings turns all warnings into errors, matching the CI gate.
78+
# The workspace [lints.clippy] table and any clippy.toml are honoured automatically.
79+
echo "[pre-commit] cargo clippy --all-targets -- -D warnings ..."
4180

42-
# 2. Run cargo clippy against staged targets / workspace
43-
echo "[pre-commit] Running cargo clippy..."
44-
if ! cargo clippy --all-targets -- -D warnings; then
81+
if ! cargo clippy \
82+
--manifest-path "$REPO_ROOT/Cargo.toml" \
83+
--all-targets \
84+
-- -D warnings 2>&1; then
4585
echo ""
46-
echo "❌ Error: Cargo clippy found compiler warnings or errors."
47-
echo "💡 Fix the clippy issues before committing."
86+
echo "[pre-commit] ❌ Clippy check failed." >&2
87+
echo "[pre-commit] Fix the warnings above, re-stage, and commit." >&2
88+
echo "[pre-commit] To bypass (not recommended): git commit --no-verify" >&2
4889
exit 1
4990
fi
91+
echo "[pre-commit] ✅ clippy OK"
5092

51-
echo "✅ Pre-commit checks passed successfully!"
93+
echo "[pre-commit] All checks passed."
5294
exit 0

README.md

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,12 +142,43 @@ This produces the WASM under `target/` for deployment to Stellar (e.g. testnet/m
142142

143143
### 6. Install Git hooks
144144

145-
Set up the repository pre-commit hook to enforce code formatting (`cargo fmt`) and linting (`cargo clippy`) on staged files prior to committing:
145+
Set up the repository pre-commit hook to enforce code formatting and linting
146+
on staged files before every commit:
146147

147148
```bash
148149
./scripts/install_git_hooks.sh
149150
```
150151

152+
The installer is **idempotent** — safe to run repeatedly. It:
153+
154+
1. Sets the executable bit on all scripts under `.githooks/`.
155+
2. Runs `git config core.hooksPath .githooks` (no global config changes).
156+
3. Smoke-tests the hook to confirm it exits 0.
157+
158+
After installation, every `git commit` automatically runs:
159+
160+
| Check | Command | Scope |
161+
|-------|---------|-------|
162+
| Formatting | `cargo fmt --check` | staged `.rs` files only |
163+
| Linting | `cargo clippy --all-targets -- -D warnings` | full workspace |
164+
165+
**Bypass options:**
166+
167+
```bash
168+
git commit --no-verify # standard Git flag, no logging
169+
SKIP_PRE_COMMIT=1 git commit # env-var bypass, prints a warning to stderr
170+
```
171+
172+
**Verify the installation is correct without modifying anything:**
173+
174+
```bash
175+
./scripts/install_git_hooks.sh --check
176+
```
177+
178+
> Note: `cargo clippy` always runs against the full workspace (not only staged files)
179+
> because clippy operates at crate granularity. A change in one file can surface
180+
> a lint in a test that imports it, so the broader scope is intentional.
181+
151182
---
152183

153184
## Build, test, and deploy
@@ -158,6 +189,7 @@ Set up the repository pre-commit hook to enforce code formatting (`cargo fmt`) a
158189
| Run tests | `cargo test` |
159190
| Build contract WASM | `soroban contract build` |
160191
| Install Git hooks | `./scripts/install_git_hooks.sh` |
192+
| Verify hook installation | `./scripts/install_git_hooks.sh --check` |
161193
| One-command local deploy | `./scripts/deploy_local.sh` |
162194
| Run with Soroban CLI (e.g. testnet) | See [Stellar docs](https://developers.stellar.org/docs/tools/soroban-cli) for `soroban contract deploy` and `invoke`. |
163195

0 commit comments

Comments
 (0)