From 085c42d22581ff17a516218d50ffef27c78169ca Mon Sep 17 00:00:00 2001 From: Don Denton Date: Tue, 31 Jan 2023 23:35:17 -0500 Subject: [PATCH] [FEATURE] Add `failOnMatch` option This option, when `"true"` will fail the checker when the given pattern matches the commit message. This allows for a simpler kind check for negative conditions such as "The commit message does not start with 'fixup'." I rewrote some test cases here so that they were grouped into input types. This allowed me to use the same inputs for both the default settings and also the `failOnMatch` setting. Each of the "default" tests are exactly the same as they were before, with the "via `failOnMatch`" tests being the tests that were actually added. --- README.md | 9 ++ __tests__/commit-message-checker.test.ts | 108 +++++++++++++++++++---- action.yml | 6 +- dist/index.js | 12 ++- src/commit-message-checker.ts | 15 +++- src/input-helper.ts | 4 + 6 files changed, 127 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index e167fab..836447d 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,15 @@ jobs: excludeTitle: 'true' # optional: this excludes the title of a pull request checkAllCommitMessages: 'true' # optional: this checks all commits associated with a pull request accessToken: ${{ secrets.GITHUB_TOKEN }} # github access token is only required if checkAllCommitMessages is true + - name: Squash/Fixup Check + uses: gsactions/commit-message-checker@v2 + with: + pattern: '^(fixup|squash)' + failOnMatch: 'true' # optional: this will fail the test if the pattern matches a commit message + error: Don't forget to squash/fixup your commits before merging + excludeDescription: 'true' + checkAllCommitMessages: 'true' + accessToken: ${{ secrets.GITHUB_TOKEN }} - name: Check for Resolves / Fixes uses: gsactions/commit-message-checker@v2 with: diff --git a/__tests__/commit-message-checker.test.ts b/__tests__/commit-message-checker.test.ts index b37d64a..837bafe 100644 --- a/__tests__/commit-message-checker.test.ts +++ b/__tests__/commit-message-checker.test.ts @@ -77,51 +77,123 @@ describe('commit-message-checker tests', () => { ).rejects.toThrow('MESSAGES not defined.') }) - it('check fails single message', async () => { + describe('single message, without a match', () => { const checkerArguments: ICheckerArguments = { pattern: 'some-pattern', flags: '', error: 'some-error', messages: ['some-message'] } - await expect( - commitMessageChecker.checkCommitMessages(checkerArguments) - ).rejects.toThrow('some-error') + + it('fails by default', async () => { + await expect( + commitMessageChecker.checkCommitMessages(checkerArguments) + ).rejects.toThrow('some-error') + }) + + it('succeeds via `failOnMatch`', async () => { + await expect( + commitMessageChecker.checkCommitMessages({ + ...checkerArguments, + failOnMatch: true + }) + ).resolves.toBeUndefined() + }) }) - it('check fails multiple messages', async () => { + describe('single message, with a match', () => { + const checkerArguments: ICheckerArguments = { + pattern: '.*', + flags: '', + error: 'some-error', + messages: ['some-message'] + } + + it('succeeds by default', async () => { + await expect( + commitMessageChecker.checkCommitMessages(checkerArguments) + ).resolves.toBeUndefined() + }) + + it('fails via `failOnMatch`', async () => { + await expect( + commitMessageChecker.checkCommitMessages({ + ...checkerArguments, + failOnMatch: true + }) + ).rejects.toThrow('some-error') + }) + }) + + describe('multiple messages, with a single match', () => { const checkerArguments: ICheckerArguments = { pattern: 'some-pattern', flags: '', error: 'some-error', messages: ['some-message', 'some-pattern'] } - await expect( - commitMessageChecker.checkCommitMessages(checkerArguments) - ).rejects.toThrow('some-error') + + it('fails by default', async () => { + await expect( + commitMessageChecker.checkCommitMessages(checkerArguments) + ).rejects.toThrow('some-error') + }) + + it('fails via `failOnMatch`', async () => { + await expect( + commitMessageChecker.checkCommitMessages({ + ...checkerArguments, + failOnMatch: true + }) + ).rejects.toThrow('some-error') + }) }) - it('check succeeds on single message', async () => { + describe('multiple messages, without any match', () => { const checkerArguments: ICheckerArguments = { - pattern: '.*', + pattern: 'some-pattern', flags: '', error: 'some-error', - messages: ['some-message'] + messages: ['some-message', 'other-message'] } - await expect( - commitMessageChecker.checkCommitMessages(checkerArguments) - ).resolves.toBeUndefined() + + it('fails by default', async () => { + await expect( + commitMessageChecker.checkCommitMessages(checkerArguments) + ).rejects.toThrow('some-error') + }) + + it('succeeds via `failOnMatch`', async () => { + await expect( + commitMessageChecker.checkCommitMessages({ + ...checkerArguments, + failOnMatch: true + }) + ).resolves.toBeUndefined() + }) }) - it('check succeeds on multiple messages', async () => { + describe('multiple messages, all matching', () => { const checkerArguments: ICheckerArguments = { pattern: '.*', flags: '', error: 'some-error', messages: ['some-message', 'other-message'] } - await expect( - commitMessageChecker.checkCommitMessages(checkerArguments) - ).resolves.toBeUndefined() + + it('succeeds by default', async () => { + await expect( + commitMessageChecker.checkCommitMessages(checkerArguments) + ).resolves.toBeUndefined() + }) + + it('fails via `failOnMatch`', async () => { + await expect( + commitMessageChecker.checkCommitMessages({ + ...checkerArguments, + failOnMatch: true + }) + ).rejects.toThrow('some-error') + }) }) }) diff --git a/action.yml b/action.yml index cbce124..562ee1a 100644 --- a/action.yml +++ b/action.yml @@ -10,12 +10,16 @@ inputs: required: false default: 'gm' error: - description: 'A error message which will be returned in case of an error.' + description: 'An error message which will be returned in case of an error.' required: true excludeTitle: description: 'Setting this input to true will exclude the Pull Request title from the check.' required: false default: 'false' + failOnMatch: + description: 'Setting this input to true will reverse the logic so that a positive match will fail the check.' + required: false + default: 'false' excludeDescription: description: 'Setting this input to true will exclude the Pull Request description from the check.' required: false diff --git a/dist/index.js b/dist/index.js index e59aa2b..cc62aa2 100644 --- a/dist/index.js +++ b/dist/index.js @@ -89,9 +89,9 @@ function checkCommitMessages(args) { } // Check messages let result = true; - core.info(`Checking commit messages against "${args.pattern}"...`); + core.info(`Checking commit messages ${args.failOnMatch ? 'do not ' : ''}match "${args.pattern}"...`); for (const message of args.messages) { - if (checkMessage(message, args.pattern, args.flags)) { + if (checkMessage(message, args.pattern, args.flags, args.failOnMatch)) { core.info(`- OK: "${message}"`); } else { @@ -113,9 +113,10 @@ exports.checkCommitMessages = checkCommitMessages; * @param pattern regex pattern for the check. * @returns boolean */ -function checkMessage(message, pattern, flags) { +function checkMessage(message, pattern, flags, failOnMatch) { const regex = new RegExp(pattern, flags); - return regex.test(message); + const result = regex.test(message); + return failOnMatch ? !result : result; } @@ -200,6 +201,9 @@ function getInputs() { // Get error message result.error = core.getInput('error', { required: true }); core.debug(`error: ${result.error}`); + // Get failOnMatch + result.failOnMatch = core.getInput('failOnMatch') === 'true'; + core.debug(`failOnMatch: ${result.failOnMatch}`); // Get excludeTitle const excludeTitleStr = core.getInput('excludeTitle'); core.debug(`excludeTitle: ${excludeTitleStr}`); diff --git a/src/commit-message-checker.ts b/src/commit-message-checker.ts index 2b75ecc..3dd4d7f 100644 --- a/src/commit-message-checker.ts +++ b/src/commit-message-checker.ts @@ -26,6 +26,7 @@ import * as core from '@actions/core' */ export interface ICheckerArguments { pattern: string + failOnMatch?: boolean flags: string error: string messages: string[] @@ -66,10 +67,14 @@ export async function checkCommitMessages( // Check messages let result = true - core.info(`Checking commit messages against "${args.pattern}"...`) + core.info( + `Checking commit messages ${args.failOnMatch ? 'do not ' : ''}match "${ + args.pattern + }"...` + ) for (const message of args.messages) { - if (checkMessage(message, args.pattern, args.flags)) { + if (checkMessage(message, args.pattern, args.flags, args.failOnMatch)) { core.info(`- OK: "${message}"`) } else { core.info(`- failed: "${message}"`) @@ -93,8 +98,10 @@ export async function checkCommitMessages( function checkMessage( message: string, pattern: string, - flags: string + flags: string, + failOnMatch?: boolean ): boolean { const regex = new RegExp(pattern, flags) - return regex.test(message) + const result = regex.test(message) + return failOnMatch ? !result : result } diff --git a/src/input-helper.ts b/src/input-helper.ts index eff4be3..5ec3b3e 100644 --- a/src/input-helper.ts +++ b/src/input-helper.ts @@ -52,6 +52,10 @@ export async function getInputs(): Promise { result.error = core.getInput('error', {required: true}) core.debug(`error: ${result.error}`) + // Get failOnMatch + result.failOnMatch = core.getInput('failOnMatch') === 'true' + core.debug(`failOnMatch: ${result.failOnMatch}`) + // Get excludeTitle const excludeTitleStr = core.getInput('excludeTitle') core.debug(`excludeTitle: ${excludeTitleStr}`)