-
Notifications
You must be signed in to change notification settings - Fork 35
159 lines (147 loc) · 5.82 KB
/
Copy pathcommit-message-check.yml
File metadata and controls
159 lines (147 loc) · 5.82 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
name: Commit Message Check
on:
pull_request:
types: [opened, edited, reopened, synchronize]
branches: [main]
push:
branches: [main]
permissions:
contents: read
jobs:
check-pr-title:
name: pr-title-check
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- name: Validate PR title against Conventional Commits
env:
PR_TITLE: ${{ github.event.pull_request.title }}
run: |
set -euo pipefail
pattern='^(feat|fix|chore|docs|test|refactor|perf|style|build|ci|revert|release)(\([a-z0-9_./-]+\))?!?: .+'
if [[ ! "$PR_TITLE" =~ $pattern ]]; then
{
echo "::error title=PR title invalid::PR title does not match Conventional Commits"
echo ""
echo "PR title: $PR_TITLE"
echo ""
echo "PRs are squash-merged into main, so the PR title becomes the commit"
echo "subject on main. It must follow Conventional Commits."
echo ""
echo "Required format: <type>(<optional-scope>)<!>: <description>"
echo ""
echo "Allowed types: feat, fix, chore, docs, test, refactor, perf, style,"
echo " build, ci, revert, release."
echo ""
echo "Examples:"
echo " feat(api): add idempotency-key header"
echo " fix(core): reject on malformed JSON instead of crashing"
echo " chore(deps): bump c8 to ^10.1.4"
echo ""
echo "How to fix: edit the PR title in the GitHub UI; the workflow"
echo "will re-run automatically on the 'edited' event."
} >&2
exit 1
fi
echo "PR title OK: $PR_TITLE"
check-pr-branch:
name: branch-name-check
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- name: Validate PR branch against semantic branch convention
env:
HEAD_REF: ${{ github.event.pull_request.head.ref }}
run: |
set -euo pipefail
branch_pattern='^([a-z0-9][a-z0-9._-]*/)?(feat|fix|chore|docs|test|refactor|perf|style|build|ci|revert)/[a-z0-9][a-z0-9._-]*(/[a-z0-9][a-z0-9._-]*)*$'
release_pattern='^release/v[0-9]+\.[0-9]+\.[0-9]+$'
case "$HEAD_REF" in
main|master|develop|dev|trunk|dependabot/*|renovate/*|github-actions/*)
echo "PR branch OK: $HEAD_REF"
exit 0
;;
esac
if [[ "$HEAD_REF" =~ $branch_pattern || "$HEAD_REF" =~ $release_pattern ]]; then
echo "PR branch OK: $HEAD_REF"
exit 0
fi
{
echo "::error title=PR branch invalid::PR branch does not match the semantic branch convention"
echo ""
echo "PR branch: $HEAD_REF"
echo ""
echo "Required branch format:"
echo " <type>/<slug>"
echo " <namespace>/<type>/<slug>"
echo " release/vX.Y.Z"
echo ""
echo "Allowed types: feat, fix, chore, docs, test, refactor, perf, style,"
echo " build, ci, revert."
echo ""
echo "Examples:"
echo " feat/semantic-git-hooks"
echo " fix/pre-push-range"
echo " codex/chore/enforce-git-conventions"
echo " release/v1.7.0"
} >&2
exit 1
check-commits:
name: commit-subjects-check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0
- name: Validate commit subjects in range
env:
EVENT_NAME: ${{ github.event_name }}
PR_BASE: ${{ github.event.pull_request.base.sha }}
PR_HEAD: ${{ github.event.pull_request.head.sha }}
PUSH_BEFORE: ${{ github.event.before }}
PUSH_AFTER: ${{ github.event.after }}
run: |
set -euo pipefail
pattern='^(feat|fix|chore|docs|test|refactor|perf|style|build|ci|revert|release)(\([a-z0-9_./-]+\))?!?: .+'
if [ "$EVENT_NAME" = "pull_request" ]; then
range="$PR_BASE..$PR_HEAD"
else
zero='0000000000000000000000000000000000000000'
if [ "$PUSH_BEFORE" = "$zero" ]; then
range="$PUSH_AFTER~1..$PUSH_AFTER"
else
range="$PUSH_BEFORE..$PUSH_AFTER"
fi
fi
fail=0
while IFS= read -r line; do
sha="${line%% *}"
subject="${line#* }"
case "$subject" in
Merge\ *|Revert\ *|fixup!*|squash!*|amend!*) continue ;;
esac
if [[ ! "$subject" =~ $pattern ]]; then
echo "::error title=Commit ${sha:0:8} invalid::$subject"
fail=1
fi
done < <(git log --format='%H %s' "$range")
if [ "$fail" -ne 0 ]; then
{
echo ""
echo "One or more commit subjects do not match Conventional Commits."
echo ""
echo "Required format: <type>(<optional-scope>)<!>: <description>"
echo "Allowed types: feat, fix, chore, docs, test, refactor, perf, style,"
echo " build, ci, revert, release."
echo ""
echo "Examples:"
echo " feat(api): add idempotency-key header"
echo " fix(core): reject on malformed JSON instead of crashing"
echo ""
echo "How to fix on a PR: rewrite the offending commits with"
echo " git rebase -i origin/main"
echo "and change 'pick' to 'reword' on each one. Then force-push the branch."
} >&2
exit 1
fi
echo "All commit subjects OK."