diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c280c5f..5f7f347 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -43,6 +43,8 @@ The empty lines between the different parts are required. | **perf** | Performance optimizations | patch | | **refactor** | Changes to the code structure without fixing bugs or adding features | patch | | **chore** | Changes to the project setup and tools, dependency bumps, house-keeping | patch | +| **revert** | reverts a previous commit | patch | + || | **docs** | Changes to the documentation | none | | **style** | Cleanup & lint rule fixes. Note that often it's better to just amend the previous commit if it introduced lint errors | none | diff --git a/lib/git/commits.js b/lib/git/commits.js index 8b62799..1ea9e86 100644 --- a/lib/git/commits.js +++ b/lib/git/commits.js @@ -50,9 +50,17 @@ function parseCommit(commit) { const parentSha = meta.shift() || null; const data = commitParser.sync(message, { issuePrefixes: ['#', 'https?://[\\w\\.-/]*[-/]+'], + revertPattern: /^revert:?\s"([\s\S]*)"/i, + revertCorrespondence: ['message'], }); const prMatch = message.match(PR_MERGE_PATTERN); + if (data.revert) { + data.type = 'pr'; + data.subject = data.revert.message; + data.header = data.header.replace(/^Revert /, 'revert: '); + } + if (prMatch) { const prId = prMatch[1]; data.type = 'pr'; diff --git a/lib/steps/release-info.js b/lib/steps/release-info.js index 1d36a30..8fcbb55 100644 --- a/lib/steps/release-info.js +++ b/lib/steps/release-info.js @@ -32,29 +32,25 @@ 'use strict'; -const util = require('util'); - -const INVALID_COMMITS_MESSAGE = [ - 'This repository uses AngularJS Git Commit Message Conventions[1]', - 'to automatically determine the semver implications of changes', - 'and to generate changelogs for releases.', - '', - 'The following commits could not be parsed:', - '', - '<>', - '', - 'Most likely they are missing one of the valid type prefixes', - '(feat, fix, docs, style, refactor, test, chore).', - '', - 'You can reword commit messages using rebase[2]:', - '', - '~~~bash', - 'git rebase -i <>', - '~~~', - '', - '[1] Docs on the conventions: http://gr.pn/1OWll98', - '[2] https://git-scm.com/docs/git-rebase', -].join('\n'); +const INVALID_COMMITS_MESSAGE = `This repository uses AngularJS Git Commit Message Conventions[1] +to automatically determine the semver implications of changes +and to generate changelogs for releases. + +The following commits could not be parsed: + +<> + +Most likely they are missing one of the valid type prefixes +(feat, fix, docs, style, refactor, test, chore). + +You can reword commit messages using rebase[2]: + +~~~bash +git rebase -i <> +~~~ + +[1] Docs on the conventions: http://gr.pn/1OWll98 +[2] https://git-scm.com/docs/git-rebase`; const RELEASE_TYPES = ['none', 'patch', 'minor', 'major']; @@ -62,21 +58,25 @@ function formatInvalidCommit(commit) { return `* [${commit.sha.slice(0, 7)}] ${commit.header}`; } -function InvalidCommitsError(commits) { - Error.call(this); - Error.captureStackTrace(this, InvalidCommitsError); - const commitsBlock = commits.map(formatInvalidCommit).join('\n'); - this.message = INVALID_COMMITS_MESSAGE.replace( - '<>', - commitsBlock - ).replace('<>', commits[0].parentSha || '--root'); - this.code = 'EINVALIDCOMMITS'; - this.commits = commits; +class InvalidCommitsError extends Error { + constructor(commits) { + super(); + // this.captureStackTrace(this, InvalidCommitsError); + const commitsBlock = commits.map(formatInvalidCommit).join('\n'); + this.message = INVALID_COMMITS_MESSAGE.replace( + '<>', + commitsBlock + ).replace('<>', commits[0].parentSha || '--root'); + this.code = 'EINVALIDCOMMITS'; + this.commits = commits; + } } -util.inherits(InvalidCommitsError, Error); - -function isBreaking(note) { +/** + * @param {{title: string}} note + * @return {boolean} + */ +function hasBreakingChange(note) { return note.title.startsWith('BREAKING CHANGE'); } @@ -87,7 +87,7 @@ function determineReleaseInfo(commits, acceptInvalidCommits) { for (let idx = 0; idx < commits.length; ++idx) { const commit = commits[idx]; - if ((commit.notes || []).some(isBreaking)) { + if ((commit.notes || []).some(hasBreakingChange)) { releaseType = 3; continue; } @@ -97,6 +97,7 @@ function determineReleaseInfo(commits, acceptInvalidCommits) { case 'fix': case 'refactor': case 'perf': + case 'revert': releaseType = Math.max(releaseType, 1); break; diff --git a/test/fixtures/breaking-change-commit b/test/fixtures/breaking-change-commit new file mode 100755 index 0000000..bbc785e --- /dev/null +++ b/test/fixtures/breaking-change-commit @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -e +git init + +echo "console.log('do stuff');" > index.js +git add index.js +git commit -m "fix: Do stuff + +something + +BREAKING CHANGE: +Ups something broke :D +" diff --git a/test/fixtures/merge-commit b/test/fixtures/merge-commit new file mode 100755 index 0000000..df41431 --- /dev/null +++ b/test/fixtures/merge-commit @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +set -e +git init + +echo "console.log('do stuff');" > merge.js +git add merge.js +git commit -m 'Merge pull request #119 from theowner/some/branch' diff --git a/test/fixtures/revert-commit b/test/fixtures/revert-commit new file mode 100755 index 0000000..30a0f26 --- /dev/null +++ b/test/fixtures/revert-commit @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +set -e +git init + +echo "console.log('do stuff');" > revert-multi-line.js +git add revert-multi-line.js +git commit -m 'Revert "throw an error if a callback is passed" + +This reverts commit 9bb4d6c.' + +echo "console.log('do stuff');" > revert.js +git add revert.js +git commit -m 'Revert "Fix: remove peek baseURL"' + +echo "console.log('do stuff');" > index.js +git add index.js +git commit -m 'revert: this' diff --git a/test/fixtures/silent-commits b/test/fixtures/silent-commits index 9ab7297..40202b9 100755 --- a/test/fixtures/silent-commits +++ b/test/fixtures/silent-commits @@ -4,10 +4,6 @@ git init git checkout -b silent-commits -echo "console.log('more');" > doc.js -git add doc.js -git commit -m "doc: Adding doc commit" - echo "console.log('more');" > docs.js git add docs.js git commit -m "docs: Adding docs commit" diff --git a/test/steps/release-info.test.js b/test/steps/release-info.test.js index 066e828..2e7d960 100644 --- a/test/steps/release-info.test.js +++ b/test/steps/release-info.test.js @@ -96,39 +96,52 @@ describe('determineReleaseInfo', () => { }); }); - describe('with "chore", "fix", "refactor" & "perf" commit messages', () => { - const dirname = withFixture('patch-commits'); - let commits = []; - before('load commits', async () => { - commits = await getCommits(dirname); - }); + const testcases = [ + { + desc: 'with "chore", "fix", "refactor" & "perf" commit type', + fixture: 'patch-commits', + expected: 'patch', + }, + { + desc: 'with "feat" commit type', + fixture: 'minor-commits', + expected: 'minor', + }, + { + desc: 'with "doc", "docs", "style", "test" & "pr" commit type', + fixture: 'silent-commits', + expected: 'none', + }, + { + desc: 'with "BREAKING CHANGE" commit footer', + fixture: 'breaking-change-commit', + expected: 'major', + }, + { + desc: 'with github "Revert" commit message or "revert" commit type', + fixture: 'revert-commit', + expected: 'patch', + }, + { + desc: 'with github "Merge" commit message', + fixture: 'merge-commit', + expected: 'none', + }, + ]; - it('returns "patch" version info', () => { - assert.equal('patch', determineReleaseInfo(commits, true)); - }); - }); - - describe('with "feat" commit messages', () => { - const dirname = withFixture('minor-commits'); - let commits = []; - before('load commits', async () => { - commits = await getCommits(dirname); - }); + describe('test cases', () => { + for (const testcase of testcases) { + describe(testcase.desc, () => { + const dirname = withFixture(testcase.fixture); + let commits = []; + before('load commits', async () => { + commits = await getCommits(dirname); + }); - it('returns "minor" version info', () => { - assert.equal('minor', determineReleaseInfo(commits, true)); - }); - }); - - describe('with "doc", "docs", "style", "test" & "pr" commit messages', () => { - const dirname = withFixture('silent-commits'); - let commits = []; - before('load commits', async () => { - commits = await getCommits(dirname); - }); - - it('returns "none" version info', () => { - assert.equal('none', determineReleaseInfo(commits, true)); - }); + it(`returns "${testcase.expected}" version info`, () => { + assert.equal(testcase.expected, determineReleaseInfo(commits)); + }); + }); + } }); });