78 lines
3 KiB
Bash
78 lines
3 KiB
Bash
#!/bin/bash
|
|
|
|
# Semantic commit pattern:
|
|
# feat|fix|docs|style|refactor|test|perf|build|ci|chore|revert(optional scope): message
|
|
# Allow dots (.), uppercase letters, and other common filename characters in scope
|
|
PATTERN='^(feat|fix|docs|style|refactor|test|perf|build|ci|chore|revert)(\([a-zA-Z0-9\-\.\_]+\))?: .{1,}$'
|
|
|
|
# Pattern for merge commits from pull requests - simplified to catch all formats
|
|
PR_MERGE_PATTERN='^Merge pull request'
|
|
|
|
# Pattern for standard Git merge commits
|
|
GIT_MERGE_PATTERN='^Merge (branch|remote-tracking branch) .+ into .+$'
|
|
|
|
# Set text colors
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[0;33m'
|
|
BLUE='\033[0;34m'
|
|
NC='\033[0m' # No Color
|
|
|
|
INVALID_COMMITS=0
|
|
|
|
# Read stdin (format: <old-value> <new-value> <ref-name>)
|
|
while read -r OLD_REV NEW_REV REF_NAME; do
|
|
# Skip if it's a branch deletion
|
|
if [[ "$NEW_REV" = "0000000000000000000000000000000000000000" ]]; then
|
|
continue
|
|
fi
|
|
|
|
# Skip if it's a new branch (no old revision)
|
|
if [[ "$OLD_REV" = "0000000000000000000000000000000000000000" ]]; then
|
|
OLD_REV=$(git rev-list --max-parents=0 "$NEW_REV")
|
|
fi
|
|
|
|
echo -e "${YELLOW}Checking commits in $REF_NAME${NC}"
|
|
|
|
# Get all commit hashes between old and new revision
|
|
COMMITS=$(git rev-list "$OLD_REV".."$NEW_REV")
|
|
|
|
# Check each commit message
|
|
for COMMIT in $COMMITS; do
|
|
COMMIT_MSG=$(git log --format=%B -n 1 "$COMMIT")
|
|
COMMIT_SUBJECT=$(echo "$COMMIT_MSG" | head -n 1)
|
|
PARENT_COUNT=$(git rev-list --parents -n 1 "$COMMIT" | awk '{print NF - 1}')
|
|
|
|
# Skip validation for merge commits (both PR merges and standard Git merges)
|
|
if [[ $PARENT_COUNT -gt 1 ]] && ([[ "$COMMIT_SUBJECT" =~ $PR_MERGE_PATTERN ]] || [[ "$COMMIT_SUBJECT" =~ $GIT_MERGE_PATTERN ]]); then
|
|
echo -e "${BLUE}➜ Skipping merge commit:${NC} $COMMIT_SUBJECT"
|
|
continue
|
|
fi
|
|
|
|
if ! [[ "$COMMIT_SUBJECT" =~ $PATTERN ]]; then
|
|
echo -e "${RED}✗ Invalid commit message:${NC} $COMMIT_SUBJECT"
|
|
echo -e "${RED} Commit:${NC} $COMMIT"
|
|
echo -e "${YELLOW} Message should follow semantic commit format:${NC}"
|
|
echo -e " type(scope): message"
|
|
echo -e " ${YELLOW}where type is one of:${NC} feat, fix, docs, style, refactor, test, perf, build, ci, chore, revert"
|
|
echo ""
|
|
INVALID_COMMITS=$((INVALID_COMMITS + 1))
|
|
else
|
|
echo -e "${GREEN}✓ Valid commit:${NC} $COMMIT_SUBJECT"
|
|
fi
|
|
done
|
|
done
|
|
|
|
# If any commits were invalid, reject the push
|
|
if [[ $INVALID_COMMITS -gt 0 ]]; then
|
|
echo -e "${RED}Push rejected: $INVALID_COMMITS commit(s) have invalid messages.${NC}"
|
|
echo -e "${YELLOW}Please rewrite your commit messages to follow the semantic commit format:${NC}"
|
|
echo "type(scope): message"
|
|
echo "Examples:"
|
|
echo " feat(auth): add login with Google"
|
|
echo " fix: correct calculation in billing module"
|
|
echo " style(release.yml): format permissions section in YAML"
|
|
exit 1
|
|
fi
|
|
|
|
exit 0
|