@@ -4,7 +4,7 @@ permissions:
44 pull-requests : read
55on :
66 pull_request :
7- types : ['opened', 'edited', 'reopened', 'synchronize']
7+ types : ['opened', 'edited', 'reopened', 'synchronize', 'labeled', 'unlabeled' ]
88 branches-ignore :
99 - " v[0-9]+.[0-9]+.[0-9]+.[0-9]+"
1010 - release
1515
1616jobs :
1717 detect-changes :
18+ if : ${{ !contains(github.event.pull_request.labels.*.name, 'skip-pr-title-semver-check') }}
1819 runs-on : ubuntu-latest
1920 outputs :
2021 changed_crates : ${{ steps.detect.outputs.crates }}
7879
7980 semver-check :
8081 needs : detect-changes
81- if : needs.detect-changes.outputs.has_rust_changes == 'true'
82+ if : ${{ needs.detect-changes.outputs.has_rust_changes == 'true' && !contains(github.event.pull_request.labels.*.name, 'skip-pr-title-semver-check') }}
8283 runs-on : ubuntu-latest
8384 outputs :
8485 result_json : ${{ steps.semver.outputs.result_json }}
@@ -156,7 +157,7 @@ jobs:
156157
157158 validate :
158159 needs : [detect-changes, semver-check]
159- if : needs.detect-changes.outputs.has_rust_changes == 'true'
160+ if : ${{ needs.detect-changes.outputs.has_rust_changes == 'true' && !contains(github.event.pull_request.labels.*.name, 'skip-pr-title-semver-check') }}
160161 runs-on : ubuntu-latest
161162 steps :
162163 - name : Validate PR title against semver changes
@@ -206,52 +207,30 @@ jobs:
206207 echo ""
207208
208209 VALIDATION_FAILED="false"
210+ FAILURE_REASONS=()
209211
210- # Validation rules
212+ # Rule: ci/docs/style/test/build cannot change the public API
211213 case "$TYPE" in
212- fix)
213- if [[ "$SEMVER_LEVEL" == "major" ]] && [[ -z "$IS_BREAKING_CHANGE" ]]; then
214+ ci|docs|style|test|build)
215+ if [[ "$SEMVER_LEVEL" == "major" ]] || [[ "$SEMVER_LEVEL" == "minor" ]]; then
216+ VALIDATION_FAILED="true"
217+ FAILURE_REASONS+=("'$TYPE' cannot have major or minor API changes. Use a different PR type, or avoid public API changes.")
218+ fi
219+ ;;
220+ feat|fix|refactor|chore|perf|revert)
221+ # These can be any semver level (subject to breaking change rules below)
222+ ;;
223+ *)
214224 VALIDATION_FAILED="true"
215- elif [[ "$SEMVER_LEVEL" == "minor" ]] || [[ "$SEMVER_LEVEL" == "none" ]]; then
216- VALIDATION_FAILED="true"
217- fi
218- ;;
219-
220- feat)
221- if [[ "$SEMVER_LEVEL" == "major" ]] && [[ -z "$IS_BREAKING_CHANGE" ]]; then
222- VALIDATION_FAILED="true"
223- elif [[ "$SEMVER_LEVEL" == "patch" ]] || [[ "$SEMVER_LEVEL" == "none" ]]; then
224- VALIDATION_FAILED="true"
225- fi
226- ;;
227-
228- chore|ci|docs|style|test|build|perf)
229- # Breaking change marker shouldn't be there.
230- if [[ -n "$IS_BREAKING_CHANGE" ]]; then
231- VALIDATION_FAILED="true"
232- fi
233-
234- # These should not change public API
235- if [[ "$SEMVER_LEVEL" == "major" ]] || [[ "$SEMVER_LEVEL" == "minor" ]]; then
236- VALIDATION_FAILED="true"
237- fi
238- ;;
239-
240- refactor)
241- if [[ "$SEMVER_LEVEL" == "major" ]] && [[ -z "$IS_BREAKING_CHANGE" ]]; then
242- VALIDATION_FAILED="true"
243- fi
244- ;;
245-
246- revert)
247- # Revert commits are allowed to have any semver level
248- ;;
225+ FAILURE_REASONS+=("Unknown PR type: '$TYPE'. Valid types: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert")
226+ ;;
227+ esac
249228
250- *)
251- echo "$TYPE not handled";
229+ # Rule: major API changes must have a breaking change marker
230+ if [[ "$SEMVER_LEVEL" == "major" ]] && [[ -z "$IS_BREAKING_CHANGE" ]]; then
252231 VALIDATION_FAILED="true"
253- ;;
254- esac
232+ FAILURE_REASONS+=("Major API changes require a breaking change marker. Add '!' to PR title (e.g. 'feat!:' or 'feat(scope)!:') or add 'BREAKING CHANGE:' footer in PR body.")
233+ fi
255234
256235 if [[ "$VALIDATION_FAILED" == "true" ]]; then
257236 echo ""
@@ -267,69 +246,21 @@ jobs:
267246 echo "--------------------------------------------"
268247 echo "WHAT WAS DETECTED:"
269248 echo "--------------------------------------------"
270- # Show details for each crate
271249 echo "$SEMVER_RESULT_JSON" | jq -r '.crates[] | "Crate: \(.name)\n Level: \(.level)\n Reason: \(.reason)\n Details:\n\(.details | split("\n") | map(" " + .) | join("\n"))\n"'
272250 echo ""
273251 echo "--------------------------------------------"
274252 echo "WHY THIS FAILED:"
275253 echo "--------------------------------------------"
276- case "$TYPE" in
277- fix)
278- if [[ "$SEMVER_LEVEL" == "major" ]] && [[ -z "$IS_BREAKING_CHANGE" ]]; then
279- echo "'fix' with major changes requires breaking change marker."
280- echo "Add '!' to PR title (fix!:) or add 'BREAKING CHANGE:' footer in PR body."
281- elif [[ "$SEMVER_LEVEL" == "minor" ]]; then
282- echo "'fix' cannot have minor-level changes (new public API)."
283- echo "Use 'feat' type instead, or remove the new public API additions."
284- elif [[ "$SEMVER_LEVEL" == "none" ]]; then
285- echo "'fix' requires changes to published crates."
286- echo "Use 'chore' or 'ci' for non-published changes."
287- fi
288- ;;
289- feat)
290- if [[ "$SEMVER_LEVEL" == "major" ]] && [[ -z "$IS_BREAKING_CHANGE" ]]; then
291- echo "'feat' with major changes requires breaking change marker."
292- echo "Add '!' to PR title (feat!:) or add 'BREAKING CHANGE:' footer in PR body."
293- elif [[ "$SEMVER_LEVEL" == "patch" ]]; then
294- echo "'feat' requires minor-level changes (new public API)."
295- echo "Use 'fix' for bug fixes, or ensure new items are marked 'pub'."
296- elif [[ "$SEMVER_LEVEL" == "none" ]]; then
297- echo "'feat' requires changes to published crates."
298- echo "Use 'chore' for non-published changes."
299- fi
300- ;;
301- chore|ci|docs|style|test|build|perf)
302- if [[ -n "$IS_BREAKING_CHANGE" ]]; then
303- echo "'$TYPE' cannot have breaking change marker."
304- echo "Remove '!' from title or use 'feat!', 'fix!', or 'refactor!' instead."
305- elif [[ "$SEMVER_LEVEL" == "major" ]]; then
306- echo "'$TYPE' cannot have major-level changes (breaking API)."
307- echo "Use 'refactor!' or 'feat!' for intentional breaking changes."
308- elif [[ "$SEMVER_LEVEL" == "minor" ]]; then
309- echo "'$TYPE' cannot have minor-level changes (new public API)."
310- echo "Use 'feat' for new features, or mark new items as pub(crate)."
311- fi
312- ;;
313- refactor)
314- if [[ "$SEMVER_LEVEL" == "major" ]] && [[ -z "$IS_BREAKING_CHANGE" ]]; then
315- echo "'refactor' with major changes requires breaking change marker."
316- echo "Add '!' to PR title (refactor!:) or add 'BREAKING CHANGE:' footer in PR body."
317- fi
318- ;;
319- *)
320- echo "Unknown PR type: '$TYPE'"
321- echo "Valid types: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert"
322- ;;
323- esac
254+ for reason in "${FAILURE_REASONS[@]}"; do
255+ echo "- $reason"
256+ done
324257 echo ""
325258 echo "--------------------------------------------"
326259 echo "VALID COMBINATIONS:"
327260 echo "--------------------------------------------"
328- echo " fix -> patch, or major (with '!')"
329- echo " feat -> minor, or major (with '!')"
330- echo " refactor -> patch, minor, or major (with '!')"
331- echo " chore/ci/docs/style/test/build/perf -> patch or none only"
332- echo " revert -> any level"
261+ echo " ci/docs/style/test/build -> patch or none only (no public API changes)"
262+ echo " all other types -> any level allowed"
263+ echo " major changes -> must have '!' or 'BREAKING CHANGE:' footer"
333264 echo ""
334265 exit 1
335266 else
0 commit comments