diff --git a/README.md b/README.md index 40aff1a5..b1c406e7 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,66 @@ jobs: Alternatively, you can run on other event types such as `on: [push]`. In that case the action will lint the push event's commit(s) instead of linting commits from a pull request. You can also combine `push` and `pull_request` together in the same workflow. +### Using with GitHub Merge Queues + +GitHub's [merge queue](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/configuring-pull-request-merges/managing-a-merge-queue) is a feature that allows you to queue pull requests for merging once they meet certain criteria. When using merge queues, you need to ensure that your workflows are set up to handle the merge_group event, which is triggered when pull requests are added to the merge queue. + +#### Workflow Configuration + +To use the commitlint-github-action with merge queues, you need to set up a workflow that listens to the merge_group event. Here's an example of how to configure your workflow: + +```yaml +name: Lint Commit Messages in Merge Queue + +on: + merge_group: + types: + - checks_requested + +jobs: + commitlint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + ref: ${{ github.sha }} + + - uses: wagoid/commitlint-github-action@v6 +``` + +#### Important Note: + +To ensure that the merge_group event triggers correctly, you need to have **at least one workflow that responds to the pull_request event** with a job named the same as the one in your merge_group workflow (**commitlint** in this example). This is necessary because the merge queue relies on the existence of status checks from the pull request context. + +Here's a minimal pull_request workflow to satisfy this requirement: + +```yaml +name: Placeholder Workflow for Merge Queue + +on: + pull_request: + +jobs: + commitlint: + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@v3 +``` + +This workflow can also be a meaningful one that checks out the commits in your PR and runs other checks, but it must have a job named **commitlint**. + +### Enabling Merge Queues in Your Repository + +Before you can use merge queues, you need to enable the feature in your repository settings: + +- Go to your repository's Settings > Branches. +- Under Branch protection rules, edit the rule for your target branch (e.g. master). +- Enable Require merge queue. +- Specify your new job (e.g. commitlint) and any other required status checks, that must pass before merging. + +For more information on configuring merge queues, refer to the [GitHub documentation on managing a merge queue](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/configuring-pull-request-merges/managing-a-merge-queue). + ## Inputs You can supply these inputs to the `wagoid/commitlint-github-action@v6` step. diff --git a/src/action.mjs b/src/action.mjs index 49d72856..bb979f57 100644 --- a/src/action.mjs +++ b/src/action.mjs @@ -7,6 +7,7 @@ import { format } from '@commitlint/format' import load from '@commitlint/load' import generateOutputs from './generateOutputs.mjs' +const mergeGroupEvent = 'merge_group' const pullRequestEvent = 'pull_request' const pullRequestTargetEvent = 'pull_request_target' const pullRequestEvents = [pullRequestEvent, pullRequestTargetEvent] @@ -65,7 +66,21 @@ const getPullRequestEventCommits = async () => { })) } +const getMergeGroupEventCommits = async () => { + const { merge_group } = eventContext.payload + + return [ + { + message: merge_group.head_commit.message, + hash: merge_group.head_sha, + }, + ] +} + const getEventCommits = async () => { + if (GITHUB_EVENT_NAME === mergeGroupEvent) { + return getMergeGroupEventCommits() + } if (pullRequestEvents.includes(GITHUB_EVENT_NAME)) { return getPullRequestEventCommits() } diff --git a/src/action.test.mjs b/src/action.test.mjs index 7b91e48c..1a57f402 100644 --- a/src/action.test.mjs +++ b/src/action.test.mjs @@ -5,8 +5,10 @@ import { jest, describe, it } from '@jest/globals' import * as td from 'testdouble' import { buildResponseCommit, + createMergeGroupEventPayload, createPullRequestEventPayload, createPushEventPayload, + updateMergeGroupEnvVars, updatePullRequestEnvVars, updatePushEnvVars, } from './testUtils.mjs' @@ -952,4 +954,56 @@ describe('Commit Linter action', () => { td.verify(mockCore.setFailed(contains(incorrectCommit.message))) }) }) + + describe('when handling merge_group event', () => { + beforeEach(async () => { + cwd = await git.bootstrap('fixtures/conventional', process.cwd()) + td.when(mockCore.getInput('configFile')).thenReturn( + './commitlint.config.mjs', + ) + + td.replace(process, 'cwd', () => cwd) + td.replace(console, 'log') + }) + + it('should lint the squashed commit message successfully', async () => { + const mergeGroupData = { + head_sha: 'merge-group-head-sha', + head_commit: { + id: 'merge-group-head-sha', + message: 'feat: add new feature\n\nThis is a detailed description.', + tree_id: 'tree-sha', + }, + } + + await createMergeGroupEventPayload(cwd, mergeGroupData) + updateMergeGroupEnvVars(cwd) + + await runAction() + + td.verify(mockCore.setFailed(), { times: 0, ignoreExtraArgs: true }) + td.verify(console.log('Lint free! 🎉')) + }) + + it('should fail if the squashed commit message has linting errors', async () => { + const mergeGroupData = { + head_sha: 'merge-group-head-sha', + head_commit: { + id: 'merge-group-head-sha', + message: 'bad commit message', + tree_id: 'tree-sha', + }, + } + + await createMergeGroupEventPayload(cwd, mergeGroupData) + updateMergeGroupEnvVars(cwd) + + await runAction() + + td.verify( + mockCore.setFailed(contains('You have commit messages with errors')), + ) + td.verify(mockCore.setFailed(contains('bad commit message'))) + }) + }) }) diff --git a/src/testUtils.mjs b/src/testUtils.mjs index 604bf2a0..07426bf6 100644 --- a/src/testUtils.mjs +++ b/src/testUtils.mjs @@ -77,3 +77,32 @@ export const buildResponseCommit = (sha, message) => ({ message, }, }) + +export const createMergeGroupEventPayload = async (cwd, mergeGroupData) => { + const payload = { + action: 'checks_requested', + merge_group: mergeGroupData, + repository: { + owner: { + login: 'wagoid', + }, + name: 'commitlint-github-action', + }, + } + + const eventPath = path.join(cwd, 'mergeGroupEventPayload.json') + + updateEnvVars({ + GITHUB_EVENT_PATH: eventPath, + GITHUB_EVENT_NAME: 'merge_group', + GITHUB_REPOSITORY: 'wagoid/commitlint-github-action', + }) + await writeFile(eventPath, JSON.stringify(payload), 'utf8') +} + +export const updateMergeGroupEnvVars = (cwd) => { + updateEnvVars({ + GITHUB_WORKSPACE: cwd, + GITHUB_EVENT_NAME: 'merge_group', + }) +}