11#! /usr/bin/env bash
22
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)
87#
98# Merge commits are excluded from this check.
109
@@ -24,26 +23,215 @@ BASE_COMMIT="0b8be2c15160c216e8b6ec82c99a000e81c0e429"
2423# Get a list of non-merge commit hashes after BASE_COMMIT.
2524commits=$( git rev-list --no-merges " ${BASE_COMMIT} " ..HEAD)
2625
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
2752failed=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" )
2898
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]} "
32111
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
35124
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
42214 fi
43215done
44216
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} "
47235fi
48236
49237exit 0
0 commit comments