1
1
#! /usr/bin/env bash
2
2
3
- # Check that every non-merge commit after the specified base commit has a commit
4
- # message ending with a valid Change-Id line. A valid Change-Id line must be the
5
- # last non-empty line of the commit message and follow the format:
6
- #
7
- # Change-Id: I<hexadecimal_hash>
3
+ # This script checks:
4
+ # 1. Change-Id presence (indicates commit-msg hook processing)
5
+ # 2. Commit message quality (indicates pre-commit hook compliance)
6
+ # 3. Bypass detection (detects --no-verify usage or web interface commits)
8
7
#
9
8
# Merge commits are excluded from this check.
10
9
@@ -24,26 +23,215 @@ BASE_COMMIT="0b8be2c15160c216e8b6ec82c99a000e81c0e429"
24
23
# Get a list of non-merge commit hashes after BASE_COMMIT.
25
24
commits=$( git rev-list --no-merges " ${BASE_COMMIT} " ..HEAD)
26
25
26
+ # Hook bypass detection patterns
27
+ BYPASS_INDICATORS=(
28
+ " --no-verify"
29
+ " WIP"
30
+ )
31
+
32
+ # Quality patterns that indicate hook processing
33
+ PROBLEMATIC_PATTERNS=(
34
+ ' ^[a-z]' # Uncapitalized subjects
35
+ ' \.$' # Ending with period
36
+ ' ^.{1,10}$' # Too short subjects
37
+ ' ^.{80,}' # Too long subjects
38
+ ' ^(Update|Fix|Change|Modify) [a-zA-Z0-9_-]+\.(c|h)$' # Generic filename updates
39
+ )
40
+
41
+ # Early exit if no commits to check
42
+ [[ -z " $commits " ]] && { echo -e " ${GREEN} No commits to check.${NC} " ; exit 0; }
43
+
44
+ # Pre-compute indicator patterns for faster matching
45
+ bypass_pattern=" "
46
+ for indicator in " ${BYPASS_INDICATORS[@]} " ; do
47
+ bypass_pattern+=" |${indicator,,} "
48
+ done
49
+ bypass_pattern=" ${bypass_pattern# |} "
50
+
51
+ # Ultra-fast approach: minimize git calls and parsing overhead
27
52
failed=0
53
+ warnings=0
54
+ suspicious_commits=()
55
+
56
+ # Cache all commit data at once using the most efficient git format
57
+ declare -A commit_cache short_cache subject_cache msg_cache
58
+
59
+ # Single git call to populate all caches - handle multiline messages properly
60
+ current_commit=" "
61
+ current_msg=" "
62
+ reading_msg=false
63
+
64
+ while IFS= read -r line; do
65
+ case " $line " in
66
+ " COMMIT " * )
67
+ # Save previous message if we were reading one
68
+ if [[ " $reading_msg " = true && -n " $current_commit " ]]; then
69
+ msg_cache[" $current_commit " ]=" $current_msg "
70
+ fi
71
+ current_commit=" ${line# COMMIT } "
72
+ commit_cache[" $current_commit " ]=1
73
+ reading_msg=false
74
+ current_msg=" "
75
+ ;;
76
+ " SHORT " * )
77
+ short_cache[" $current_commit " ]=" ${line# SHORT } "
78
+ ;;
79
+ " SUBJECT " * )
80
+ subject_cache[" $current_commit " ]=" ${line# SUBJECT } "
81
+ ;;
82
+ " MSGSTART" )
83
+ reading_msg=true
84
+ current_msg=" "
85
+ ;;
86
+ * )
87
+ # If we're reading a message, accumulate lines
88
+ if [[ " $reading_msg " = true ]]; then
89
+ if [[ -z " $current_msg " ]]; then
90
+ current_msg=" $line "
91
+ else
92
+ current_msg=" $current_msg " $' \n ' " $line "
93
+ fi
94
+ fi
95
+ ;;
96
+ esac
97
+ done < <( git log --format=" COMMIT %H%nSHORT %h%nSUBJECT %s%nMSGSTART%n%B" --no-merges " ${BASE_COMMIT} ..HEAD" )
28
98
29
- for commit in $commits ; do
30
- # Retrieve the commit message for the given commit.
31
- commit_msg=$( git log -1 --format=%B " ${commit} " )
99
+ # Save the last message
100
+ if [[ " $reading_msg " = true && -n " $current_commit " ]]; then
101
+ msg_cache[" $current_commit " ]=" $current_msg "
102
+ fi
103
+
104
+ # Process cached data - no more git calls needed
105
+ for commit in " ${! commit_cache[@]} " ; do
106
+ [[ -z " $commit " ]] && continue
107
+
108
+ short_hash=" ${short_cache[$commit]} "
109
+ subject=" ${subject_cache[$commit]} "
110
+ full_msg=" ${msg_cache[$commit]} "
32
111
33
- # Extract the last non-empty line from the commit message.
34
- last_line=$( echo " $commit_msg " | awk ' NF {line=$0} END {print line}' )
112
+ # Initialize issue tracking
113
+ has_issues=0
114
+ has_warnings=0
115
+ issue_list=" "
116
+ warning_list=" "
117
+
118
+ # Check 1: Change-Id validation (fastest check first)
119
+ if [[ " $full_msg " != * " Change-Id: I" * ]]; then
120
+ has_issues=1
121
+ issue_list+=" Missing valid Change-Id (likely bypassed commit-msg hook)|"
122
+ (( failed++ ))
123
+ fi
35
124
36
- # Check if the last line matches the expected Change-Id format.
37
- if [[ ! $last_line =~ ^Change-Id:\ I[0-9a-fA-F]+$ ]]; then
38
- subject=$( git log -1 --format=%s " ${commit} " )
39
- short_hash=$( git rev-parse --short " ${commit} " )
40
- echo " Commit ${short_hash} with subject '$subject ' does not end with a valid Change-Id."
41
- failed=1
125
+ # Check 2: Bypass indicators (single pattern match)
126
+ full_msg_lower=" ${full_msg,,} "
127
+ if [[ " $full_msg_lower " =~ ($bypass_pattern ) ]]; then
128
+ has_warnings=1
129
+ warning_list+=" Contains bypass indicator: '${BASH_REMATCH[1]} '|"
130
+ (( warnings++ ))
131
+ fi
132
+
133
+ # Check 3: Subject validation (batch character operations)
134
+ subject_len=${# subject}
135
+ first_char=" ${subject: 0: 1} "
136
+ last_char=" ${subject: -1} "
137
+
138
+ # Length checks
139
+ if [[ $subject_len -le 10 ]]; then
140
+ has_warnings=1
141
+ warning_list+=" Subject very short ($subject_len chars)|"
142
+ (( warnings++ ))
143
+ elif [[ $subject_len -ge 80 ]]; then
144
+ has_issues=1
145
+ issue_list+=" Subject too long ($subject_len chars)|"
146
+ (( failed++ ))
147
+ fi
148
+
149
+ # Character validation using ASCII values
150
+ if [[ ${# first_char} -eq 1 ]]; then
151
+ # Check if it's a lowercase letter
152
+ case " $first_char " in
153
+ [a-z])
154
+ has_issues=1
155
+ issue_list+=" Subject not capitalized|"
156
+ (( failed++ ))
157
+ ;;
158
+ esac
159
+ fi
160
+
161
+ # Period check
162
+ if [[ " $last_char " == " ." ]]; then
163
+ has_issues=1
164
+ issue_list+=" Subject ends with period|"
165
+ (( failed++ ))
166
+ fi
167
+
168
+ # Generic filename check (simplified pattern)
169
+ if [[ " $subject " =~ ^(Update| Fix| Change| Modify)[[:space:]] ]]; then
170
+ if [[ " $subject " =~ \. (c| h)$ ]]; then
171
+ has_warnings=1
172
+ warning_list+=" Generic filename-only subject|"
173
+ (( warnings++ ))
174
+ fi
175
+ fi
176
+
177
+ # Check 4: Web interface (string contains check)
178
+ if [[ " $full_msg " == * " Co-authored-by:" * ]]; then
179
+ if [[ " $full_msg " != * " Change-Id:" * ]]; then
180
+ has_issues=1
181
+ issue_list+=" Likely created via GitHub web interface|"
182
+ (( failed++ ))
183
+ fi
184
+ fi
185
+
186
+ # Check 5: Queue.c body (most expensive - do last and only when needed)
187
+ if [[ " $full_msg " =~ ^[^$' \n ' ]* $' \n ' [[:space:]]* $' \n ' Change-Id: ]]; then
188
+ # Body appears empty - check if queue.c was modified
189
+ if git diff-tree --no-commit-id --name-only -r " $commit " | grep -q " ^queue\.c$" ; then
190
+ has_issues=1
191
+ issue_list+=" Missing commit body for queue.c changes|"
192
+ (( failed++ ))
193
+ fi
194
+ fi
195
+
196
+ # Report issues (only if found)
197
+ if [[ $has_issues -eq 1 || $has_warnings -eq 1 ]]; then
198
+ echo -e " ${YELLOW} Commit ${short_hash} :${NC} ${subject} "
199
+
200
+ if [[ $has_issues -eq 1 ]]; then
201
+ IFS=' |' read -ra issues <<< " ${issue_list%|}"
202
+ for issue in " ${issues[@]} " ; do
203
+ [[ -n " $issue " ]] && echo -e " [ ${RED} FAIL${NC} ] $issue "
204
+ done
205
+ suspicious_commits+=(" $short_hash : $subject " )
206
+ fi
207
+
208
+ if [[ $has_warnings -eq 1 ]]; then
209
+ IFS=' |' read -ra warnings_arr <<< " ${warning_list%|}"
210
+ for warning in " ${warnings_arr[@]} " ; do
211
+ [[ -n " $warning " ]] && echo -e " ${YELLOW} !${NC} $warning "
212
+ done
213
+ fi
42
214
fi
43
215
done
44
216
45
- if [ $failed -ne 0 ]; then
46
- throw " Some commits are missing a valid Change-Id. Please amend the commit messages accordingly."
217
+ if [[ $failed -gt 0 ]]; then
218
+ echo -e " \n${RED} Problematic commits detected:${NC} "
219
+ for commit in " ${suspicious_commits[@]} " ; do
220
+ echo -e " ${RED} •${NC} $commit "
221
+ done
222
+
223
+ echo -e " \n${RED} These commits likely bypassed git hooks. Recommended actions:${NC} "
224
+ echo -e " 1. ${YELLOW} Verify hooks are installed:${NC} scripts/install-git-hooks"
225
+ echo -e " 2. ${YELLOW} Never use --no-verify flag${NC} "
226
+ echo -e " 3. ${YELLOW} Avoid GitHub web interface for commits${NC} "
227
+ echo -e " 4. ${YELLOW} Amend commits if possible:${NC} git rebase -i ${BASE_COMMIT} "
228
+ echo
229
+
230
+ throw " Git hook compliance validation failed. Please fix the issues above."
231
+ fi
232
+
233
+ if [[ $warnings -gt 0 ]]; then
234
+ echo -e " \n${YELLOW} Some commits have quality warnings but passed basic validation.${NC} "
47
235
fi
48
236
49
237
exit 0
0 commit comments