|
| 1 | +name: Commit Lint |
| 2 | + |
| 3 | +on: |
| 4 | + pull_request: |
| 5 | + branches: [main, develop] |
| 6 | + types: [opened, synchronize, reopened, edited] |
| 7 | + |
| 8 | +permissions: |
| 9 | + contents: read |
| 10 | + pull-requests: read |
| 11 | + |
| 12 | +jobs: |
| 13 | + lint-commits: |
| 14 | + name: Validate Commit Messages |
| 15 | + runs-on: ubuntu-latest |
| 16 | + |
| 17 | + steps: |
| 18 | + - name: Harden Runner |
| 19 | + uses: step-security/harden-runner@v2 |
| 20 | + with: |
| 21 | + egress-policy: audit |
| 22 | + |
| 23 | + - name: Checkout code |
| 24 | + uses: actions/checkout@v4 |
| 25 | + with: |
| 26 | + fetch-depth: 0 |
| 27 | + |
| 28 | + - name: Validate commit messages |
| 29 | + run: | |
| 30 | + # Colors for output |
| 31 | + RED='\033[0;31m' |
| 32 | + GREEN='\033[0;32m' |
| 33 | + YELLOW='\033[1;33m' |
| 34 | + BLUE='\033[0;34m' |
| 35 | + NC='\033[0m' # No Color |
| 36 | +
|
| 37 | + echo -e "${BLUE}Validating commit messages...${NC}" |
| 38 | +
|
| 39 | + # Get the base branch |
| 40 | + BASE_REF="${{ github.event.pull_request.base.sha }}" |
| 41 | + HEAD_REF="${{ github.event.pull_request.head.sha }}" |
| 42 | +
|
| 43 | + # Get all commits in this PR |
| 44 | + COMMITS=$(git log --pretty=format:"%H %s" "$BASE_REF".."$HEAD_REF") |
| 45 | +
|
| 46 | + # Conventional commit pattern |
| 47 | + # Types: build, chore, ci, docs, feat, fix, perf, refactor, revert, style, test |
| 48 | + PATTERN="^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\([a-z0-9_-]+\))?(!)?: .+" |
| 49 | +
|
| 50 | + INVALID_COMMITS=() |
| 51 | + VALID_COUNT=0 |
| 52 | + TOTAL_COUNT=0 |
| 53 | +
|
| 54 | + while IFS= read -r line; do |
| 55 | + [[ -z "$line" ]] && continue |
| 56 | +
|
| 57 | + TOTAL_COUNT=$((TOTAL_COUNT + 1)) |
| 58 | + HASH=$(echo "$line" | awk '{print $1}') |
| 59 | + MESSAGE=$(echo "$line" | cut -d' ' -f2-) |
| 60 | +
|
| 61 | + if [[ "$MESSAGE" =~ $PATTERN ]]; then |
| 62 | + echo -e "${GREEN}✓${NC} $MESSAGE" |
| 63 | + VALID_COUNT=$((VALID_COUNT + 1)) |
| 64 | + else |
| 65 | + echo -e "${RED}✗${NC} $MESSAGE" |
| 66 | + INVALID_COMMITS+=("$HASH: $MESSAGE") |
| 67 | + fi |
| 68 | + done <<< "$COMMITS" |
| 69 | +
|
| 70 | + echo "" |
| 71 | + echo -e "${BLUE}Summary:${NC}" |
| 72 | + echo -e " Total commits: $TOTAL_COUNT" |
| 73 | + echo -e " Valid commits: ${GREEN}$VALID_COUNT${NC}" |
| 74 | + echo -e " Invalid commits: ${RED}$((TOTAL_COUNT - VALID_COUNT))${NC}" |
| 75 | +
|
| 76 | + # If there are invalid commits, fail the check |
| 77 | + if [ ${#INVALID_COMMITS[@]} -gt 0 ]; then |
| 78 | + echo "" |
| 79 | + echo -e "${RED}❌ Found invalid commit messages:${NC}" |
| 80 | + echo "" |
| 81 | + for commit in "${INVALID_COMMITS[@]}"; do |
| 82 | + echo -e " ${RED}✗${NC} $commit" |
| 83 | + done |
| 84 | + echo "" |
| 85 | + echo -e "${YELLOW}Commit messages must follow the Conventional Commits specification:${NC}" |
| 86 | + echo "" |
| 87 | + echo -e " Format: ${BLUE}type(scope): description${NC}" |
| 88 | + echo "" |
| 89 | + echo -e " Types:" |
| 90 | + echo -e " ${GREEN}feat${NC} - New feature" |
| 91 | + echo -e " ${GREEN}fix${NC} - Bug fix" |
| 92 | + echo -e " ${GREEN}docs${NC} - Documentation changes" |
| 93 | + echo -e " ${GREEN}style${NC} - Code style changes (formatting, etc.)" |
| 94 | + echo -e " ${GREEN}refactor${NC} - Code refactoring" |
| 95 | + echo -e " ${GREEN}perf${NC} - Performance improvements" |
| 96 | + echo -e " ${GREEN}test${NC} - Test changes" |
| 97 | + echo -e " ${GREEN}chore${NC} - Build process or auxiliary tool changes" |
| 98 | + echo -e " ${GREEN}ci${NC} - CI configuration changes" |
| 99 | + echo -e " ${GREEN}build${NC} - Build system changes" |
| 100 | + echo -e " ${GREEN}revert${NC} - Revert a previous commit" |
| 101 | + echo "" |
| 102 | + echo -e " Examples:" |
| 103 | + echo -e " ${BLUE}feat(auth): Add login functionality${NC}" |
| 104 | + echo -e " ${BLUE}fix(api): Resolve null pointer exception${NC}" |
| 105 | + echo -e " ${BLUE}docs(readme): Update README with installation steps${NC}" |
| 106 | + echo -e " ${BLUE}feat(auth)!: Breaking change in auth flow${NC}" |
| 107 | + echo "" |
| 108 | + echo -e " For more info: ${BLUE}https://www.conventionalcommits.org${NC}" |
| 109 | +
|
| 110 | + # Add to step summary |
| 111 | + { |
| 112 | + echo "## ❌ Commit Message Validation Failed" |
| 113 | + echo "" |
| 114 | + echo "The following commits do not follow the Conventional Commits specification:" |
| 115 | + echo "" |
| 116 | + for commit in "${INVALID_COMMITS[@]}"; do |
| 117 | + echo "- \`$commit\`" |
| 118 | + done |
| 119 | + echo "" |
| 120 | + echo "### Required Format" |
| 121 | + echo "" |
| 122 | + echo "\`\`\`" |
| 123 | + echo "type(scope): description" |
| 124 | + echo "\`\`\`" |
| 125 | + echo "" |
| 126 | + echo "### Valid Types" |
| 127 | + echo "" |
| 128 | + echo "- \`feat\` - New feature" |
| 129 | + echo "- \`fix\` - Bug fix" |
| 130 | + echo "- \`docs\` - Documentation changes" |
| 131 | + echo "- \`style\` - Code style changes" |
| 132 | + echo "- \`refactor\` - Code refactoring" |
| 133 | + echo "- \`perf\` - Performance improvements" |
| 134 | + echo "- \`test\` - Test changes" |
| 135 | + echo "- \`chore\` - Maintenance tasks" |
| 136 | + echo "- \`ci\` - CI changes" |
| 137 | + echo "- \`build\` - Build system changes" |
| 138 | + echo "" |
| 139 | + echo "### Examples" |
| 140 | + echo "" |
| 141 | + echo "- \`feat(auth): Add login functionality\`" |
| 142 | + echo "- \`fix(api): Resolve null pointer exception\`" |
| 143 | + echo "- \`docs(readme): Update README\`" |
| 144 | + echo "" |
| 145 | + echo "Learn more: [Conventional Commits](https://www.conventionalcommits.org)" |
| 146 | + } >> $GITHUB_STEP_SUMMARY |
| 147 | +
|
| 148 | + exit 1 |
| 149 | + fi |
| 150 | +
|
| 151 | + echo "" |
| 152 | + echo -e "${GREEN}✓ All commit messages are valid${NC}" |
| 153 | +
|
| 154 | + # Add success to step summary |
| 155 | + { |
| 156 | + echo "## ✅ Commit Message Validation Passed" |
| 157 | + echo "" |
| 158 | + echo "All $VALID_COUNT commit(s) follow the Conventional Commits specification." |
| 159 | + } >> $GITHUB_STEP_SUMMARY |
| 160 | +
|
| 161 | + exit 0 |
0 commit comments