diff --git a/core/jreleaser-model-impl/src/main/java/org/jreleaser/model/internal/validation/release/BaseReleaserValidator.java b/core/jreleaser-model-impl/src/main/java/org/jreleaser/model/internal/validation/release/BaseReleaserValidator.java index b99cc99b4..ae7c6c800 100644 --- a/core/jreleaser-model-impl/src/main/java/org/jreleaser/model/internal/validation/release/BaseReleaserValidator.java +++ b/core/jreleaser-model-impl/src/main/java/org/jreleaser/model/internal/validation/release/BaseReleaserValidator.java @@ -294,10 +294,6 @@ private static void validateChangelog(JReleaserContext context, BaseReleaser ser changelog.setSort(org.jreleaser.model.Changelog.Sort.DESC); } - if (isBlank(changelog.getFormat())) { - changelog.setFormat("- {{commitShortHash}} {{commitTitle}} ({{commitAuthor}})"); - } - if (isBlank(changelog.getCategoryTitleFormat())) { changelog.setCategoryTitleFormat("## {{categoryTitle}}"); } @@ -325,6 +321,11 @@ private static void validateChangelog(JReleaserContext context, BaseReleaser ser loadPreset(context, changelog, errors); } + // set the default format after the preset, as preset can contain a default format too + if (isBlank(changelog.getFormat())) { + changelog.setFormat("- {{commitShortHash}} {{commitTitle}} ({{commitAuthor}})"); + } + if (changelog.getCategories().isEmpty()) { changelog.getCategories().add(Changelog.Category.of("feature", RB.$("default.category.feature"), "", "feature", "enhancement")); changelog.getCategories().add(Changelog.Category.of("fix", RB.$("default.category.bug.fix"), "", "bug", "fix")); @@ -424,6 +425,10 @@ private static void loadPreset(JReleaserContext context, Changelog changelog, Er if (null != inputStream) { Changelog loaded = JReleaserConfigLoader.load(Changelog.class, presetFileName, inputStream); + if(isBlank(changelog.getFormat())) { + changelog.setFormat(loaded.getFormat()); + } + Set labelersCopy = new TreeSet<>(Changelog.Labeler.ORDER); labelersCopy.addAll(changelog.getLabelers()); labelersCopy.addAll(loaded.getLabelers()); diff --git a/core/jreleaser-model-impl/src/main/resources/META-INF/jreleaser/changelog/preset-conventional-commits.yml b/core/jreleaser-model-impl/src/main/resources/META-INF/jreleaser/changelog/preset-conventional-commits.yml index c4dd2a444..7fec87096 100644 --- a/core/jreleaser-model-impl/src/main/resources/META-INF/jreleaser/changelog/preset-conventional-commits.yml +++ b/core/jreleaser-model-impl/src/main/resources/META-INF/jreleaser/changelog/preset-conventional-commits.yml @@ -81,10 +81,4 @@ categories: labels: - 'docs' -replacers: - - search: '((?:build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(?:\(.*\))?)!(:\s.*)' - replace: '🚨 $1$2' - - search: '(?:build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)\((.*)\):\s(.*)' - replace: '\*\*$1\*\*: $2' - - search: '(?:build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test):\s(.*)' - replace: '$1' \ No newline at end of file +format: '- {{commitShortHash}} {{#commitIsConventional}}{{#conventionalCommitIsBreakingChange}}🚨 {{/conventionalCommitIsBreakingChange}}{{#conventionalCommitScope}}**{{conventionalCommitScope}}**: {{/conventionalCommitScope}}{{conventionalCommitDescription}}{{#conventionalCommitBreakingChangeContent}} - *{{conventionalCommitBreakingChangeContent}}*{{/conventionalCommitBreakingChangeContent}}{{/commitIsConventional}}{{^commitIsConventional}}{{commitTitle}}{{/commitIsConventional}}' \ No newline at end of file diff --git a/sdks/jreleaser-git-java-sdk/src/main/java/org/jreleaser/sdk/git/ChangelogGenerator.java b/sdks/jreleaser-git-java-sdk/src/main/java/org/jreleaser/sdk/git/ChangelogGenerator.java index 76dd462db..faa30dfe7 100644 --- a/sdks/jreleaser-git-java-sdk/src/main/java/org/jreleaser/sdk/git/ChangelogGenerator.java +++ b/sdks/jreleaser-git-java-sdk/src/main/java/org/jreleaser/sdk/git/ChangelogGenerator.java @@ -36,6 +36,7 @@ import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Comparator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; @@ -43,10 +44,12 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.OptionalInt; import java.util.Set; import java.util.TreeSet; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.IntStream; import java.util.stream.StreamSupport; import static java.lang.System.lineSeparator; @@ -348,7 +351,7 @@ protected String formatChangelog(JReleaserContext context, commits.stream() .sorted(revCommitComparator) - .map(Commit::of) + .map(rc -> "conventional-commits".equals(changelog.getPreset()) ? ConventionalCommit.of(rc) : Commit.of(rc)) .peek(c -> { if (!changelog.getContributors().isEnabled()) return; @@ -571,10 +574,27 @@ protected static class Commit { private String fullHash; private String shortHash; private String title; - private String body; + protected String body; private Author author; private int time; + protected Commit(RevCommit rc) { + fullHash = rc.getId().name(); + shortHash = rc.getId().abbreviate(7).name(); + body = rc.getFullMessage(); + String[] lines = split(body); + title = lines[0]; + author = new Author(rc.getAuthorIdent().getName(), rc.getAuthorIdent().getEmailAddress()); + addContributor(rc.getCommitterIdent().getName(), rc.getCommitterIdent().getEmailAddress()); + time = rc.getCommitTime(); + for (String line : lines) { + Matcher m = CO_AUTHORED_BY_PATTERN.matcher(line); + if (m.matches()) { + addContributor(m.group(1), m.group(2)); + } + } + } + Map asContext(boolean links, String commitsUrl) { Map context = new LinkedHashMap<>(); if (links) { @@ -597,30 +617,137 @@ private void addContributor(String name, String email) { } static Commit of(RevCommit rc) { - Commit c = new Commit(); - c.fullHash = rc.getId().name(); - c.shortHash = rc.getId().abbreviate(7).name(); - c.body = rc.getFullMessage(); - String[] lines = split(c.body); - c.title = lines[0]; - c.author = new Author(rc.getAuthorIdent().getName(), rc.getAuthorIdent().getEmailAddress()); - c.addContributor(rc.getCommitterIdent().getName(), rc.getCommitterIdent().getEmailAddress()); - c.time = rc.getCommitTime(); - for (String line : lines) { - Matcher m = CO_AUTHORED_BY_PATTERN.matcher(line); - if (m.matches()) { - c.addContributor(m.group(1), m.group(2)); - } - } - return c; + return new Commit(rc); } - private static String[] split(String str) { + protected static String[] split(String str) { // Any Unicode linebreak sequence return str.split("\\R"); } } + static class ConventionalCommit extends Commit { + private static final Pattern FIRST_LINE_PATTERN = + Pattern.compile("^(?[a-z]+)(?:\\((?\\w+)\\))?(?!)?: (?.*$)"); + private static final Pattern BREAKING_CHANGE_PATTERN = Pattern.compile("^BREAKING[ \\-]CHANGE:\\s+(?[\\w\\W]+)", Pattern.MULTILINE); + private static final Pattern TRAILER_PATTERN = Pattern.compile("(?^\\w+(?:-\\w+)*)(?:: | #)(?.*$)"); + + private boolean isConventional = true; + private boolean ccIsBreakingChange; + private String ccType = ""; + private String ccScope = ""; + private String ccDescription = ""; + private String ccBody = ""; + private final List trailers = new ArrayList<>(); + private String ccBreakingChangeContent = ""; + + private ConventionalCommit(RevCommit rc) { + super(rc); + List lines = new ArrayList<>(Arrays.asList(split(body))); + Matcher matcherFirstLine = FIRST_LINE_PATTERN.matcher(lines.get(0)); + if (matcherFirstLine.matches()) { + lines.remove(0); // consumed first line + if (matcherFirstLine.group("bang") != null && !matcherFirstLine.group("bang").isEmpty()) { + ccIsBreakingChange = true; + } + ccType = matcherFirstLine.group("type"); + ccScope = matcherFirstLine.group("scope") == null ? "" : matcherFirstLine.group("scope"); + ccDescription = matcherFirstLine.group("description"); + } else { + isConventional = false; + return; + } + + // drop any empty lines at the beginning + while (!lines.isEmpty() && lines.get(0).equals("")) { + lines.remove(0); + } + + // try to match trailers from the end + while (!lines.isEmpty()) { + Matcher matcherTrailer = TRAILER_PATTERN.matcher(lines.get(lines.size() - 1)); + if (matcherTrailer.matches()) { + String token = matcherTrailer.group("token"); + if(token.equals("BREAKING-CHANGE")) break; + trailers.add(new Trailer(token, matcherTrailer.group("value"))); + lines.remove(lines.size() - 1); // consume last line + } else { + break; + } + } + + // drop any empty lines at the end + while (!lines.isEmpty() && lines.get(lines.size() - 1).equals("")) { + lines.remove(lines.size() - 1); + } + + Matcher matcherBC = BREAKING_CHANGE_PATTERN.matcher(String.join("\n", lines)); + if (matcherBC.find()) { + ccIsBreakingChange = true; + ccBreakingChangeContent = matcherBC.group("content"); + // consume the breaking change + OptionalInt match = IntStream.range(0, lines.size()) + .filter(i -> BREAKING_CHANGE_PATTERN.matcher(lines.get(i)).find()) + .findFirst(); + if (match.isPresent()) { + if (lines.size() > match.getAsInt()) { + lines.subList(match.getAsInt(), lines.size()).clear(); + } + } + } + + // the rest is the body + ccBody = String.join("\n", lines); + } + + public static Commit of(RevCommit rc) { + ConventionalCommit c = new ConventionalCommit(rc); + if(c.isConventional) return c; + // not ideal to reparse the commit, but that way we return a Commit instead of a ConventionalCommit + else return Commit.of(rc); + } + + @Override + Map asContext(boolean links, String commitsUrl) { + Map context = super.asContext(links, commitsUrl); + context.put("commitIsConventional", isConventional); + context.put("conventionalCommitBreakingChangeContent", passThrough(ccBreakingChangeContent)); + context.put("conventionalCommitIsBreakingChange", ccIsBreakingChange); + context.put("conventionalCommitType", passThrough(ccType)); + context.put("conventionalCommitScope", passThrough(ccScope)); + context.put("conventionalCommitDescription", passThrough(ccDescription)); + context.put("conventionalCommitBody", passThrough(ccBody)); + return context; + } + + public List getTrailers() { + return trailers; + } + + static class Trailer { + private final String token; + private final String value; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Trailer)) return false; + Trailer trailer = (Trailer) o; + return token.equals(trailer.token) && value.equals(trailer.value); + } + + @Override + public int hashCode() { + return Objects.hash(token, value); + } + + public Trailer(String token, String value) { + this.token = token; + this.value = value; + } + } + } + private static class Author implements Comparable { protected final String name; protected final String email; diff --git a/sdks/jreleaser-git-java-sdk/src/test/java/org/jreleaser/sdk/git/ConventionalCommitUnitTest.java b/sdks/jreleaser-git-java-sdk/src/test/java/org/jreleaser/sdk/git/ConventionalCommitUnitTest.java new file mode 100644 index 000000000..19d9a15b3 --- /dev/null +++ b/sdks/jreleaser-git-java-sdk/src/test/java/org/jreleaser/sdk/git/ConventionalCommitUnitTest.java @@ -0,0 +1,355 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Copyright 2020-2022 The JReleaser authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jreleaser.sdk.git; + +import org.eclipse.jgit.lib.AbbreviatedObjectId; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.revwalk.RevCommit; +import org.jreleaser.model.internal.release.Changelog; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class ConventionalCommitUnitTest { + + private ChangelogGenerator.Commit mockCommit(String commitBody) { + RevCommit revCommit = mock(RevCommit.class); + ObjectId objectId = mock(ObjectId.class); + AbbreviatedObjectId abbreviatedObjectId = mock(AbbreviatedObjectId.class); + PersonIdent committer = mock(PersonIdent.class); + PersonIdent author = mock(PersonIdent.class); + Changelog changelog = mock(Changelog.class); + int time = 123456; + + when(revCommit.getId()).thenReturn(objectId); + when(objectId.name()).thenReturn("full-hash"); + when(objectId.abbreviate(7)).thenReturn(abbreviatedObjectId); + when(abbreviatedObjectId.name()).thenReturn("short-hash"); + when(revCommit.getFullMessage()).thenReturn(commitBody); + when(revCommit.getCommitterIdent()).thenReturn(committer); + when(committer.getName()).thenReturn("committer-name"); + when(committer.getEmailAddress()).thenReturn("committer@example.com"); + when(revCommit.getAuthorIdent()).thenReturn(author); + when(author.getName()).thenReturn("author-name"); + when(author.getEmailAddress()).thenReturn("author@example.com"); + when(revCommit.getCommitTime()).thenReturn(time); + when(changelog.getPreset()).thenReturn("conventional-commits"); + + return ChangelogGenerator.ConventionalCommit.of(revCommit); + } + + @Test + public void badlyFormattedCommitIsNotConventional() { + String commitBody = "featadd new feature"; + + ChangelogGenerator.Commit c = mockCommit(commitBody); + + assertThat(c) + .isInstanceOf(ChangelogGenerator.Commit.class) + .isNotInstanceOf(ChangelogGenerator.ConventionalCommit.class); + } + + @Test + public void typeCanBeAnyWord() { + String commitBody = "whatever: correct spelling of CHANGELOG"; + + ChangelogGenerator.Commit c = mockCommit(commitBody); + + assertThat(c) + .hasFieldOrPropertyWithValue("isConventional", true) + .hasFieldOrPropertyWithValue("ccIsBreakingChange", false) + .hasFieldOrPropertyWithValue("ccBreakingChangeContent", "") + .hasFieldOrPropertyWithValue("ccType", "whatever") + .hasFieldOrPropertyWithValue("ccScope", "") + .hasFieldOrPropertyWithValue("ccDescription", "correct spelling of CHANGELOG") + .hasFieldOrPropertyWithValue("ccBody", ""); + assertThat(((ChangelogGenerator.ConventionalCommit) c).getTrailers()).isEmpty(); + } + + @Test + public void bangIsBreakingChange() { + String commitBody = "feat(scope)!: add new feature"; + + ChangelogGenerator.Commit c = mockCommit(commitBody); + + assertThat(c) + .hasFieldOrPropertyWithValue("isConventional", true) + .hasFieldOrPropertyWithValue("ccIsBreakingChange", true) + .hasFieldOrPropertyWithValue("ccBreakingChangeContent", "") + .hasFieldOrPropertyWithValue("ccType", "feat") + .hasFieldOrPropertyWithValue("ccScope", "scope") + .hasFieldOrPropertyWithValue("ccDescription", "add new feature") + .hasFieldOrPropertyWithValue("ccBody", ""); + assertThat(((ChangelogGenerator.ConventionalCommit) c).getTrailers()).isEmpty(); + } + + @Test + public void singleLineBreakingChange() { + String commitBody = "feat(scope): add new feature\n" + + "\n" + + "BREAKING CHANGE: single line breaking change"; + + ChangelogGenerator.Commit c = mockCommit(commitBody); + + assertThat(c) + .hasFieldOrPropertyWithValue("isConventional", true) + .hasFieldOrPropertyWithValue("ccIsBreakingChange", true) + .hasFieldOrPropertyWithValue("ccBreakingChangeContent", "single line breaking change") + .hasFieldOrPropertyWithValue("ccType", "feat") + .hasFieldOrPropertyWithValue("ccScope", "scope") + .hasFieldOrPropertyWithValue("ccDescription", "add new feature") + .hasFieldOrPropertyWithValue("ccBody", ""); + assertThat(((ChangelogGenerator.ConventionalCommit) c).getTrailers()).isEmpty(); + } + + @Test + public void singleLineBreakingChangeWithDash() { + String commitBody = "feat(scope): add new feature\n" + + "\n" + + "BREAKING-CHANGE: single line breaking change"; + + ChangelogGenerator.Commit c = mockCommit(commitBody); + + assertThat(c) + .hasFieldOrPropertyWithValue("isConventional", true) + .hasFieldOrPropertyWithValue("ccIsBreakingChange", true) + .hasFieldOrPropertyWithValue("ccBreakingChangeContent", "single line breaking change") + .hasFieldOrPropertyWithValue("ccType", "feat") + .hasFieldOrPropertyWithValue("ccScope", "scope") + .hasFieldOrPropertyWithValue("ccDescription", "add new feature") + .hasFieldOrPropertyWithValue("ccBody", ""); + assertThat(((ChangelogGenerator.ConventionalCommit) c).getTrailers()).isEmpty(); + } + + @Test + public void singleLineBreakingChangeLowerCase() { + String commitBody = "feat(scope): add new feature\n" + + "\n" + + "breaking CHANGE: single line breaking change"; + + ChangelogGenerator.Commit c = mockCommit(commitBody); + + assertThat(c) + .hasFieldOrPropertyWithValue("isConventional", true) + .hasFieldOrPropertyWithValue("ccIsBreakingChange", false) + .hasFieldOrPropertyWithValue("ccBreakingChangeContent", "") + .hasFieldOrPropertyWithValue("ccType", "feat") + .hasFieldOrPropertyWithValue("ccScope", "scope") + .hasFieldOrPropertyWithValue("ccDescription", "add new feature") + .hasFieldOrPropertyWithValue("ccBody", "breaking CHANGE: single line breaking change"); + assertThat(((ChangelogGenerator.ConventionalCommit) c).getTrailers()).isEmpty(); + } + + @Test + public void multiLineBreakingChange() { + String commitBody = "feat(scope): add new feature\n" + + "\n" + + "BREAKING CHANGE: multi line\n" + + "breaking change"; + + ChangelogGenerator.Commit c = mockCommit(commitBody); + + assertThat(c) + .hasFieldOrPropertyWithValue("isConventional", true) + .hasFieldOrPropertyWithValue("ccIsBreakingChange", true) + .hasFieldOrPropertyWithValue("ccBreakingChangeContent", "multi line\nbreaking change") + .hasFieldOrPropertyWithValue("ccType", "feat") + .hasFieldOrPropertyWithValue("ccScope", "scope") + .hasFieldOrPropertyWithValue("ccDescription", "add new feature") + .hasFieldOrPropertyWithValue("ccBody", ""); + assertThat(((ChangelogGenerator.ConventionalCommit) c).getTrailers()).isEmpty(); + } + + @Test + public void singleLineBreakingChangeFollowedByGitTrailer() { + String commitBody = "feat(scope): add new feature\n" + + "\n" + + "BREAKING CHANGE: single line breaking change\n" + + "Reviewed-by: Z\n" + + "Closes #42"; + + ChangelogGenerator.Commit c = mockCommit(commitBody); + + assertThat(c) + .hasFieldOrPropertyWithValue("isConventional", true) + .hasFieldOrPropertyWithValue("ccIsBreakingChange", true) + .hasFieldOrPropertyWithValue("ccBreakingChangeContent", "single line breaking change"); + assertThat(((ChangelogGenerator.ConventionalCommit) c).getTrailers()) + .hasSize(2) + .containsExactlyInAnyOrder(new ChangelogGenerator.ConventionalCommit.Trailer("Reviewed-by", "Z"), new ChangelogGenerator.ConventionalCommit.Trailer("Closes", "42")); + } + + @Test + public void ccExample1() { + String commitBody = "feat: allow provided config object to extend other configs\n" + + "\n" + + "BREAKING CHANGE: `extends` key in config file is now used for extending other config files"; + + ChangelogGenerator.Commit c = mockCommit(commitBody); + + assertThat(c) + .hasFieldOrPropertyWithValue("isConventional", true) + .hasFieldOrPropertyWithValue("ccIsBreakingChange", true) + .hasFieldOrPropertyWithValue("ccBreakingChangeContent", "`extends` key in config file is now used for extending other config files") + .hasFieldOrPropertyWithValue("ccType", "feat") + .hasFieldOrPropertyWithValue("ccScope", "") + .hasFieldOrPropertyWithValue("ccDescription", "allow provided config object to extend other configs") + .hasFieldOrPropertyWithValue("ccBody", ""); + assertThat(((ChangelogGenerator.ConventionalCommit) c).getTrailers()).isEmpty(); + } + + @Test + public void ccExample2() { + String commitBody = "feat!: send an email to the customer when a product is shipped"; + + ChangelogGenerator.Commit c = mockCommit(commitBody); + + assertThat(c) + .hasFieldOrPropertyWithValue("isConventional", true) + .hasFieldOrPropertyWithValue("ccIsBreakingChange", true) + .hasFieldOrPropertyWithValue("ccBreakingChangeContent", "") + .hasFieldOrPropertyWithValue("ccType", "feat") + .hasFieldOrPropertyWithValue("ccScope", "") + .hasFieldOrPropertyWithValue("ccDescription", "send an email to the customer when a product is shipped") + .hasFieldOrPropertyWithValue("ccBody", ""); + assertThat(((ChangelogGenerator.ConventionalCommit) c).getTrailers()).isEmpty(); + } + + @Test + public void ccExample3() { + String commitBody = "feat(api)!: send an email to the customer when a product is shipped"; + + ChangelogGenerator.Commit c = mockCommit(commitBody); + + assertThat(c) + .hasFieldOrPropertyWithValue("isConventional", true) + .hasFieldOrPropertyWithValue("ccIsBreakingChange", true) + .hasFieldOrPropertyWithValue("ccBreakingChangeContent", "") + .hasFieldOrPropertyWithValue("ccType", "feat") + .hasFieldOrPropertyWithValue("ccScope", "api") + .hasFieldOrPropertyWithValue("ccDescription", "send an email to the customer when a product is shipped") + .hasFieldOrPropertyWithValue("ccBody", ""); + assertThat(((ChangelogGenerator.ConventionalCommit) c).getTrailers()).isEmpty(); + + assertThat(c.asContext(false, "")) + .containsEntry("commitIsConventional", true) + .containsEntry("conventionalCommitBreakingChangeContent", "") + .containsEntry("conventionalCommitIsBreakingChange", true) + .containsEntry("conventionalCommitType", "!!feat!!") + .containsEntry("conventionalCommitScope", "!!api!!") + .containsEntry("conventionalCommitDescription", "!!send an email to the customer when a product is shipped!!") + .containsEntry("conventionalCommitBody", ""); + } + + @Test + public void ccExample4() { + String commitBody = "chore!: drop support for Node 6\n" + + "\n" + + "BREAKING CHANGE: use JavaScript features not available in Node 6."; + + ChangelogGenerator.Commit c = mockCommit(commitBody); + + assertThat(c) + .hasFieldOrPropertyWithValue("isConventional", true) + .hasFieldOrPropertyWithValue("ccIsBreakingChange", true) + .hasFieldOrPropertyWithValue("ccBreakingChangeContent", "use JavaScript features not available in Node 6.") + .hasFieldOrPropertyWithValue("ccType", "chore") + .hasFieldOrPropertyWithValue("ccScope", "") + .hasFieldOrPropertyWithValue("ccDescription", "drop support for Node 6") + .hasFieldOrPropertyWithValue("ccBody", ""); + assertThat(((ChangelogGenerator.ConventionalCommit) c).getTrailers()).isEmpty(); + + assertThat(c.asContext(false, "")) + .containsEntry("commitIsConventional", true) + .containsEntry("conventionalCommitBreakingChangeContent", "!!use JavaScript features not available in Node 6.!!") + .containsEntry("conventionalCommitIsBreakingChange", true) + .containsEntry("conventionalCommitType", "!!chore!!") + .containsEntry("conventionalCommitScope", "") + .containsEntry("conventionalCommitDescription", "!!drop support for Node 6!!") + .containsEntry("conventionalCommitBody", ""); + } + + @Test + public void ccExample5() { + String commitBody = "docs: correct spelling of CHANGELOG"; + + ChangelogGenerator.Commit c = mockCommit(commitBody); + + assertThat(c) + .hasFieldOrPropertyWithValue("isConventional", true) + .hasFieldOrPropertyWithValue("ccIsBreakingChange", false) + .hasFieldOrPropertyWithValue("ccBreakingChangeContent", "") + .hasFieldOrPropertyWithValue("ccType", "docs") + .hasFieldOrPropertyWithValue("ccScope", "") + .hasFieldOrPropertyWithValue("ccDescription", "correct spelling of CHANGELOG") + .hasFieldOrPropertyWithValue("ccBody", ""); + assertThat(((ChangelogGenerator.ConventionalCommit) c).getTrailers()).isEmpty(); + } + + @Test + public void ccExample6() { + String commitBody = "feat(lang): add Polish language"; + + ChangelogGenerator.Commit c = mockCommit(commitBody); + + assertThat(c) + .hasFieldOrPropertyWithValue("isConventional", true) + .hasFieldOrPropertyWithValue("ccIsBreakingChange", false) + .hasFieldOrPropertyWithValue("ccBreakingChangeContent", "") + .hasFieldOrPropertyWithValue("ccType", "feat") + .hasFieldOrPropertyWithValue("ccScope", "lang") + .hasFieldOrPropertyWithValue("ccDescription", "add Polish language") + .hasFieldOrPropertyWithValue("ccBody", ""); + assertThat(((ChangelogGenerator.ConventionalCommit) c).getTrailers()).isEmpty(); + } + + @Test + public void ccExample7() { + String commitBody = "fix: prevent racing of requests\n" + + "\n" + + "Introduce a request id and a reference to latest request. Dismiss\n" + + "incoming responses other than from latest request.\n" + + "\n" + + "Remove timeouts which were used to mitigate the racing issue but are\n" + + "obsolete now.\n" + + "\n" + + "Reviewed-by: Z\n" + + "Refs: #123"; + + ChangelogGenerator.Commit c = mockCommit(commitBody); + + assertThat(c) + .hasFieldOrPropertyWithValue("isConventional", true) + .hasFieldOrPropertyWithValue("ccIsBreakingChange", false) + .hasFieldOrPropertyWithValue("ccBreakingChangeContent", "") + .hasFieldOrPropertyWithValue("ccType", "fix") + .hasFieldOrPropertyWithValue("ccScope", "") + .hasFieldOrPropertyWithValue("ccDescription", "prevent racing of requests") + .hasFieldOrPropertyWithValue("ccBody", "Introduce a request id and a reference to latest request. Dismiss\n" + + "incoming responses other than from latest request.\n" + + "\n" + + "Remove timeouts which were used to mitigate the racing issue but are\n" + + "obsolete now."); + assertThat(((ChangelogGenerator.ConventionalCommit) c).getTrailers()) + .hasSize(2) + .containsExactlyInAnyOrder(new ChangelogGenerator.ConventionalCommit.Trailer("Reviewed-by", "Z"), new ChangelogGenerator.ConventionalCommit.Trailer("Refs", "#123")); + } +}