11name : Custom Commit Message Validator
22
3-
4-
5-
63on :
7-
84 pull_request :
9-
105 types : [opened, reopened, synchronize]
116
12-
13-
14-
157jobs :
16-
178 validate_COMMIT_MESSAGEs :
18-
199 runs-on : ubuntu-latest
2010
21-
22-
23-
2411 permissions :
25-
2612 contents : read
27-
2813 pull-requests : read
2914
30-
31-
32-
3315 steps :
34-
3516 - name : Checkout Repository
36-
3717 uses : actions/checkout@v4
38-
3918 with :
40-
4119 fetch-depth : 0
4220
43-
44-
45-
4621 - name : Get PR Commits SHAs
47-
4822 id : get_pr_commits
49-
5023 run : |
51-
5224 # Fetch all commit SHAs associated with the current Pull Request
53-
54-
55-
5625 PR_COMMIT_SHAS=$(gh pr view ${{ github.event.pull_request.number }} --json commits --jq '[.commits[].oid] | join(" ")')
57-
58-
59-
60-
6126 echo "PR_COMMITS_LIST=${PR_COMMIT_SHAS}" >> "$GITHUB_OUTPUT"
62-
6327 env :
64-
65-
6628 GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
6729
68-
69-
70-
7130 - name : Validate Each Commit Message
72-
7331 env :
74-
7532 TARGET_DATE : " 2025-07-22" # Only check commits after this date
76-
7733 SKIP_KEYWORDS : " Merge,Revert,Release" # Comma-separated keywords to skip validation
78-
7934 run : |
80-
8135 # Read commit SHAs into array
82-
83-
8436 IFS=' ' read -r -a commit_shas <<< "${{ steps.get_pr_commits.outputs.PR_COMMITS_LIST }}"
85-
86-
87-
8837 has_validation_failed=false
89-
90-
91-
9238 # Convert skip keywords to array
93-
9439 IFS=',' read -r -a skip_keywords <<< "$SKIP_KEYWORDS"
95-
9640
97-
9841 for commit_sha in "${commit_shas[@]}"; do
99-
10042 echo "--- Checking commit: ${commit_sha} ---"
101-
10243
103-
10444 # Get commit timestamps for date filtering
105-
10645 COMMIT_DATE_UNIX=$(git log -n 1 --format=%at "${commit_sha}")
107-
10846 TARGET_DATE_UNIX=$(date -d "${TARGET_DATE}" +"%s")
109-
11047 COMMIT_DATE_HUMAN=$(git log -n 1 --format=%ad --date=iso-strict "${commit_sha}")
111-
112-
113-
11448 # Skip older commits
115-
11649 if (( COMMIT_DATE_UNIX < TARGET_DATE_UNIX )); then
117-
11850 echo "Skipping commit ${commit_sha} (Date: ${COMMIT_DATE_HUMAN}) as it is older than ${TARGET_DATE}."
119-
12051 continue
121-
12252 fi
123-
124-
125-
12653 echo "Processing commit ${commit_sha} (Date: ${COMMIT_DATE_HUMAN}) as it is on or after ${TARGET_DATE}."
127-
12854 COMMIT_MESSAGE=$(git log --format=%B -n 1 "${commit_sha}")
129-
130-
131-
13255 echo "Message content:"
133-
13456 echo "${COMMIT_MESSAGE}"
135-
13657 echo ""
137-
138-
139-
14058 # Check for skip keywords
141-
14259 skip_this_commit=false
143-
14460 for keyword in "${skip_keywords[@]}"; do
145-
14661 # Trim whitespace from keyword
147-
14862 trimmed_keyword=$(echo "$keyword" | xargs)
149-
15063
151-
15264 if [[ -n "$trimmed_keyword" ]] &&
153-
15465 [[ "${COMMIT_MESSAGE}" == *"$trimmed_keyword"* ]]; then
155-
15666 echo "Skipping validation for commit ${commit_sha} due to skip keyword: ${trimmed_keyword}"
157-
15867 skip_this_commit=true
159-
16068 break
161-
16269 fi
163-
16470 done
165-
16671
167-
16872 if [ "$skip_this_commit" = true ]; then
169-
17073 continue
171-
17274 fi
173-
174-
175-
17675 # --- Validation Logic Start ---
177-
17876
179-
18077 # 1. Check Conventional Commit format
181-
18278 if [[ ! "${COMMIT_MESSAGE}" =~ ^(feat|fix|docs|chore|style|refactor|perf|test|build|ci|revert)(\([a-zA-Z0-9_-]+\))?(!?): ]]; then
183-
18479 echo "::error file=COMMIT_MESSAGE::Commit ${commit_sha} does not start with a conventional commit type. Message: '${COMMIT_MESSAGE}'"
185-
18680 has_validation_failed=true
187-
18881 continue
189-
19082 fi
191-
192-
193-
19483 # Extract commit subject (first line)
195-
19684 commit_subject=$(echo "${COMMIT_MESSAGE}" | head -n 1)
197-
198-
199-
20085 # 2. NEW: Check minimum subject length (15 characters)
201-
20286 if [[ ${#commit_subject} -lt 15 ]]; then
203-
20487 echo "::error file=COMMIT_MESSAGE::Commit ${commit_sha} subject is too short (min 15 characters). Length: ${#commit_subject}. Subject: '${commit_subject}'"
205-
20688 has_validation_failed=true
207-
20889 continue
209-
21090 fi
211-
212-
213-
21491 # 3. Check maximum subject length (72 characters)
215-
21692 if [[ ${#commit_subject} -gt 72 ]]; then
217-
21893 echo "::warning file=COMMIT_MESSAGE::Commit ${commit_sha} subject line exceeds 72 characters. Length: ${#commit_subject}. Subject: '${commit_subject}'"
219-
220-
22194 fi
222-
223-
224-
22595 # 4. Check empty line between subject and body
226-
22796 if [[ $(echo "${COMMIT_MESSAGE}" | wc -l) -gt 1 ]]; then
228-
22997 second_line=$(echo "${COMMIT_MESSAGE}" | awk 'NR==2 {print}' | xargs)
230-
23198 if [[ -n "${second_line}" ]]; then
232-
23399 echo "::error file=COMMIT_MESSAGE::Commit ${commit_sha} is missing an empty line between subject and body. Message: '${COMMIT_MESSAGE}'"
234-
235-
236-
237-
238100 has_validation_failed=true
239-
240101 continue
241-
242102 fi
243-
244103 fi
245-
246-
247-
248104 done
249-
250-
251-
252-
253-
254-
255-
256-
257-
258105 # Fail job if any validation errors occurred
259-
260-
261106 if [ "$has_validation_failed" = true ]; then
262-
263107 echo "::error::One or more commit messages failed validation. Please review the errors above."
264-
265108 exit 1
266-
267109 fi
268-
269-
270-
271110 echo "All relevant commit messages in the PR passed validation."
0 commit comments