-
-
Notifications
You must be signed in to change notification settings - Fork 129
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: validate commit message for 1 commit PRs #87
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
const core = require('@actions/core'); | ||
const github = require('@actions/github'); | ||
const parseConfig = require('./parseConfig'); | ||
const validatePrTitle = require('./validatePrTitle'); | ||
const validateCommitMessage = require('./validateCommitMessage'); | ||
|
||
module.exports = async function run() { | ||
try { | ||
|
@@ -12,7 +12,8 @@ module.exports = async function run() { | |
requireScope, | ||
wip, | ||
subjectPattern, | ||
subjectPatternError | ||
subjectPatternError, | ||
validateSingleCommit | ||
} = parseConfig(); | ||
|
||
const contextPullRequest = github.context.payload.pull_request; | ||
|
@@ -41,13 +42,36 @@ module.exports = async function run() { | |
let validationError; | ||
if (!isWip) { | ||
try { | ||
await validatePrTitle(pullRequest.title, { | ||
await validateCommitMessage(pullRequest.title, { | ||
types, | ||
scopes, | ||
requireScope, | ||
subjectPattern, | ||
subjectPatternError | ||
}); | ||
|
||
if (validateSingleCommit) { | ||
const {data: commits} = await client.pulls.listCommits({ | ||
owner, | ||
repo, | ||
pull_number: contextPullRequest.number, | ||
per_page: 2 | ||
}); | ||
|
||
if (commits.length === 1) { | ||
await validateCommitMessage( | ||
commits[0].commit.message, | ||
{ | ||
types, | ||
scopes, | ||
requireScope, | ||
subjectPattern, | ||
subjectPatternError | ||
}, | ||
'single commit message' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: I'd move the But the comment below is closely related to this as well. |
||
); | ||
} | ||
} | ||
} catch (error) { | ||
validationError = error; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
const validatePrTitle = require('./validatePrTitle'); | ||
const validateCommitMessage = require('./validateCommitMessage'); | ||
|
||
it('allows valid PR titles that use the default types', async () => { | ||
const inputs = [ | ||
|
@@ -10,84 +10,86 @@ it('allows valid PR titles that use the default types', async () => { | |
]; | ||
|
||
for (let index = 0; index < inputs.length; index++) { | ||
await validatePrTitle(inputs[index]); | ||
await validateCommitMessage(inputs[index]); | ||
} | ||
}); | ||
|
||
it('throws for PR titles without a type', async () => { | ||
await expect(validatePrTitle('Fix bug')).rejects.toThrow( | ||
await expect(validateCommitMessage('Fix bug')).rejects.toThrow( | ||
'No release type found in pull request title "Fix bug".' | ||
); | ||
}); | ||
|
||
it('throws for PR titles with only a type', async () => { | ||
await expect(validatePrTitle('fix:')).rejects.toThrow( | ||
await expect(validateCommitMessage('fix:')).rejects.toThrow( | ||
'No release type found in pull request title "fix:".' | ||
); | ||
}); | ||
|
||
it('throws for PR titles without a subject', async () => { | ||
await expect(validatePrTitle('fix: ')).rejects.toThrow( | ||
await expect(validateCommitMessage('fix: ')).rejects.toThrow( | ||
'No subject found in pull request title "fix: ".' | ||
); | ||
}); | ||
|
||
it('throws for PR titles with an unknown type', async () => { | ||
await expect(validatePrTitle('foo: Bar')).rejects.toThrow( | ||
await expect(validateCommitMessage('foo: Bar')).rejects.toThrow( | ||
'Unknown release type "foo" found in pull request title "foo: Bar".' | ||
); | ||
}); | ||
|
||
describe('defined scopes', () => { | ||
it('allows a missing scope by default', async () => { | ||
await validatePrTitle('fix: Bar'); | ||
await validateCommitMessage('fix: Bar'); | ||
}); | ||
|
||
it('allows all scopes by default', async () => { | ||
await validatePrTitle('fix(core): Bar'); | ||
await validateCommitMessage('fix(core): Bar'); | ||
}); | ||
|
||
it('allows a missing scope when custom scopes are defined', async () => { | ||
await validatePrTitle('fix: Bar', {scopes: ['foo']}); | ||
await validateCommitMessage('fix: Bar', {scopes: ['foo']}); | ||
}); | ||
|
||
it('allows a matching scope', async () => { | ||
await validatePrTitle('fix(core): Bar', {scopes: ['core']}); | ||
await validateCommitMessage('fix(core): Bar', {scopes: ['core']}); | ||
}); | ||
|
||
it('allows multiple matching scopes', async () => { | ||
await validatePrTitle('fix(core,e2e): Bar', { | ||
await validateCommitMessage('fix(core,e2e): Bar', { | ||
scopes: ['core', 'e2e', 'web'] | ||
}); | ||
}); | ||
|
||
it('throws when an unknown scope is detected within multiple scopes', async () => { | ||
await expect( | ||
validatePrTitle('fix(core,e2e,foo,bar): Bar', {scopes: ['foo', 'core']}) | ||
validateCommitMessage('fix(core,e2e,foo,bar): Bar', { | ||
scopes: ['foo', 'core'] | ||
}) | ||
).rejects.toThrow( | ||
'Unknown scopes "e2e,bar" found in pull request title "fix(core,e2e,foo,bar): Bar". Use one of the available scopes: foo, core.' | ||
); | ||
}); | ||
|
||
it('throws when an unknown scope is detected', async () => { | ||
await expect( | ||
validatePrTitle('fix(core): Bar', {scopes: ['foo']}) | ||
validateCommitMessage('fix(core): Bar', {scopes: ['foo']}) | ||
).rejects.toThrow( | ||
'Unknown scope "core" found in pull request title "fix(core): Bar". Use one of the available scopes: foo.' | ||
); | ||
}); | ||
|
||
describe('require scope', () => { | ||
it('passes when a scope is provided', async () => { | ||
await validatePrTitle('fix(core): Bar', { | ||
await validateCommitMessage('fix(core): Bar', { | ||
scopes: ['core'], | ||
requireScope: true | ||
}); | ||
}); | ||
|
||
it('throws when a scope is missing', async () => { | ||
await expect( | ||
validatePrTitle('fix: Bar', { | ||
validateCommitMessage('fix: Bar', { | ||
scopes: ['foo', 'bar'], | ||
requireScope: true | ||
}) | ||
|
@@ -104,13 +106,13 @@ describe('custom types', () => { | |
const types = ['foo', 'bar', 'baz']; | ||
|
||
for (let index = 0; index < inputs.length; index++) { | ||
await validatePrTitle(inputs[index], {types}); | ||
await validateCommitMessage(inputs[index], {types}); | ||
} | ||
}); | ||
|
||
it('throws for PR titles with an unknown type', async () => { | ||
await expect( | ||
validatePrTitle('fix: Foobar', {types: ['foo', 'bar']}) | ||
validateCommitMessage('fix: Foobar', {types: ['foo', 'bar']}) | ||
).rejects.toThrow( | ||
'Unknown release type "fix" found in pull request title "fix: Foobar".' | ||
); | ||
|
@@ -119,11 +121,11 @@ describe('custom types', () => { | |
|
||
describe('description validation', () => { | ||
it('does not validate the description by default', async () => { | ||
await validatePrTitle('fix: sK!"§4123'); | ||
await validateCommitMessage('fix: sK!"§4123'); | ||
}); | ||
|
||
it('can pass the validation when `subjectPatternError` is configured', async () => { | ||
await validatePrTitle('fix: foobar', { | ||
await validateCommitMessage('fix: foobar', { | ||
subjectPattern: '^(?![A-Z]).+$', | ||
subjectPatternError: | ||
'The subject found in the pull request title cannot start with an uppercase character.' | ||
|
@@ -134,7 +136,7 @@ describe('description validation', () => { | |
const customError = | ||
'The subject found in the pull request title cannot start with an uppercase character.'; | ||
await expect( | ||
validatePrTitle('fix: Foobar', { | ||
validateCommitMessage('fix: Foobar', { | ||
subjectPattern: '^(?![A-Z]).+$', | ||
subjectPatternError: customError | ||
}) | ||
|
@@ -143,7 +145,7 @@ describe('description validation', () => { | |
|
||
it('interpolates variables into `subjectPatternError`', async () => { | ||
await expect( | ||
validatePrTitle('fix: Foobar', { | ||
validateCommitMessage('fix: Foobar', { | ||
subjectPattern: '^(?![A-Z]).+$', | ||
subjectPatternError: | ||
'The subject "{subject}" found in the pull request title "{title}" cannot start with an uppercase character.' | ||
|
@@ -155,7 +157,7 @@ describe('description validation', () => { | |
|
||
it('throws for invalid subjects', async () => { | ||
await expect( | ||
validatePrTitle('fix: Foobar', { | ||
validateCommitMessage('fix: Foobar', { | ||
subjectPattern: '^(?![A-Z]).+$' | ||
}) | ||
).rejects.toThrow( | ||
|
@@ -165,7 +167,7 @@ describe('description validation', () => { | |
|
||
it('throws for only partial matches', async () => { | ||
await expect( | ||
validatePrTitle('fix: Foobar', { | ||
validateCommitMessage('fix: Foobar', { | ||
subjectPattern: 'Foo' | ||
}) | ||
).rejects.toThrow( | ||
|
@@ -174,8 +176,16 @@ describe('description validation', () => { | |
}); | ||
|
||
it('accepts valid subjects', async () => { | ||
await validatePrTitle('fix: foobar', { | ||
await validateCommitMessage('fix: foobar', { | ||
subjectPattern: '^(?![A-Z]).+$' | ||
}); | ||
}); | ||
}); | ||
|
||
it('throws an appropriate error message for single commit message errors', async () => { | ||
await expect( | ||
validateCommitMessage('Fix bug', undefined, 'single commit message') | ||
).rejects.toThrow( | ||
'No release type found in single commit message "Fix bug".' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Currently the full error message looks like this:
I'm not sure this helps the PR author to address the issue since we say "Add a prefix". Maybe we should clarify that we're working around an issue of Github here, possibly referencing https://github.community/t/how-to-change-the-default-squash-merge-commit-message/1155 and telling the user to add another commit to avoid the issue? That's also what semantic-pull-requests does. Since the error message becomes a bit different here, maybe it makes sense to pass in an enum value to E.g. validateMessage('foo', {type: 'title'})
validateMessage('foo', {type: 'commit'}) I've also renamed to function again here, since it can be either a commit message or a PR title. What do you think? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't want to complicate the logic in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's a great idea! |
||
); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for including good docs and testing the feature!