-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpre-commit
More file actions
executable file
·113 lines (94 loc) · 4.35 KB
/
pre-commit
File metadata and controls
executable file
·113 lines (94 loc) · 4.35 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
#!/usr/bin/env bash
# ─────────────────────────────────────────────────────────────────────────────
# Repository : https://github.com/ssx/git-hooks
# Author : Scott Robinson (https://github.com/ssx)
# License : MIT — see LICENSE for details
# ─────────────────────────────────────────────────────────────────────────────
#
# pre-commit runner
#
# Discovers check scripts from pre-commit.d/<scope>/ and runs them against
# staged files. Each file's extension is resolved to a scope via EXT_MAP.
#
# ── Extension → scope mapping ─────────────────────────────────────────────────
# Maps a file extension to a scope directory under pre-commit.d/.
# Format: "extension:scope"
#
# Files whose extension is not listed here only run "all" checks.
# A scope directory doesn't need to exist — it's silently skipped.
#
EXT_MAP=(
"php:php"
"phtml:php" # Magento/PHP templates treated as PHP
# "ts:js" # example: TypeScript runs js/ checks
# "scss:css" # example: SCSS runs css/ checks
)
# ── Adding a new check ────────────────────────────────────────────────────────
# 1. Create an executable script in pre-commit.d/<scope>/
# - pre-commit.d/all/ → runs on every staged file
# - pre-commit.d/<scope>/ → runs on files whose extension maps to <scope>
# 2. Name it NN-description (e.g. 05-no-exit) — NN controls run order.
# 3. Interface:
# $1 path to temp file containing the staged file content
# $2 original repo-relative file path (use this in error output)
# Print error detail to stdout. Exit 1 on failure, 0 on pass.
# ─────────────────────────────────────────────────────────────────────────────
HOOKS_DIR="$(cd "$(dirname "$0")" && pwd)"
CHECKS_DIR="$HOOKS_DIR/pre-commit.d"
# Resolve a file extension to its mapped scope. Prints the scope and returns 0
# on success, prints nothing and returns 1 if no mapping exists.
get_scope() {
local ext="$1" entry
for entry in "${EXT_MAP[@]}"; do
if [ "${entry%%:*}" = "$ext" ]; then
printf '%s' "${entry#*:}"
return 0
fi
done
return 1
}
# ── Main ──────────────────────────────────────────────────────────────────────
all_staged=$(git diff --cached --name-only --diff-filter=ACM 2>/dev/null) || true
[ -z "$all_staged" ] && exit 0
tmpdir=$(mktemp -d /tmp/pre-commit-XXXXXX)
trap 'rm -rf "$tmpdir"' EXIT
hook_failed=0
counter=0
while IFS= read -r file; do
counter=$((counter + 1))
tmp="$tmpdir/$counter"
git show ":$file" > "$tmp"
ext="${file##*.}"
scope=$(get_scope "$ext") || scope=""
# Build the list of scope dirs that apply to this file
scope_dirs=("$CHECKS_DIR/all")
if [ -n "$scope" ] && [ -d "$CHECKS_DIR/$scope" ]; then
scope_dirs=("${scope_dirs[@]}" "$CHECKS_DIR/$scope")
fi
file_header_shown=0
for scope_dir in "${scope_dirs[@]}"; do
[ -d "$scope_dir" ] || continue
for check in "$scope_dir"/*; do
[ -f "$check" ] && [ -x "$check" ] || continue
# Derive a readable label from the filename (strip NN- prefix and dashes)
label=$(basename "$check" | sed 's/^[0-9]*-//')
label="${label//-/ }"
output=$("$check" "$tmp" "$file" 2>&1)
status=$?
if [ "$status" -ne 0 ]; then
if [ "$file_header_shown" -eq 0 ]; then
printf '\n %s\n' "$file"
file_header_shown=1
fi
printf ' [FAIL] %s\n' "$label"
[ -n "$output" ] && printf '%s\n' "$output"
hook_failed=1
fi
done
done
done <<< "$all_staged"
if [ "$hook_failed" -ne 0 ]; then
printf '\n Pre-commit checks failed. Fix the issues above before committing.\n\n'
exit 1
fi
exit 0