Skip to content
Merged
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
36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,42 @@ The commit will also be flagged if the commit message does not include a Jira
ID (unless marked with NO_JIRA or a Copilot Autofix co-author line), or if the
size of new or modified files exceeds a threshold.

## Conventional Commits

Conventional Commits validation is optional. Add a `.conventional-commits` file
at the repository root to enable it. The supported header format is:

```text
<type>: <subject>
<type>(<scope>): <subject>
```

Examples:

```text
fix: handle an empty search response
feat(PLA-0001): add structure filtering
break(NO_JIRA): remove the legacy search endpoint
BREAKING CHANGE(api): drop support for legacy endpoints
```

Or with breaking changes described in the footer:

```text
feat(api): migrate to v2 response schema

BREAKING CHANGE: remove statusCode field from responses
```

Supported types are `feat`, `fix`, `refactor`, `build`, `chore`, `ci`, `docs`,
`perf`, `revert`, `style`, and `test`. For major (breaking) releases, both the
standard `BREAKING CHANGE` (in header or footer) and CCDC's shorthand `break`
(configured via `release.config.cjs`) are accepted. Optional scopes like
`feat(<scope>):` and `break(<scope>):` are supported.
A branch may contain more than one type. Releases should choose the highest
required version bump, so `break` / `BREAKING CHANGE` takes precedence over
`feat`, which takes precedence over `fix`.


# GitHub Actions

Expand Down
47 changes: 37 additions & 10 deletions main/githooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -960,11 +960,6 @@ def check_commit_msg(message, files, repo):
# Not checking for JIRA or large file in commit message generated by github
return 0

if re.match(r'^ccdc-opensource/', repo):
# Do not check for JIRA in opensource repo as we don't want to require external contributors to do this
return 0


# Check for Conventional Commits compliance.
# Opt-in per repo: commit an empty marker file named
# `.conventional-commits` at the repo root.
Expand All @@ -977,6 +972,8 @@ def check_commit_msg(message, files, repo):
return 1

if (
not re.match(r'^ccdc-opensource/', repo)
and
NO_JIRA_MARKER not in message
and copilot_autofix_coauthor_pattern.search(message) is None
and jira_id_pattern.search(message) is None
Expand Down Expand Up @@ -1012,11 +1009,14 @@ def _conventional_commits_enabled():


def conventional_commit_present(message):
'''Return True if the commit message follows the Angular Conventional Commits standard.'''
# Angular Conventional Commits header: type(scope?): subject
# Allowed types from @commitlint/config-angular:
# build, chore, ci, docs, feat, fix, perf, refactor, revert, style, test
# Note: Angular does not use the `!` breaking-change marker in the header;
'''Return True if the commit message follows Conventional Commits / Angular standard.

Supported types:
- Standard types: feat, fix, refactor, build, chore, ci, docs, perf, revert, style, test
- Breaking changes: 'BREAKING CHANGE' (standard) and 'break' (CCDC custom shorthand in release.config.cjs)
- Both support an optional (scope) and breaking changes can also appear in the footer.
- Note: Angular commit guidelines do not use the '!' breaking-change marker in the header.
'''
pattern = re.compile(
r'^(BREAKING CHANGE|break|feat|fix|refactor|build|chore|ci|docs|perf|revert|style|test)' # type
r'(\([\w\-\.\/]+\))?' # optional scope
Expand Down Expand Up @@ -1071,6 +1071,33 @@ def _test(input, is_good=True):
_test('I forgot to add the jira marker!', False)
_test('Close but no cigar abc-1234', False)

@patch('githooks._conventional_commits_enabled', return_value=True)
def test_opensource_repo_still_checks_conventional_commit(self, _enabled):
self.assertEqual(0, check_commit_msg('feat: valid change', [], 'ccdc-opensource/example'))
self.assertEqual(1, check_commit_msg('invalid: invalid change', [], 'ccdc-opensource/example'))


class TestConventionalCommitPresent(unittest.TestCase):
def test_supported_types(self):
for commit_type in (
'BREAKING CHANGE', 'break', 'feat', 'fix', 'refactor', 'build',
'chore', 'ci', 'docs', 'perf', 'revert', 'style', 'test'
):
with self.subTest(commit_type=commit_type):
self.assertTrue(conventional_commit_present(f'{commit_type}: subject'))

def test_scope_and_multiline_description(self):
self.assertTrue(conventional_commit_present('feat(api): subject\n\nMore detail'))
self.assertTrue(conventional_commit_present('break(api): subject\n\nMore detail'))
self.assertTrue(conventional_commit_present('BREAKING CHANGE(api): subject\n\nMore detail'))
self.assertTrue(conventional_commit_present(
'feat(contracts): update api endpoints\n\nBREAKING CHANGE: remove statusCode field from responses'
))

def test_unsupported_type(self):
self.assertFalse(conventional_commit_present('invalid(PLA-3474): test'))
self.assertFalse(conventional_commit_present('breaking-change: test'))


def run_licence_check(files):
'''Check or fix complete CCDC licence headers.
Expand Down
Loading