Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions .github/check-conventional-pr-title.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Copyright 2025 Canonical Ltd.
# See LICENSE file for licensing details.

"""Check that a PR title follows the Conventional Commits specification.

Reads the PR title from the PR_TITLE environment variable.
Exits with a non-zero status and prints an error message if the title is invalid.

Reference: https://www.conventionalcommits.org/en/v1.0.0/

This repo's commit types and optional component scopes follow HACKING.md.
"""

from __future__ import annotations

import os
import re
import sys

_TYPES = frozenset({
'build',
'chore',
'ci',
'docs',
'feat',
'fix',
'perf',
'refactor',
'style',
'test',
})

# <type>[optional scope][optional !]: <description>
_PATTERN = re.compile(
r'^(?P<type>[A-Za-z]+)' # lower-case only, but let this be validated by _TYPES
r'(?:\((?P<scope>[^()]+)\))?'
r'(?P<breaking>!)?'
r': '
r'(?P<description>.+)$'
)


def _main() -> None:
title = os.environ.get('PR_TITLE', '').strip()
if not title:
print('PR_TITLE environment variable is not set or empty.', file=sys.stderr)
sys.exit(1)

match = _PATTERN.match(title)
if not match:
print(
f'PR title does not follow Conventional Commits format.\n'
f'Expected: <type>[(scope)][!]: <description>\n'
f'Got: {title!r}\n'
'Read more: https://github.com/canonical/pebble/blob/master/HACKING.md#commits',
file=sys.stderr,
)
sys.exit(1)

commit_type = match.group('type')
if commit_type not in _TYPES:
print(
f'Invalid type {commit_type!r} in PR title.\n'
f'Valid types: {", ".join(sorted(_TYPES))}\n'
f'Got: {title!r}\n'
'Read more: https://github.com/canonical/pebble/blob/master/HACKING.md#commits',
file=sys.stderr,
)
sys.exit(1)

print(f'OK: {title!r}')


if __name__ == '__main__':
_main()
21 changes: 21 additions & 0 deletions .github/workflows/validate-pr-title.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
name: "Validate PR Title"
# Ensure that the PR title conforms to the Conventional Commits and our choice of types and scopes, so that library version bumps can be detected automatically

on:
pull_request:
types: [opened, edited, synchronize]

permissions: {}

jobs:
main:
name: Validate PR title
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6.0.2
with:
persist-credentials: false
- run: python3 .github/check-conventional-pr-title.py
env:
PR_TITLE: ${{ github.event.pull_request.title }}
Loading