-
-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathpre-commit.hook
More file actions
executable file
·149 lines (131 loc) · 5.13 KB
/
Copy pathpre-commit.hook
File metadata and controls
executable file
·149 lines (131 loc) · 5.13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
#!/bin/bash
#
# Pre-commit hook
# 1. Formatting check (always; fast)
# - C/C++ files: clang-format
# - JS/CSS/HTML files: prettier (if available)
# 2. Build + test (conditional on staged source/build files)
# - Skipped on docs-only / config-only / web-only commits
# - Mirrors what the CI runs locally so a green commit ≈ green CI
# 3. Optional local extension: if ./pre-commit.local.hook exists and is
# executable, it runs after phases 1+2 succeed. Use for per-developer
# checks that shouldn't live in the public repo. Gitignored.
#
# Install: ./install-git-hooks.sh
# Bypass: git commit --no-verify
#
# Change to repository root
cd "$(git rev-parse --show-toplevel)" || exit 1
# Color output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Optional local-only pre-commit extension. If ./pre-commit.local.hook
# exists and is executable, it runs after the tracked phases succeed.
# The local hook is gitignored (*.local.hook). Failure (non-zero exit)
# blocks the commit just like any other phase.
run_local_extension() {
if [ -x "./pre-commit.local.hook" ]; then
./pre-commit.local.hook
local rc=$?
if [ "$rc" -ne 0 ]; then
echo ""
echo -e "${RED}Local pre-commit extension failed (exit $rc).${NC}"
echo -e "${YELLOW}Bypass: git commit --no-verify${NC}"
exit "$rc"
fi
fi
}
# -----------------------------------------------------------------------------
# Phase 1 — formatting
# -----------------------------------------------------------------------------
if [ ! -f "./format_code.sh" ]; then
echo -e "${RED}ERROR: format_code.sh not found!${NC}"
exit 1
fi
# Check the STAGED index content (--staged), not the working tree (--changed):
# the index is what this commit records, so a working tree formatted after a
# `git add` can't mask an unformatted staged blob.
echo -e "${BLUE}Pre-commit: checking formatting of staged content…${NC}"
./format_code.sh --staged --check
FORMAT_RESULT=$?
if [ $FORMAT_RESULT -ne 0 ]; then
echo ""
echo -e "${RED}Formatting check failed.${NC}"
echo -e "${YELLOW}Fix with: ./format_code.sh --changed${NC}"
echo -e "${YELLOW}Then re-stage and commit again.${NC}"
echo -e "${YELLOW}Bypass: git commit --no-verify${NC}"
exit 1
fi
# -----------------------------------------------------------------------------
# Phase 2 — build + test (conditional)
# -----------------------------------------------------------------------------
# Only run the CI test suite when the staged changes can actually affect it.
# Source code, headers, the test suite itself, and the build configuration
# trigger it. Pure docs / config / web-only commits skip the test phase so
# fast-iteration cycles stay fast.
STAGED=$(git diff --cached --name-only --diff-filter=ACMR)
SHOULD_TEST=0
if [ -n "$STAGED" ]; then
if echo "$STAGED" | grep -qE '^(src|include|common|tests|benchmarks|cmake)/|(^|/)CMakeLists\.txt$|\.cmake$'; then
SHOULD_TEST=1
fi
fi
if [ "$SHOULD_TEST" -eq 0 ]; then
echo -e "${GREEN}Pre-commit: format OK; no source/build files staged, skipping test suite.${NC}"
run_local_extension
exit 0
fi
# Locate an existing build directory. Prefer build-debug (the developer's
# daily driver per CLAUDE.md); fall back to build-ci or other variants.
BUILD_DIR=""
for d in build-debug build-ci build-full build; do
if [ -f "$d/CMakeCache.txt" ]; then
BUILD_DIR="$d"
break
fi
done
if [ -z "$BUILD_DIR" ]; then
echo -e "${YELLOW}Pre-commit: format OK; no build directory found, skipping test suite.${NC}"
echo -e "${YELLOW} Bootstrap with: cmake --preset debug${NC}"
run_local_extension
exit 0
fi
BUILD_LOG=$(mktemp -t dawn-precommit-build.XXXXXX.log)
TEST_LOG=$(mktemp -t dawn-precommit-test.XXXXXX.log)
trap 'rm -f "$BUILD_LOG" "$TEST_LOG"' EXIT
echo -e "${BLUE}Pre-commit: building tests-ci in $BUILD_DIR…${NC}"
if ! make -C "$BUILD_DIR" -j"$(nproc)" tests-ci > "$BUILD_LOG" 2>&1; then
echo ""
echo -e "${RED}Test build FAILED. Last 30 lines:${NC}"
tail -30 "$BUILD_LOG"
PRESERVED=$(mktemp -t dawn-precommit-build-failed.XXXXXX.log)
cp "$BUILD_LOG" "$PRESERVED"
echo ""
echo -e "${YELLOW}Full log preserved at: $PRESERVED${NC}"
echo -e "${YELLOW}Bypass: git commit --no-verify${NC}"
exit 1
fi
echo -e "${BLUE}Pre-commit: running CI test suite…${NC}"
if ! ctest --test-dir "$BUILD_DIR" --output-on-failure --no-tests=error -L ci > "$TEST_LOG" 2>&1; then
echo ""
echo -e "${RED}Test suite FAILED. Failing cases:${NC}"
grep -E "FAIL:|\*\*\*Failed|tests failed" "$TEST_LOG" | head -20
PRESERVED=$(mktemp -t dawn-precommit-test-failed.XXXXXX.log)
cp "$TEST_LOG" "$PRESERVED"
echo ""
echo -e "${YELLOW}Reproduce: ctest --test-dir $BUILD_DIR --output-on-failure -L ci${NC}"
echo -e "${YELLOW}Full log: $PRESERVED${NC}"
echo -e "${YELLOW}Bypass: git commit --no-verify${NC}"
exit 1
fi
PASS_LINE=$(grep -E "tests passed" "$TEST_LOG" | tail -1)
if [ -n "$PASS_LINE" ]; then
echo -e "${GREEN}Pre-commit: $PASS_LINE${NC}"
else
echo -e "${GREEN}Pre-commit: test suite passed.${NC}"
fi
run_local_extension
exit 0