Repository navigation
Expand file tree
/
Copy pathcommit-msg
More file actions
executable file
·103 lines (90 loc) · 3.39 KB
/
Copy pathcommit-msg
File metadata and controls
executable file
·103 lines (90 loc) · 3.39 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
#!/usr/bin/env bash
# scripts/hooks/commit-msg — Conventional Commit validator
#
# Validates that commit messages follow the Conventional Commits specification:
# <type>(<optional scope>): <description>
#
# Allowed types:
# feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert, release, merge, sync
#
# Optional breaking change indicator '!' before colon:
# feat(api)!: remove deprecated endpoints
#
# Subject line rules:
# - Starts with a valid type
# - Colon followed by a space
# - Non-empty description
# - Does not end with a period
#
# Exemptions:
# - Merge commits (e.g. "Merge branch ...", "Merge pull request ...")
# - Rebase / fixup commits (e.g. "fixup! ...", "squash! ...")
# - Automated tooling when DEVFLOW_ALLOW_ANY_COMMIT=1
set -euo pipefail
commit_msg_file="$1"
if [ "${DEVFLOW_ALLOW_ANY_COMMIT:-0}" = "1" ]; then
exit 0
fi
if [ ! -f "$commit_msg_file" ]; then
exit 0
fi
# Read first line (subject)
subject=$(head -n 1 "$commit_msg_file" | sed 's/[[:space:]]*$//')
# Skip comments or empty lines
if [ -z "$subject" ] || [[ "$subject" =~ ^# ]]; then
exit 0
fi
# Allow merge / squash / fixup commits
if [[ "$subject" =~ ^Merge\ ]] || [[ "$subject" =~ ^Merge\ pull\ request ]] || [[ "$subject" =~ ^(fixup|squash|amend)\!\ ]]; then
exit 0
fi
pattern='^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert|release|merge|sync)(\([a-zA-Z0-9_.\/-]+\))?(\!)?: .+$'
if ! [[ "$subject" =~ $pattern ]]; then
echo "commit-msg error: invalid commit message format." >&2
echo "" >&2
echo " Commit message subject was:" >&2
echo " $subject" >&2
echo "" >&2
echo " Expected Conventional Commit format:" >&2
echo " <type>(<optional-scope>): <description>" >&2
echo "" >&2
echo " Allowed types:" >&2
echo " feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert, release, merge, sync" >&2
echo "" >&2
echo " Examples:" >&2
echo " feat(cli): add hermes driver validation flag" >&2
echo " fix(core): handle missing state file on initial start" >&2
echo " docs: update release cut guide in contributing.md" >&2
echo " chore: update workspace dependencies" >&2
exit 1
fi
# The header above documents "Does not end with a period", but the pattern's
# trailing `.+$` accepts one — verified with a negative control: the subject
# `feat(cli): trailing period.` passed while three other malformed controls
# were correctly rejected. Checked separately rather than folded into the
# pattern so a one-character description (`feat: x`) still passes.
if [[ "$subject" =~ \.$ ]]; then
echo "commit-msg error: commit subject must not end with a period." >&2
echo "" >&2
echo " Commit message subject was:" >&2
echo " $subject" >&2
exit 1
fi
if [ "${#subject}" -gt 72 ]; then
echo "commit-msg warning: commit subject is longer than 72 characters (${#subject} chars)." >&2
echo " Subject: $subject" >&2
fi
# Chain to global commit-msg hook if present
global_hooks=$(git config --global --get core.hooksPath || true)
if [ -n "$global_hooks" ]; then
global_hooks="${global_hooks/#\~/$HOME}"
if [ -x "$global_hooks/commit-msg" ]; then
exec "$global_hooks/commit-msg" "$@"
fi
fi
# Fall back to repo-local hook if displaced
repo_hook="$(git rev-parse --git-common-dir)/hooks/commit-msg"
if [ -x "$repo_hook" ]; then
exec "$repo_hook" "$@"
fi
exit 0