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
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,33 @@ The most important prefixes you should have in mind are:
* `feat!:`, or `fix!:`, `refactor!:`, etc., which represent a breaking change
(indicated by the `!`) and will result in a SemVer major.

### What if my PR contains multiple fixes or features?

Release Please allows you to represent multiple changes in a single commit,
using footers:

```
feat: adds v4 UUID to crypto

This adds support for v4 UUIDs to the library.

fix(utils): unicode no longer throws exception
PiperOrigin-RevId: 345559154
BREAKING-CHANGE: encode method no longer throws.
Source-Link: googleapis/googleapis@5e0dcb2

feat(utils): update encode to support unicode
PiperOrigin-RevId: 345559182
Source-Link: googleapis/googleapis@e5eef86
```

The above commit message will contain:

1. an entry for the **"adds v4 UUID to crypto"** feature.
2. an entry for the fix **"unicode no longer throws exception"**, along with a note
that it's a breaking change.
3. an entry for the feature **"update encode to support unicode"**.

## How do I change the version number?

When a commit to the main branch has `Release-As: x.x.x` in the **commit body**, Release Please will open a new pull request for the specified version.
Expand Down
57 changes: 57 additions & 0 deletions __snapshots__/conventional-commits.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,60 @@ exports['ConventionalCommits generateChangelogEntry supports additional markdown

* upgrade to Node 7 ([abc345](https://www.github.com/bcoe/release-please/commit/abc345))
`

exports['ConventionalCommits generateChangelogEntry parses additional commits in footers 1'] = `
## v1.0.0 (1665-10-10)


### ⚠ BREAKING CHANGES

* cool feature

### Features

* awesome feature ([abc678](https://www.github.com/bcoe/release-please/commit/abc678))
* cool feature ([abc345](https://www.github.com/bcoe/release-please/commit/abc345))


### Bug Fixes

* **subsystem:** also a fix ([abc345](https://www.github.com/bcoe/release-please/commit/abc345))
`

exports['ConventionalCommits generateChangelogEntry parses footer commits that contain footers 1'] = `
## v1.0.0 (1665-10-10)


### ⚠ BREAKING CHANGES

* **recaptchaenterprise:** for some reason this migration is breaking.

### Features

* awesome feature ([abc678](https://www.github.com/bcoe/release-please/commit/abc678))
* **recaptchaenterprise:** migrate microgenertor ([abc345](https://www.github.com/bcoe/release-please/commit/abc345))


### Bug Fixes

* **securitycenter:** fixes security center. ([abc345](https://www.github.com/bcoe/release-please/commit/abc345))
`

exports['ConventionalCommits generateChangelogEntry parses commits from footer, when body contains multiple paragraphs 1'] = `
## v1.0.0 (1665-10-10)


### ⚠ BREAKING CHANGES

* **recaptchaenterprise:** for some reason this migration is breaking.

### Features

* **recaptchaenterprise:** migrate microgenertor ([abc345](https://www.github.com/bcoe/release-please/commit/abc345))


### Bug Fixes

* fixes bug [#733](https://www.github.com/bcoe/release-please/issues/733) ([abc345](https://www.github.com/bcoe/release-please/commit/abc345))
* **securitycenter:** fixes security center. ([abc345](https://www.github.com/bcoe/release-please/commit/abc345))
`
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
"typescript": "^3.8.3"
},
"dependencies": {
"@conventional-commits/parser": "^0.3.0",
"@conventional-commits/parser": "^0.4.1",
"@iarna/toml": "^2.2.5",
"@octokit/graphql": "^4.3.1",
"@octokit/request": "^5.3.4",
Expand Down
53 changes: 26 additions & 27 deletions src/conventional-commits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,30 @@ interface Note {
text: string;
}

function getParsedCommits(commits: Commit[]): CommitWithHash[] {
const parsedCommits: CommitWithHash[] = [];
for (const commit of commits) {
try {
for (const parsedCommit of toConventionalChangelogFormat(
parser(commit.message)
)) {
const commitWithHash = postProcessCommits(
parsedCommit
) as CommitWithHash;
commitWithHash.hash = commit.sha;
parsedCommits.push(commitWithHash);
}
} catch (_err) {
// Commit is not in conventional commit format, it does not
// contribute to the CHANGELOG generation.
}
}
return parsedCommits;
}

// TODO(@bcoe): now that we walk the actual AST of conventional commits
// we should be able to move post processing into
// to-conventional-changelog.ts.
function postProcessCommits(commit: ConventionalChangelogCommit) {
commit.notes.forEach(note => {
let text = '';
Expand Down Expand Up @@ -179,41 +203,16 @@ export class ConventionalCommits {
this.headerPartial || preset.writerOpts.headerPartial;
preset.writerOpts.mainTemplate =
this.mainTemplate || preset.writerOpts.mainTemplate;
const parsedCommits = [];
for (const commit of this.commits) {
try {
const parsedCommit = postProcessCommits(
toConventionalChangelogFormat(parser(commit.message))
) as CommitWithHash;
parsedCommit.hash = commit.sha;
parsedCommits.push(parsedCommit);
} catch (_err) {
// Commit is not in conventional commit format, it does not
// contribute to the CHANGELOG generation.
}
}
const parsed: string = conventionalChangelogWriter
.parseArray(parsedCommits, context, preset.writerOpts)
.parseArray(getParsedCommits(this.commits), context, preset.writerOpts)
.trim();
return parsed;
}
private async guessReleaseType(preMajor: boolean): Promise<BumpSuggestion> {
const VERSIONS = ['major', 'minor', 'patch'];
const preset = await presetFactory({preMajor});
const parsedCommits = [];
for (const commit of this.commits) {
try {
const parsedCommit = toConventionalChangelogFormat(
parser(commit.message)
);
parsedCommits.push(parsedCommit);
} catch (_err) {
// Commit is not in conventional commit format, it does not
// contribute to the CHANGELOG generation.
}
}
const commits = conventionalCommitsFilter(
parsedCommits
getParsedCommits(this.commits)
) as ConventionalChangelogCommit;

let result = preset.recommendedBumpOpts.whatBump(
Expand Down
123 changes: 90 additions & 33 deletions src/util/to-conventional-changelog-format.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,21 @@ const visitWithAncestors = require('unist-util-visit-parents');
const NUMBER_REGEX = /^[0-9]+$/;
import * as parser from '@conventional-commits/parser';

type SummaryNode =
type SummaryNodes =
| parser.Type
| parser.Scope
| parser.BreakingChange
| parser.Text;
type FooterNodes =
| parser.Type
| parser.Scope
| parser.BreakingChange
| parser.Separator
| parser.Text
| parser.Newline;

// Converts conventional commit AST into conventional-changelog's
// output format, see: https://www.npmjs.com/package/conventional-commits-parser
export default function toConventionalChangelogFormat(
ast: parser.Message
): parser.ConventionalChangelogCommit {
const cc: parser.ConventionalChangelogCommit = {
function getBlankConventionalCommit(): parser.ConventionalChangelogCommit {
return {
body: '',
subject: '',
type: '',
Expand All @@ -43,6 +46,15 @@ export default function toConventionalChangelogFormat(
header: '',
footer: null,
};
}

// Converts conventional commit AST into conventional-changelog's
// output format, see: https://www.npmjs.com/package/conventional-commits-parser
export default function toConventionalChangelogFormat(
ast: parser.Message
): parser.ConventionalChangelogCommit[] {
const commits: parser.ConventionalChangelogCommit[] = [];
const headerCommit = getBlankConventionalCommit();
// Separate the body and summary nodes, this simplifies the subsequent
// tree walking logic:
let body;
Expand All @@ -59,22 +71,22 @@ export default function toConventionalChangelogFormat(
});

// <type>, "(", <scope>, ")", ["!"], ":", <whitespace>*, <text>
visit(summary, (node: SummaryNode) => {
visit(summary, (node: SummaryNodes) => {
switch (node.type) {
case 'type':
cc.type = node.value;
cc.header += node.value;
headerCommit.type = node.value;
headerCommit.header += node.value;
break;
case 'scope':
cc.scope = node.value;
cc.header += `(${node.value})`;
headerCommit.scope = node.value;
headerCommit.header += `(${node.value})`;
break;
case 'breaking-change':
cc.header += '!';
headerCommit.header += '!';
break;
case 'text':
cc.subject = node.value;
cc.header += `: ${node.value}`;
headerCommit.subject = node.value;
headerCommit.header += `: ${node.value}`;
break;
default:
break;
Expand All @@ -83,10 +95,8 @@ export default function toConventionalChangelogFormat(

// [<any body-text except pre-footer>]
if (body) {
visit(body, 'text', (node: parser.Text) => {
// TODO(@bcoe): once we have \n tokens in tree we can drop this:
if (cc.body !== '') cc.body += '\n';
cc.body += node.value;
visit(body, ['text', 'newline'], (node: parser.Text) => {
headerCommit.body += node.value;
});
}

Expand All @@ -104,39 +114,35 @@ export default function toConventionalChangelogFormat(
if (!parent) {
return;
}
let startCollecting = false;
switch (parent.type) {
case 'summary':
breaking.text = cc.subject;
breaking.text = headerCommit.subject;
break;
case 'body':
breaking.text = '';
// We treat text from the BREAKING CHANGE marker forward as
// the breaking change notes:
visit(
parent,
['text', 'breaking-change'],
['text', 'newline'],
(node: parser.Text | parser.BreakingChange) => {
// TODO(@bcoe): once we have \n tokens in tree we can drop this:
if (startCollecting && node.type === 'text') {
if (breaking.text !== '') breaking.text += '\n';
breaking.text += node.value;
} else if (node.type === 'breaking-change') {
startCollecting = true;
}
breaking.text += node.value;
}
);
break;
case 'token':
// If the '!' breaking change marker is used, the breaking change
// will be identified when the footer is parsed as a commit:
if (!node.value.includes('BREAKING')) return;
parent = ancestors.pop();
visit(parent, 'text', (node: parser.Text) => {
visit(parent, ['text', 'newline'], (node: parser.Text) => {
breaking.text = node.value;
});
break;
}
}
);
if (breaking.text !== '') cc.notes.push(breaking);
if (breaking.text !== '') headerCommit.notes.push(breaking);

// Populates references array from footers:
// references: [{
Expand Down Expand Up @@ -183,9 +189,60 @@ export default function toConventionalChangelogFormat(
);
// TODO(@bcoe): how should references like "Refs: v8:8940" work.
if (hasRefSepartor && reference.issue.match(NUMBER_REGEX)) {
cc.references.push(reference);
headerCommit.references.push(reference);
}
});

return cc;
/*
* Split footers that resemble commits into additional commits, e.g.,
* chore: multiple commits
* chore(recaptchaenterprise): migrate recaptchaenterprise to the Java microgenerator
* Committer: @miraleung
* PiperOrigin-RevId: 345559154
* ...
*/
visitWithAncestors(
ast,
['type'],
(node: parser.Type, ancestors: parser.Node[]) => {
let parent = ancestors.pop();
if (!parent) {
return;
}
if (parent.type === 'token') {
parent = ancestors.pop();
let footerText = '';
visit(
parent,
['type', 'scope', 'breaking-change', 'separator', 'text', 'newline'],
(node: FooterNodes) => {
switch (node.type) {
case 'scope':
footerText += `(${node.value})`;
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is necessary because we don't currently include ( and ) in the parser, I'd like to add a parenthesis node, which would simplify logic.

break;
case 'separator':
// Footers of the form Fixes #99, should not be parsed.
if (node.value.includes('#')) return;
footerText += `${node.value} `;
break;
default:
footerText += node.value;
break;
}
}
);
try {
for (const commit of toConventionalChangelogFormat(
parser.parser(footerText)
)) {
commits.push(commit);
}
} catch (err) {
// Footer does not appear to be an additional commit.
}
}
}
);
commits.push(headerCommit);
return commits;
}
Loading