Skip to content

Commit

Permalink
feat(changelog): Introduce property to skip merge commits in the chan…
Browse files Browse the repository at this point in the history
…gelog. Resolves jreleaser#858
  • Loading branch information
leveretka committed Jul 18, 2022
1 parent 0002e62 commit d3eb1fc
Show file tree
Hide file tree
Showing 6 changed files with 166 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ public class Changelog extends AbstractModelObject<Changelog> implements Domain,

private Boolean enabled;
private Boolean links;
private Boolean skipMergeCommits;
private Sort sort = Sort.DESC;
private String external;
private Active formatted;
Expand All @@ -79,6 +80,7 @@ public void merge(Changelog changelog) {
freezeCheck();
this.enabled = merge(this.enabled, changelog.enabled);
this.links = merge(this.links, changelog.links);
this.skipMergeCommits = merge(this.skipMergeCommits, changelog.skipMergeCommits);
this.sort = merge(this.sort, changelog.sort);
this.external = merge(this.external, changelog.external);
this.formatted = merge(this.formatted, changelog.formatted);
Expand Down Expand Up @@ -136,11 +138,20 @@ public boolean isLinks() {
return links != null && links;
}

public boolean isSkipMergeCommits() {
return skipMergeCommits != null && skipMergeCommits;
}

public void setLinks(Boolean links) {
freezeCheck();
this.links = links;
}

public void setSkipMergeCommits(Boolean skipMergeCommits) {
freezeCheck();
this.skipMergeCommits = skipMergeCommits;
}

public Sort getSort() {
return sort;
}
Expand Down Expand Up @@ -293,6 +304,7 @@ public Map<String, Object> asMap(boolean full) {
map.put("enabled", isEnabled());
map.put("external", external);
map.put("links", isLinks());
map.put("skipMergeCommits", isSkipMergeCommits());
map.put("sort", sort);
map.put("formatted", formatted);
map.put("preset", preset);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ interface Changelog {

Property<String> getPreset()

Property<Boolean> getSkipMergeCommits()

RegularFileProperty getContentTemplate()

void setContentTemplate(String contentTemplate)
Expand Down Expand Up @@ -134,4 +136,4 @@ interface Changelog {

void contributor(String contributor)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class ChangelogImpl implements Changelog {
final Property<Boolean> enabled
final Property<Boolean> links
final Property<Boolean> hideUncategorized
final Property<Boolean> skipMergeCommits
final Property<org.jreleaser.model.Changelog.Sort> sort
final RegularFileProperty external
final Property<Active> formatted
Expand All @@ -66,6 +67,7 @@ class ChangelogImpl implements Changelog {
enabled = objects.property(Boolean).convention(Providers.notDefined())
links = objects.property(Boolean).convention(Providers.notDefined())
hideUncategorized = objects.property(Boolean).convention(Providers.notDefined())
skipMergeCommits = objects.property(Boolean).convention(Providers.notDefined())
sort = objects.property(org.jreleaser.model.Changelog.Sort).convention(Providers.notDefined())
external = objects.fileProperty().convention(Providers.notDefined())
formatted = objects.property(Active).convention(Providers.notDefined())
Expand Down Expand Up @@ -99,6 +101,7 @@ class ChangelogImpl implements Changelog {
@Internal
boolean isSet() {
links.present ||
skipMergeCommits.present ||
hideUncategorized.present ||
external.present ||
sort.present ||
Expand Down Expand Up @@ -208,6 +211,7 @@ class ChangelogImpl implements Changelog {
if (!changelog.enabled) return changelog

if (links.present) changelog.links = links.get()
if (skipMergeCommits.present) changelog.skipMergeCommits = skipMergeCommits.get()
if (hideUncategorized.present) hide.uncategorized.set(hideUncategorized.get())
if (sort.present) changelog.sort = sort.get()
if (external.present) changelog.external = external.getAsFile().get().toPath()
Expand Down
3 changes: 2 additions & 1 deletion sdks/git-sdk/git-sdk.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,6 @@ dependencies {

testImplementation "org.mockito:mockito-core:$mockitoVersion"
testImplementation "org.mockito:mockito-junit-jupiter:$mockitoVersion"
testImplementation "org.mockito:mockito-inline:$mockitoVersion"
testImplementation "org.assertj:assertj-core:$assertjVersion"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ public ChangelogGenerator() {

}

private String createChangelog(JReleaserContext context) throws IOException {
protected String createChangelog(JReleaserContext context) throws IOException {
GitService gitService = context.getModel().getRelease().getGitService();
Changelog changelog = gitService.getChangelog();

Expand Down Expand Up @@ -114,6 +114,7 @@ private String createChangelog(JReleaserContext context) throws IOException {
lineSeparator() +
lineSeparator() +
StreamSupport.stream(commits.spliterator(), false)
.filter(c -> !changelog.isSkipMergeCommits() || c.getParentCount() <= 1)
.sorted(revCommitComparator)
.map(commit -> formatCommit(commit, commitsUrl, changelog, commitSeparator))
.collect(Collectors.joining(commitSeparator));
Expand All @@ -122,7 +123,7 @@ private String createChangelog(JReleaserContext context) throws IOException {
}
}

private String formatCommit(RevCommit commit, String commitsUrl, Changelog changelog, String commitSeparator) {
protected String formatCommit(RevCommit commit, String commitsUrl, Changelog changelog, String commitSeparator) {
String commitHash = commit.getId().name();
String abbreviation = commit.getId().abbreviate(7).name();
String[] input = commit.getFullMessage().trim().split(lineSeparator());
Expand Down Expand Up @@ -441,7 +442,7 @@ private ObjectId getObjectId(Git git, Ref ref) throws IOException {
return peeled.getPeeledObjectId() != null ? peeled.getPeeledObjectId() : peeled.getObjectId();
}

private String formatChangelog(JReleaserContext context,
protected String formatChangelog(JReleaserContext context,
Changelog changelog,
Iterable<RevCommit> commits,
Comparator<RevCommit> revCommitComparator,
Expand All @@ -450,6 +451,7 @@ private String formatChangelog(JReleaserContext context,
Map<String, List<Commit>> categories = new LinkedHashMap<>();

StreamSupport.stream(commits.spliterator(), false)
.filter(c -> !changelog.isSkipMergeCommits() || c.getParentCount() <= 1)
.sorted(revCommitComparator)
.map(Commit::of)
.peek(c -> {
Expand Down Expand Up @@ -572,7 +574,7 @@ private String applyReplacers(JReleaserContext context, Changelog changelog, Str
return text;
}

private String categorize(Commit commit, Changelog changelog) {
protected String categorize(Commit commit, Changelog changelog) {
if (!commit.labels.isEmpty()) {
for (Changelog.Category category : changelog.getCategories()) {
if (CollectionUtils.intersects(category.getLabels(), commit.labels)) {
Expand Down Expand Up @@ -613,7 +615,7 @@ private void applyLabels(Commit commit, Set<Changelog.Labeler> labelers) {
}
}

private boolean checkLabels(Commit commit, Changelog changelog) {
protected boolean checkLabels(Commit commit, Changelog changelog) {
if (!changelog.getIncludeLabels().isEmpty()) {
return CollectionUtils.intersects(changelog.getIncludeLabels(), commit.labels);
}
Expand All @@ -633,7 +635,7 @@ public static String generate(JReleaserContext context) throws IOException {
return new ChangelogGenerator().createChangelog(context);
}

private static class Commit {
protected static class Commit {
private static final Pattern CO_AUTHORED_BY_PATTERN = Pattern.compile("^[Cc]o-authored-by:\\s+(.*)\\s+<(.*)>.*$");
private final Set<String> labels = new LinkedHashSet<>();
private final Set<Author> committers = new LinkedHashSet<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,42 +25,75 @@
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectIdRef;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.revwalk.RevCommit;
import org.jreleaser.model.Changelog;
import org.jreleaser.model.GitService;
import org.jreleaser.model.JReleaserContext;
import org.jreleaser.model.JReleaserModel;
import org.jreleaser.model.Project;
import org.jreleaser.model.Release;
import org.jreleaser.model.VersionPattern;
import org.jreleaser.util.SemVer;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;

import java.io.IOException;
import java.io.StringReader;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;

import static java.util.stream.Collectors.toList;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
public class ChangelogGeneratorUnitTest {
@Spy
ChangelogGenerator changelogGenerator = new ChangelogGenerator();
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private Git git;
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private JReleaserContext context;
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
GitService gitService;
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
Changelog changelog;

private MockedStatic<GitSdk> gitSdkMockedStatic;

private MockedStatic<ChangelogGenerator.Commit> commitMockedStatic;


void cleanUpStaticMocks() {
gitSdkMockedStatic.close();
commitMockedStatic.close();
}

void setUpStaticMocks() {
gitSdkMockedStatic = Mockito.mockStatic(GitSdk.class);
commitMockedStatic = Mockito.mockStatic(ChangelogGenerator.Commit.class);
}

@Test
@DisplayName("When configured tag has no prefix and no matches if found then all commits from head must be used")
Expand Down Expand Up @@ -104,6 +137,109 @@ public void tagThatNoMatches() throws GitAPIException, IOException {
verify(logCommand).add(headId);
}

@Test
@DisplayName("When skipMergeCommits property is true and formatted enabled skip merge commits in the changelog")
public void skipMergeCommitsFormatted() throws GitAPIException, IOException {
// given:
setUpStaticMocks();
RevCommit mergeCommit = getMockRevCommit(true, true);

// when:
changelogGenerator.formatChangelog(context, changelog, Collections.singletonList(mergeCommit), Comparator.comparing(RevCommit::getCommitTime), "");

// then:
verify(changelogGenerator, times(0)).categorize(any(), any());

cleanUpStaticMocks();
}

@Test
@DisplayName("When skipMergeCommits property is false and formatted enabled keep merge commits in the changelog")
public void keepMergeCommitsFormatted() throws GitAPIException, IOException {
// given:
setUpStaticMocks();
RevCommit mergeCommit = getMockRevCommit(false, true);

// when:
changelogGenerator.formatChangelog(context, changelog, Collections.singletonList(mergeCommit), Comparator.comparing(RevCommit::getCommitTime), "");

// then:
verify(changelogGenerator, times(1)).categorize(any(), any());

cleanUpStaticMocks();
}

@Test
@DisplayName("When skipMergeCommits property is true and formatted disabled skip merge commits in the changelog")
public void skipMergeCommits() throws GitAPIException, IOException {
// given:
setUpStaticMocks();
RevCommit mergeCommit = getMockRevCommit(true, false);
when(gitService.getChangelog()).thenReturn(changelog);
Mockito.doReturn(Collections.singletonList(mergeCommit)).when(changelogGenerator).resolveCommits(git, context);

// when:
changelogGenerator.createChangelog(context);

// then:
verify(changelogGenerator, times(0)).formatCommit(any(), any(), any(), any());

cleanUpStaticMocks();
}

@Test
@DisplayName("When skipMergeCommits property is false and formatted disabled keep merge commits in the changelog")
public void keepMergeCommits() throws GitAPIException, IOException {
// given:
setUpStaticMocks();
RevCommit mergeCommit = getMockRevCommit(false, false);
when(gitService.getChangelog()).thenReturn(changelog);
Mockito.doReturn(Collections.singletonList(mergeCommit)).when(changelogGenerator).resolveCommits(git, context);

// when:
changelogGenerator.createChangelog(context);

// then:
verify(changelogGenerator, times(1)).formatCommit(any(), any(), any(), any());

cleanUpStaticMocks();
}

private RevCommit getMockRevCommit(boolean skipMergeCommits, boolean formatted) throws GitAPIException, IOException {
String effectiveTagName = "2.2.0";
String configuredTagName = "{{projectVersion}}";
ObjectId headId = ObjectId.fromString("085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7");
boolean isSnapshot = false;
List<Ref> tagRefs = buildMockedTagRefs(
new String[]{"refs/tags/v1.0.0", "cac0cab538b970a37ea1e769cbbde608743bc96d"},
new String[]{"refs/tags/v2.0.0", "a11bef06a3f659402fe7563abf99ad00de2209e6"});

doNecessaryMock(effectiveTagName, configuredTagName, headId, isSnapshot, tagRefs);

RevCommit mergeCommit = mock(RevCommit.class);
when(mergeCommit.getParentCount()).thenReturn(2);
when(mergeCommit.getId()).thenReturn(headId);
when(mergeCommit.getFullMessage()).thenReturn("");


GitSdk mockGitSdk = mock(GitSdk.class);
gitSdkMockedStatic.when(() -> GitSdk.of(context)).thenReturn(mockGitSdk);
when(mockGitSdk.open()).thenReturn(git);

ChangelogGenerator.Commit commit = mock(ChangelogGenerator.Commit.class);
when(commit.asContext(anyBoolean(), any())).thenReturn(new HashMap<>());
commitMockedStatic.when(() -> ChangelogGenerator.Commit.of(any())).thenReturn(commit);

Mockito.doReturn(true).when(changelogGenerator).checkLabels(commit, changelog);

Mockito.doReturn("").when(changelogGenerator).categorize(commit, changelog);

when(changelog.resolveFormatted(any())).thenReturn(formatted);
when(changelog.isSkipMergeCommits()).thenReturn(skipMergeCommits);
when(changelog.getResolvedContentTemplate(context)).thenReturn(new StringReader("Changelog"));
return mergeCommit;
}

private LogCommand doNecessaryMock(String effectiveTagName, String configuredTagName, ObjectId headId, boolean isSnapshot, List<Ref> tagRefs) throws GitAPIException, IOException {
ListTagCommand listTagCommand = mock(ListTagCommand.class);
JReleaserModel model = mock(JReleaserModel.class);
Expand All @@ -119,7 +255,6 @@ private LogCommand doNecessaryMock(String effectiveTagName, String configuredTag
when(git.log()).thenReturn(logCommand);
when(listTagCommand.call()).thenReturn(tagRefs);
when(git.tagList()).thenReturn(listTagCommand);
GitService gitService = mock(GitService.class, RETURNS_DEEP_STUBS);
when(release.getGitService()).thenReturn(gitService);

when(gitService.getEffectiveTagName(any())).thenReturn(effectiveTagName);
Expand All @@ -137,4 +272,4 @@ private List<Ref> buildMockedTagRefs(String[]... refs) {
null, 1))
.collect(toList());
}
}
}

0 comments on commit d3eb1fc

Please sign in to comment.