Skip to content

Commit a7c3047

Browse files
authored
Merge pull request #304 from sysprog21/check-commitlog
Improve commit checker and add bypass detection
2 parents bc44e71 + 29d963b commit a7c3047

File tree

2 files changed

+207
-19
lines changed

2 files changed

+207
-19
lines changed

scripts/check-commitlog.sh

Lines changed: 206 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
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.
2524
commits=$(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
2752
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")
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
43215
done
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}"
47235
fi
48236

49237
exit 0

scripts/checksums

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
25e7d2797e09bfafa0c0dee70111104648faec9d queue.h
22
b26e079496803ebe318174bda5850d2cce1fd0c1 list.h
3-
1029c2784b4cae3909190c64f53a06cba12ea38e scripts/check-commitlog.sh
3+
c286e18579b6461fc289732cee4a18a916f79659 scripts/check-commitlog.sh

0 commit comments

Comments
 (0)