From b6d95f647132e1b612a552f2cc9d9b83b7dd88a8 Mon Sep 17 00:00:00 2001 From: Rene R Date: Mon, 2 Feb 2026 18:13:42 +0100 Subject: [PATCH 1/4] :recycle: refactor model to records in addtional project --- coverage/pom.xml | 5 + pom.xml | 1 + semver-analyzer-api/pom.xml | 2 +- .../semver/analyzer/CommitAnalyzer.java | 4 +- .../semver/analyzer/model/AnalyzedCommit.java | 36 --- .../semver/analyzer/model/Configuration.java | 1 + .../analyzer/model/ConfigurationTest.java | 31 +- .../analyzer/ConventionalCommitAnalyzer.java | 77 +++-- .../ConventionalCommitAnalyzerTest.java | 142 ++++----- .../analyzer/GitMojiCommitAnalyzer.java | 81 ++--- .../analyzer/GitMojiCommitAnalyzerTest.java | 129 ++++---- .../semver/changelog/ChangelogRenderer.java | 4 +- .../sam42r/semver/changelog/HtmlRenderer.java | 4 +- .../semver/changelog/HtmlRendererTest.java | 93 ++++-- .../semver/changelog/MarkupRenderer.java | 8 +- .../semver/changelog/MarkupRendererTest.java | 47 +-- semver-maven-model/pom.xml | 41 +++ .../semver/model/analyze/AnalyzedCommit.java | 26 ++ .../semver/model/analyze}/ChangeCategory.java | 2 +- .../sam42r/semver/model/analyze/Issue.java | 4 + .../model/analyze}/SemVerChangeLevel.java | 2 +- .../semver/model/changelog}/VersionInfo.java | 2 +- .../semver/model/release/ReleaseInfo.java | 6 + .../sam42r/semver/model/scm/Commit.java | 6 + .../sam42r/semver/model/scm/Remote.java | 34 +-- .../github/sam42r/semver/model/scm/Tag.java | 4 + .../sam42r/semver/model/scm/RemoteTest.java | 33 ++ semver-maven-plugin/pom.xml | 6 + .../sam42r/semver/SemanticReleaseMojo.java | 36 +-- .../semver/util/TagVersionComparator.java | 6 +- .../sam42r/semver/util/BundleHelper.java | 1 + .../semver/util/TagVersionComparatorTest.java | 20 +- semver-release-api/pom.xml | 5 + .../semver/release/ReleasePublisher.java | 2 +- .../semver/release/model/ReleaseInfo.java | 20 -- .../release/DefaultGithubPublisher.java | 8 +- .../semver/release/GithubPublisherTest.java | 28 +- .../semver/release/GitlabPublisher.java | 8 +- .../semver/release/GitlabPublisherTest.java | 26 +- semver-scm-api/pom.xml | 6 + .../semver/scm/AbstractScmProvider.java | 29 +- .../github/sam42r/semver/scm/SCMProvider.java | 6 +- .../sam42r/semver/scm/model/Commit.java | 20 -- .../sam42r/semver/scm/model/Remote.java | 19 -- .../github/sam42r/semver/scm/model/Tag.java | 16 - .../semver/scm/util/RemoteUtilTest.java | 44 --- .../github/sam42r/semver/scm/GitProvider.java | 281 ------------------ .../sam42r/semver/scm/GitProviderFactory.java | 22 -- .../semver/scm/AbstractGitProviderTest.java | 130 -------- .../semver/scm/DefaultGitProviderTest.java | 126 +++++++- .../sam42r/semver/scm/GitProviderTest.java | 29 -- .../github/sam42r/semver/scm/HgProvider.java | 6 +- .../github/sam42r/semver/scm/SvnProvider.java | 6 +- 53 files changed, 677 insertions(+), 1054 deletions(-) delete mode 100644 semver-analyzer-api/src/main/java/io/github/sam42r/semver/analyzer/model/AnalyzedCommit.java create mode 100644 semver-maven-model/pom.xml create mode 100644 semver-maven-model/src/main/java/io/github/sam42r/semver/model/analyze/AnalyzedCommit.java rename {semver-analyzer-api/src/main/java/io/github/sam42r/semver/analyzer/model => semver-maven-model/src/main/java/io/github/sam42r/semver/model/analyze}/ChangeCategory.java (66%) create mode 100644 semver-maven-model/src/main/java/io/github/sam42r/semver/model/analyze/Issue.java rename {semver-analyzer-api/src/main/java/io/github/sam42r/semver/analyzer/model => semver-maven-model/src/main/java/io/github/sam42r/semver/model/analyze}/SemVerChangeLevel.java (88%) rename {semver-changelog-api/src/main/java/io/github/sam42r/semver/changelog/model => semver-maven-model/src/main/java/io/github/sam42r/semver/model/changelog}/VersionInfo.java (60%) create mode 100644 semver-maven-model/src/main/java/io/github/sam42r/semver/model/release/ReleaseInfo.java create mode 100644 semver-maven-model/src/main/java/io/github/sam42r/semver/model/scm/Commit.java rename semver-scm-api/src/main/java/io/github/sam42r/semver/scm/util/RemoteUtil.java => semver-maven-model/src/main/java/io/github/sam42r/semver/model/scm/Remote.java (50%) create mode 100644 semver-maven-model/src/main/java/io/github/sam42r/semver/model/scm/Tag.java create mode 100644 semver-maven-model/src/test/java/io/github/sam42r/semver/model/scm/RemoteTest.java delete mode 100644 semver-release-api/src/main/java/io/github/sam24r/semver/release/model/ReleaseInfo.java delete mode 100644 semver-scm-api/src/main/java/io/github/sam42r/semver/scm/model/Commit.java delete mode 100644 semver-scm-api/src/main/java/io/github/sam42r/semver/scm/model/Remote.java delete mode 100644 semver-scm-api/src/main/java/io/github/sam42r/semver/scm/model/Tag.java delete mode 100644 semver-scm-api/src/test/java/io/github/sam42r/semver/scm/util/RemoteUtilTest.java delete mode 100644 semver-scm-git/src/main/java/io/github/sam42r/semver/scm/GitProvider.java delete mode 100644 semver-scm-git/src/main/java/io/github/sam42r/semver/scm/GitProviderFactory.java delete mode 100644 semver-scm-git/src/test/java/io/github/sam42r/semver/scm/AbstractGitProviderTest.java delete mode 100644 semver-scm-git/src/test/java/io/github/sam42r/semver/scm/GitProviderTest.java diff --git a/coverage/pom.xml b/coverage/pom.xml index ae2ee7a..29bd859 100644 --- a/coverage/pom.xml +++ b/coverage/pom.xml @@ -19,6 +19,11 @@ + + io.github.sam42r + semver-maven-model + ${project.version} + io.github.sam42r semver-scm-api diff --git a/pom.xml b/pom.xml index 73e9064..b75c1ff 100644 --- a/pom.xml +++ b/pom.xml @@ -15,6 +15,7 @@ https://github.com/Sam42R/semver-maven-plugin + semver-maven-model semver-scm-api semver-scm-git semver-scm-subversion diff --git a/semver-analyzer-api/pom.xml b/semver-analyzer-api/pom.xml index f990a94..484c578 100644 --- a/semver-analyzer-api/pom.xml +++ b/semver-analyzer-api/pom.xml @@ -22,7 +22,7 @@ io.github.sam42r - semver-scm-api + semver-maven-model ${project.version} diff --git a/semver-analyzer-api/src/main/java/io/github/sam42r/semver/analyzer/CommitAnalyzer.java b/semver-analyzer-api/src/main/java/io/github/sam42r/semver/analyzer/CommitAnalyzer.java index 85dbb42..08be05e 100644 --- a/semver-analyzer-api/src/main/java/io/github/sam42r/semver/analyzer/CommitAnalyzer.java +++ b/semver-analyzer-api/src/main/java/io/github/sam42r/semver/analyzer/CommitAnalyzer.java @@ -1,7 +1,7 @@ package io.github.sam42r.semver.analyzer; -import io.github.sam42r.semver.analyzer.model.AnalyzedCommit; -import io.github.sam42r.semver.scm.model.Commit; +import io.github.sam42r.semver.model.analyze.AnalyzedCommit; +import io.github.sam42r.semver.model.scm.Commit; import lombok.NonNull; import java.util.List; diff --git a/semver-analyzer-api/src/main/java/io/github/sam42r/semver/analyzer/model/AnalyzedCommit.java b/semver-analyzer-api/src/main/java/io/github/sam42r/semver/analyzer/model/AnalyzedCommit.java deleted file mode 100644 index 99864d3..0000000 --- a/semver-analyzer-api/src/main/java/io/github/sam42r/semver/analyzer/model/AnalyzedCommit.java +++ /dev/null @@ -1,36 +0,0 @@ -package io.github.sam42r.semver.analyzer.model; - -import io.github.sam42r.semver.scm.model.Commit; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -import java.util.List; -import java.util.function.Predicate; - -@Data -@SuperBuilder -@EqualsAndHashCode(callSuper = false) -@NoArgsConstructor -public class AnalyzedCommit extends Commit { - - private String header; - private String body; - private String footer; - - private String type; - private ChangeCategory category; - private String scope; - private String subject; - private SemVerChangeLevel level; - - private List issues; - - public static final Predicate isBugfix = analyzedCommit -> - SemVerChangeLevel.PATCH.equals(analyzedCommit.getLevel()); - public static final Predicate isFeature = analyzedCommit -> - SemVerChangeLevel.MINOR.equals(analyzedCommit.getLevel()); - public static final Predicate isBreaking = analyzedCommit -> - SemVerChangeLevel.MAJOR.equals(analyzedCommit.getLevel()); -} diff --git a/semver-analyzer-api/src/main/java/io/github/sam42r/semver/analyzer/model/Configuration.java b/semver-analyzer-api/src/main/java/io/github/sam42r/semver/analyzer/model/Configuration.java index 53bdf75..fc24854 100644 --- a/semver-analyzer-api/src/main/java/io/github/sam42r/semver/analyzer/model/Configuration.java +++ b/semver-analyzer-api/src/main/java/io/github/sam42r/semver/analyzer/model/Configuration.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import io.github.sam42r.semver.model.analyze.AnalyzedCommit; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; diff --git a/semver-analyzer-api/src/test/java/io/github/sam42r/semver/analyzer/model/ConfigurationTest.java b/semver-analyzer-api/src/test/java/io/github/sam42r/semver/analyzer/model/ConfigurationTest.java index e90f595..4f82c49 100644 --- a/semver-analyzer-api/src/test/java/io/github/sam42r/semver/analyzer/model/ConfigurationTest.java +++ b/semver-analyzer-api/src/test/java/io/github/sam42r/semver/analyzer/model/ConfigurationTest.java @@ -1,5 +1,8 @@ package io.github.sam42r.semver.analyzer.model; +import io.github.sam42r.semver.model.analyze.AnalyzedCommit; +import io.github.sam42r.semver.model.analyze.ChangeCategory; +import io.github.sam42r.semver.model.analyze.SemVerChangeLevel; import org.junit.jupiter.api.Test; import java.util.List; @@ -17,13 +20,13 @@ void shouldReadConfigFromClasspath() { Configuration::getRelease, Configuration::getItems) .containsExactly( "R0", List.of( - AnalyzedCommit.builder().type("D1").category(ChangeCategory.DEPRECATED).level(SemVerChangeLevel.NONE).build(), - AnalyzedCommit.builder().type("R1").category(ChangeCategory.REMOVED).level(SemVerChangeLevel.NONE).build(), - AnalyzedCommit.builder().type("F1").category(ChangeCategory.FIXED).level(SemVerChangeLevel.PATCH).build(), - AnalyzedCommit.builder().type("S1").category(ChangeCategory.SECURITY).level(SemVerChangeLevel.PATCH).build(), - AnalyzedCommit.builder().type("A1").category(ChangeCategory.ADDED).level(SemVerChangeLevel.MINOR).build(), - AnalyzedCommit.builder().type("C1").category(ChangeCategory.CHANGED).level(SemVerChangeLevel.MINOR).build(), - AnalyzedCommit.builder().type("B1").category(ChangeCategory.OTHER).level(SemVerChangeLevel.MAJOR).build() + new AnalyzedCommit(null, null, null, null, "D1", ChangeCategory.DEPRECATED, null, null, SemVerChangeLevel.NONE, null), + new AnalyzedCommit(null, null, null, null, "R1", ChangeCategory.REMOVED, null, null, SemVerChangeLevel.NONE, null), + new AnalyzedCommit(null, null, null, null, "F1", ChangeCategory.FIXED, null, null, SemVerChangeLevel.PATCH, null), + new AnalyzedCommit(null, null, null, null, "S1", ChangeCategory.SECURITY, null, null, SemVerChangeLevel.PATCH, null), + new AnalyzedCommit(null, null, null, null, "A1", ChangeCategory.ADDED, null, null, SemVerChangeLevel.MINOR, null), + new AnalyzedCommit(null, null, null, null, "C1", ChangeCategory.CHANGED, null, null, SemVerChangeLevel.MINOR, null), + new AnalyzedCommit(null, null, null, null, "B1", ChangeCategory.OTHER, null, null, SemVerChangeLevel.MAJOR, null) ) ); } @@ -38,13 +41,13 @@ void shouldReadConfigFromFile() { Configuration::getRelease, Configuration::getItems) .containsExactly( "R0", List.of( - AnalyzedCommit.builder().type("D1").category(ChangeCategory.DEPRECATED).level(SemVerChangeLevel.NONE).build(), - AnalyzedCommit.builder().type("R1").category(ChangeCategory.REMOVED).level(SemVerChangeLevel.NONE).build(), - AnalyzedCommit.builder().type("F1").category(ChangeCategory.FIXED).level(SemVerChangeLevel.PATCH).build(), - AnalyzedCommit.builder().type("S1").category(ChangeCategory.SECURITY).level(SemVerChangeLevel.PATCH).build(), - AnalyzedCommit.builder().type("A1").category(ChangeCategory.ADDED).level(SemVerChangeLevel.MINOR).build(), - AnalyzedCommit.builder().type("C1").category(ChangeCategory.CHANGED).level(SemVerChangeLevel.MINOR).build(), - AnalyzedCommit.builder().type("B1").category(ChangeCategory.OTHER).level(SemVerChangeLevel.MAJOR).build() + new AnalyzedCommit(null, null, null, null, "D1", ChangeCategory.DEPRECATED, null, null, SemVerChangeLevel.NONE, null), + new AnalyzedCommit(null, null, null, null, "R1", ChangeCategory.REMOVED, null, null, SemVerChangeLevel.NONE, null), + new AnalyzedCommit(null, null, null, null, "F1", ChangeCategory.FIXED, null, null, SemVerChangeLevel.PATCH, null), + new AnalyzedCommit(null, null, null, null, "S1", ChangeCategory.SECURITY, null, null, SemVerChangeLevel.PATCH, null), + new AnalyzedCommit(null, null, null, null, "A1", ChangeCategory.ADDED, null, null, SemVerChangeLevel.MINOR, null), + new AnalyzedCommit(null, null, null, null, "C1", ChangeCategory.CHANGED, null, null, SemVerChangeLevel.MINOR, null), + new AnalyzedCommit(null, null, null, null, "B1", ChangeCategory.OTHER, null, null, SemVerChangeLevel.MAJOR, null) ) ); } diff --git a/semver-analyzer-conventional/src/main/java/io/github/sam42r/semver/analyzer/ConventionalCommitAnalyzer.java b/semver-analyzer-conventional/src/main/java/io/github/sam42r/semver/analyzer/ConventionalCommitAnalyzer.java index 7750804..b493840 100644 --- a/semver-analyzer-conventional/src/main/java/io/github/sam42r/semver/analyzer/ConventionalCommitAnalyzer.java +++ b/semver-analyzer-conventional/src/main/java/io/github/sam42r/semver/analyzer/ConventionalCommitAnalyzer.java @@ -1,10 +1,10 @@ package io.github.sam42r.semver.analyzer; -import io.github.sam42r.semver.analyzer.model.AnalyzedCommit; -import io.github.sam42r.semver.analyzer.model.ChangeCategory; import io.github.sam42r.semver.analyzer.model.Configuration; -import io.github.sam42r.semver.analyzer.model.SemVerChangeLevel; -import io.github.sam42r.semver.scm.model.Commit; +import io.github.sam42r.semver.model.analyze.AnalyzedCommit; +import io.github.sam42r.semver.model.analyze.ChangeCategory; +import io.github.sam42r.semver.model.analyze.SemVerChangeLevel; +import io.github.sam42r.semver.model.scm.Commit; import lombok.AccessLevel; import lombok.NonNull; import lombok.RequiredArgsConstructor; @@ -41,16 +41,10 @@ public class ConventionalCommitAnalyzer implements CommitAnalyzer { @SuppressWarnings("MismatchedQueryAndUpdateOfStringBuilder") private AnalyzedCommit analyzeCommit(@NonNull Commit commit) { - var analyzedCommitBuilder = AnalyzedCommit.builder() - .id(commit.getId()) - .timestamp(commit.getTimestamp()) - .author(commit.getAuthor()) - .message(commit.getMessage()); - var headerBuilder = new StringBuilder(); var bodyBuilder = new StringBuilder(); var footerBuilder = new StringBuilder(); - try (var reader = new BufferedReader(new StringReader(commit.getMessage()))) { + try (var reader = new BufferedReader(new StringReader(commit.message()))) { String line; int emptyLinesCounter = 0; while ((line = reader.readLine()) != null) { @@ -74,47 +68,44 @@ private AnalyzedCommit analyzeCommit(@NonNull Commit commit) { var header = headerBuilder.toString().trim(); var body = bodyBuilder.toString().trim(); var footer = footerBuilder.toString().trim(); - analyzedCommitBuilder - .header(header) - .body(body) - .footer(footer); - - if (!header.isEmpty()) { - var pattern = Pattern.compile(COMMIT_HEADER_PATTERN); - var matcher = pattern.matcher(header); - if (matcher.find()) { - var type = matcher.group("TYPE"); - var scope = Optional.ofNullable(matcher.group("SCOPE")) - .map(v -> v.replace("(", "")) - .map(v -> v.replace(")", "")) - .orElse(null); - var breaking = Optional.ofNullable(matcher.group("BREAKING")); - var description = matcher.group("DESCRIPTION").replaceFirst(":", "").trim(); - - analyzedCommitBuilder - .type(type) - .scope(scope) - .subject(description) - .category(getCategory(type)) - .level(breaking.isPresent() ? SemVerChangeLevel.MAJOR : getLevel(type)); - } - } - if (footer.contains("BREAKING CHANGE")) { - analyzedCommitBuilder.level(SemVerChangeLevel.MAJOR); + var pattern = Pattern.compile(COMMIT_HEADER_PATTERN); + var matcher = pattern.matcher(header); + + if (header.isEmpty() || !matcher.find()) { + return new AnalyzedCommit(commit, null, null, null, null, null, null, null, null, null); } + var type = matcher.group("TYPE"); + var scope = Optional.ofNullable(matcher.group("SCOPE")) + .map(v -> v.replace("(", "")) + .map(v -> v.replace(")", "")) + .orElse(null); + var breaking = Optional.ofNullable(matcher.group("BREAKING")); + var description = matcher.group("DESCRIPTION").replaceFirst(":", "").trim(); + // TODO search for issues in footer - return analyzedCommitBuilder.build(); + return new AnalyzedCommit( + commit, + header, + body, + footer, + type, + getCategory(type), + scope, + description, + breaking.isPresent() || footer.contains("BREAKING CHANGE") ? SemVerChangeLevel.MAJOR : getLevel(type), + null + ); } private ChangeCategory getCategory(String type) { if (type != null) { return configuration.getItems().stream() - .filter(v -> v.getType().equals(type)) + .filter(v -> v.type().equals(type)) .findAny() - .map(AnalyzedCommit::getCategory) + .map(AnalyzedCommit::category) .orElse(ChangeCategory.OTHER); } return ChangeCategory.OTHER; @@ -123,9 +114,9 @@ private ChangeCategory getCategory(String type) { private SemVerChangeLevel getLevel(String type) { if (type != null) { return configuration.getItems().stream() - .filter(v -> v.getType().equals(type)) + .filter(v -> v.type().equals(type)) .findAny() - .map(AnalyzedCommit::getLevel) + .map(AnalyzedCommit::level) .orElse(SemVerChangeLevel.NONE); } return SemVerChangeLevel.NONE; diff --git a/semver-analyzer-conventional/src/test/java/io/github/sam42r/semver/analyzer/ConventionalCommitAnalyzerTest.java b/semver-analyzer-conventional/src/test/java/io/github/sam42r/semver/analyzer/ConventionalCommitAnalyzerTest.java index 63c170c..ff8af23 100644 --- a/semver-analyzer-conventional/src/test/java/io/github/sam42r/semver/analyzer/ConventionalCommitAnalyzerTest.java +++ b/semver-analyzer-conventional/src/test/java/io/github/sam42r/semver/analyzer/ConventionalCommitAnalyzerTest.java @@ -1,9 +1,9 @@ package io.github.sam42r.semver.analyzer; -import io.github.sam42r.semver.analyzer.model.AnalyzedCommit; -import io.github.sam42r.semver.analyzer.model.ChangeCategory; -import io.github.sam42r.semver.analyzer.model.SemVerChangeLevel; -import io.github.sam42r.semver.scm.model.Commit; +import io.github.sam42r.semver.model.analyze.AnalyzedCommit; +import io.github.sam42r.semver.model.analyze.ChangeCategory; +import io.github.sam42r.semver.model.analyze.SemVerChangeLevel; +import io.github.sam42r.semver.model.scm.Commit; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -24,103 +24,85 @@ void setup() { @Test void shouldFindFix() { var actual = uut.analyzeCommits(List.of( - Commit.builder() - .id("42") - .timestamp(Instant.EPOCH) - .author("JUnit") - .message(""" + new Commit("42", Instant.EPOCH, "JUnit", + """ fix(scm): set clean commit message - + * added scope for commit messages - + refs #42 """) - .build() )); assertThat(actual).containsExactly( - AnalyzedCommit.builder() - .id("42") - .timestamp(Instant.EPOCH) - .author("JUnit") - .message(""" - fix(scm): set clean commit message - - * added scope for commit messages - - refs #42 - """) - .category(ChangeCategory.FIXED) - .level(SemVerChangeLevel.PATCH) - .header("fix(scm): set clean commit message") - .body("* added scope for commit messages") - .footer("refs #42") - .type("fix") - .scope("scm") - .subject("set clean commit message") - .build() + new AnalyzedCommit( + new Commit("42", Instant.EPOCH, "JUnit", + """ + fix(scm): set clean commit message + + * added scope for commit messages + + refs #42 + """), + "fix(scm): set clean commit message", + "* added scope for commit messages", + "refs #42", + "fix", + ChangeCategory.FIXED, + "scm", + "set clean commit message", + SemVerChangeLevel.PATCH, + null) ); } @Test void shouldFindBreakingChanges() { var actual = uut.analyzeCommits(List.of( - Commit.builder() - .id("42") - .timestamp(Instant.EPOCH) - .author("JUnit") - .message(""" + new Commit("42", Instant.EPOCH, "JUnit", + """ fix(scm): set clean commit message - + * added scope for commit messages BREAKING CHANGE: breaks everything refs #42 - """) - .build(), - Commit.builder() - .id("42") - .timestamp(Instant.EPOCH) - .author("JUnit") - .message("fix(scm)!: set clean commit message") - .build() + """), + new Commit("42", Instant.EPOCH, "JUnit", + "fix(scm)!: set clean commit message") )); assertThat(actual).containsExactly( - AnalyzedCommit.builder() - .id("42") - .timestamp(Instant.EPOCH) - .author("JUnit") - .message(""" - fix(scm): set clean commit message - - * added scope for commit messages - - refs #42 - """) - .category(ChangeCategory.FIXED) - .level(SemVerChangeLevel.MAJOR) - .header("fix(scm): set clean commit message") - .body("* added scope for commit messages") - .footer("BREAKING CHANGE: breaks everything%srefs #42".formatted(System.lineSeparator())) - .type("fix") - .scope("scm") - .subject("set clean commit message") - .build(), - AnalyzedCommit.builder() - .id("42") - .timestamp(Instant.EPOCH) - .author("JUnit") - .message("") - .category(ChangeCategory.FIXED) - .level(SemVerChangeLevel.MAJOR) - .header("fix(scm)!: set clean commit message") - .body("") - .footer("") - .type("fix") - .scope("scm") - .subject("set clean commit message") - .build() + new AnalyzedCommit( + new Commit("42", Instant.EPOCH, "JUnit", + """ + fix(scm): set clean commit message + + * added scope for commit messages + + BREAKING CHANGE: breaks everything + refs #42 + """), + "fix(scm): set clean commit message", + "* added scope for commit messages", + "BREAKING CHANGE: breaks everything%srefs #42".formatted(System.lineSeparator()), + "fix", + ChangeCategory.FIXED, + "scm", + "set clean commit message", + SemVerChangeLevel.MAJOR, + null), + new AnalyzedCommit( + new Commit("42", Instant.EPOCH, "JUnit", "fix(scm)!: set clean commit message"), + "fix(scm)!: set clean commit message", + "", + "", + "fix", + ChangeCategory.FIXED, + "scm", + "set clean commit message", + SemVerChangeLevel.MAJOR, + null) ); } } diff --git a/semver-analyzer-gitmoji/src/main/java/io/github/sam42r/semver/analyzer/GitMojiCommitAnalyzer.java b/semver-analyzer-gitmoji/src/main/java/io/github/sam42r/semver/analyzer/GitMojiCommitAnalyzer.java index 34a871e..56bed3d 100644 --- a/semver-analyzer-gitmoji/src/main/java/io/github/sam42r/semver/analyzer/GitMojiCommitAnalyzer.java +++ b/semver-analyzer-gitmoji/src/main/java/io/github/sam42r/semver/analyzer/GitMojiCommitAnalyzer.java @@ -1,14 +1,16 @@ package io.github.sam42r.semver.analyzer; -import io.github.sam42r.semver.analyzer.model.AnalyzedCommit; -import io.github.sam42r.semver.analyzer.model.ChangeCategory; import io.github.sam42r.semver.analyzer.model.Configuration; -import io.github.sam42r.semver.analyzer.model.SemVerChangeLevel; -import io.github.sam42r.semver.scm.model.Commit; +import io.github.sam42r.semver.model.analyze.AnalyzedCommit; +import io.github.sam42r.semver.model.analyze.ChangeCategory; +import io.github.sam42r.semver.model.analyze.Issue; +import io.github.sam42r.semver.model.analyze.SemVerChangeLevel; +import io.github.sam42r.semver.model.scm.Commit; import lombok.AccessLevel; import lombok.NonNull; import lombok.RequiredArgsConstructor; +import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.regex.Pattern; @@ -35,48 +37,47 @@ public class GitMojiCommitAnalyzer implements CommitAnalyzer { } private AnalyzedCommit analyzeCommit(@NonNull Commit commit) { - var analyzedCommitBuilder = AnalyzedCommit.builder() - .id(commit.getId()) - .timestamp(commit.getTimestamp()) - .author(commit.getAuthor()) - .header(commit.getMessage().trim()) - .message(commit.getMessage()); + var pattern = Pattern.compile(COMMIT_MESSAGE_PATTERN); + var matcher = pattern.matcher(commit.message()); - if (commit.getMessage().startsWith(":")) { - var pattern = Pattern.compile(COMMIT_MESSAGE_PATTERN); - var matcher = pattern.matcher(commit.getMessage()); - if (matcher.find()) { - var intention = matcher.group("INTENTION"); - var scope = Optional.ofNullable(matcher.group("SCOPE")) - .map(v -> v.replace("(", "")) - .map(v -> v.replace(")", "")) - .map(v -> v.replace(":", "")) - .map(String::trim) - .orElse(null); - var message = matcher.group("MESSAGE").trim(); - var ref = Optional.ofNullable(matcher.group("REF")) - .map(v -> v.replace("#", "")) - .map(String::trim) - .map(List::of) - .orElse(null); - analyzedCommitBuilder - .type(intention) - .scope(scope) - .subject(message) - .issues(ref) - .category(getCategory(intention)) - .level(getLevel(intention)); - } + if (!commit.message().startsWith(":") || !matcher.find()) { + return new AnalyzedCommit(commit, null, null, null, null, null, null, null, null, null); } - return analyzedCommitBuilder.build(); + + var intention = matcher.group("INTENTION"); + var scope = Optional.ofNullable(matcher.group("SCOPE")) + .map(v -> v.replace("(", "")) + .map(v -> v.replace(")", "")) + .map(v -> v.replace(":", "")) + .map(String::trim) + .orElse(null); + var message = matcher.group("MESSAGE").trim(); + var ref = Optional.ofNullable(matcher.group("REF")) + .map(v -> v.replace("#", "")) + .map(String::trim) + .map(List::of) + .orElse(Collections.emptyList()); + + return new AnalyzedCommit( + commit, + commit.message(), + null, + null, + intention, + getCategory(intention), + scope, + message, + getLevel(intention), + ref.stream().map(v -> new Issue(v, "")).toList() + ); } private ChangeCategory getCategory(String intention) { if (intention != null) { return configuration.getItems().stream() - .filter(v -> v.getType().equals(intention)) + .filter(v -> v.type().equals(intention)) .findAny() - .map(AnalyzedCommit::getCategory) + .map(AnalyzedCommit::category) .orElse(ChangeCategory.OTHER); } return ChangeCategory.OTHER; @@ -85,9 +86,9 @@ private ChangeCategory getCategory(String intention) { private SemVerChangeLevel getLevel(String intention) { if (intention != null) { return configuration.getItems().stream() - .filter(v -> v.getType().equals(intention)) + .filter(v -> v.type().equals(intention)) .findAny() - .map(AnalyzedCommit::getLevel) + .map(AnalyzedCommit::level) .orElse(SemVerChangeLevel.NONE); } return SemVerChangeLevel.NONE; diff --git a/semver-analyzer-gitmoji/src/test/java/io/github/sam42r/semver/analyzer/GitMojiCommitAnalyzerTest.java b/semver-analyzer-gitmoji/src/test/java/io/github/sam42r/semver/analyzer/GitMojiCommitAnalyzerTest.java index e1886db..e758bd5 100644 --- a/semver-analyzer-gitmoji/src/test/java/io/github/sam42r/semver/analyzer/GitMojiCommitAnalyzerTest.java +++ b/semver-analyzer-gitmoji/src/test/java/io/github/sam42r/semver/analyzer/GitMojiCommitAnalyzerTest.java @@ -1,9 +1,10 @@ package io.github.sam42r.semver.analyzer; -import io.github.sam42r.semver.analyzer.model.AnalyzedCommit; -import io.github.sam42r.semver.analyzer.model.ChangeCategory; -import io.github.sam42r.semver.analyzer.model.SemVerChangeLevel; -import io.github.sam42r.semver.scm.model.Commit; +import io.github.sam42r.semver.model.analyze.AnalyzedCommit; +import io.github.sam42r.semver.model.analyze.ChangeCategory; +import io.github.sam42r.semver.model.analyze.Issue; +import io.github.sam42r.semver.model.analyze.SemVerChangeLevel; +import io.github.sam42r.semver.model.scm.Commit; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -24,99 +25,75 @@ void setup() { @Test void shouldFindFixAndFeat() { var actual = uut.analyzeCommits(List.of( - Commit.builder() - .id("42.1") - .timestamp(Instant.EPOCH) - .author("JUnit") - .message(":bug: (scm) set clean commit message #42") - .build(), - Commit.builder() - .id("42.2") - .timestamp(Instant.EPOCH) - .author("JUnit") - .message(":sparkles: add awesome things") - .build() + new Commit("42.1", Instant.EPOCH, "JUnit", ":bug: (scm) set clean commit message #42"), + new Commit("42.2", Instant.EPOCH, "JUnit", ":sparkles: add awesome things") )); assertThat(actual).containsExactlyInAnyOrder( - AnalyzedCommit.builder() - .id("42.1") - .timestamp(Instant.EPOCH) - .author("JUnit") - .header(":bug: (scm) set clean commit message #42") - .message(":bug: (scm) set clean commit message #42") - .type(":bug:") - .scope("scm") - .subject("set clean commit message") - .issues(List.of("42")) - .category(ChangeCategory.FIXED) - .level(SemVerChangeLevel.PATCH) - .build(), - AnalyzedCommit.builder() - .id("42.2") - .timestamp(Instant.EPOCH) - .author("JUnit") - .header(":sparkles: add awesome things") - .message(":sparkles: add awesome things") - .type(":sparkles:") - .subject("add awesome things") - .category(ChangeCategory.ADDED) - .level(SemVerChangeLevel.MINOR) - .build() + new AnalyzedCommit( + new Commit("42.1", Instant.EPOCH, "JUnit", ":bug: (scm) set clean commit message #42"), + ":bug: (scm) set clean commit message #42", + null, + null, + ":bug:", + ChangeCategory.FIXED, + "scm", + "set clean commit message", + SemVerChangeLevel.PATCH, + List.of(new Issue("42", ""))), + new AnalyzedCommit( + new Commit("42.2", Instant.EPOCH, "JUnit", ":sparkles: add awesome things"), + ":sparkles: add awesome things", + null, + null, + ":sparkles:", + ChangeCategory.ADDED, + null, + "add awesome things", + SemVerChangeLevel.MINOR, + null) ); } @Test void shouldFindBreaking() { var actual = uut.analyzeCommits(List.of( - Commit.builder() - .id("42.3") - .timestamp(Instant.EPOCH) - .author("JUnit") - .message(":boom: (void): break some glass #42") - .build() + new Commit("42.3", Instant.EPOCH, "JUnit", ":boom: (void): break some glass #42") )); assertThat(actual).containsExactly( - AnalyzedCommit.builder() - .id("42.3") - .timestamp(Instant.EPOCH) - .author("JUnit") - .header(":boom: (void): break some glass #42") - .message(":boom: (void): break some glass #42") - .type(":boom:") - .scope("void") - .subject("break some glass") - .issues(List.of("42")) - .category(ChangeCategory.ADDED) - .level(SemVerChangeLevel.MAJOR) - .build() + new AnalyzedCommit( + new Commit("42.3", Instant.EPOCH, "JUnit", ":boom: (void): break some glass #42"), + ":boom: (void): break some glass #42", + null, + null, + ":boom:", + ChangeCategory.ADDED, + "void", + "break some glass", + SemVerChangeLevel.MAJOR, + List.of(new Issue("42",""))) ); } @Test void shouldFindOther() { var actual = uut.analyzeCommits(List.of( - Commit.builder() - .id("42.4") - .timestamp(Instant.EPOCH) - .author("JUnit") - .message(":white_check_mark: add test for something") - .build() + new Commit("42.4", Instant.EPOCH, "JUnit", ":white_check_mark: add test for something") )); assertThat(actual).containsExactly( - AnalyzedCommit.builder() - .id("42.4") - .timestamp(Instant.EPOCH) - .author("JUnit") - .header(":white_check_mark: add test for something") - .message(":white_check_mark: add test for something") - .type(":white_check_mark:") - .subject("add test for something") - .category(ChangeCategory.OTHER) - .level(SemVerChangeLevel.NONE) - .build() + new AnalyzedCommit( + new Commit("42.4", Instant.EPOCH, "JUnit", ":white_check_mark: add test for something"), + ":white_check_mark: add test for something", + null, + null, + ":white_check_mark:", + ChangeCategory.OTHER, + null, + "add test for something", + SemVerChangeLevel.NONE, + null) ); } } diff --git a/semver-changelog-api/src/main/java/io/github/sam42r/semver/changelog/ChangelogRenderer.java b/semver-changelog-api/src/main/java/io/github/sam42r/semver/changelog/ChangelogRenderer.java index d21d5f3..fda4cff 100644 --- a/semver-changelog-api/src/main/java/io/github/sam42r/semver/changelog/ChangelogRenderer.java +++ b/semver-changelog-api/src/main/java/io/github/sam42r/semver/changelog/ChangelogRenderer.java @@ -1,7 +1,7 @@ package io.github.sam42r.semver.changelog; -import io.github.sam42r.semver.analyzer.model.AnalyzedCommit; -import io.github.sam42r.semver.changelog.model.VersionInfo; +import io.github.sam42r.semver.model.changelog.VersionInfo; +import io.github.sam42r.semver.model.analyze.AnalyzedCommit; import lombok.NonNull; import java.io.InputStream; diff --git a/semver-changelog-html/src/main/java/io/github/sam42r/semver/changelog/HtmlRenderer.java b/semver-changelog-html/src/main/java/io/github/sam42r/semver/changelog/HtmlRenderer.java index 0b80a0e..3a485d7 100644 --- a/semver-changelog-html/src/main/java/io/github/sam42r/semver/changelog/HtmlRenderer.java +++ b/semver-changelog-html/src/main/java/io/github/sam42r/semver/changelog/HtmlRenderer.java @@ -1,7 +1,7 @@ package io.github.sam42r.semver.changelog; -import io.github.sam42r.semver.analyzer.model.AnalyzedCommit; -import io.github.sam42r.semver.changelog.model.VersionInfo; +import io.github.sam42r.semver.model.changelog.VersionInfo; +import io.github.sam42r.semver.model.analyze.AnalyzedCommit; import lombok.NonNull; import lombok.RequiredArgsConstructor; import org.apache.commons.codec.digest.DigestUtils; diff --git a/semver-changelog-html/src/test/java/io/github/sam42r/semver/changelog/HtmlRendererTest.java b/semver-changelog-html/src/test/java/io/github/sam42r/semver/changelog/HtmlRendererTest.java index e885cee..b502c0b 100644 --- a/semver-changelog-html/src/test/java/io/github/sam42r/semver/changelog/HtmlRendererTest.java +++ b/semver-changelog-html/src/test/java/io/github/sam42r/semver/changelog/HtmlRendererTest.java @@ -1,8 +1,11 @@ package io.github.sam42r.semver.changelog; -import io.github.sam42r.semver.analyzer.model.AnalyzedCommit; -import io.github.sam42r.semver.analyzer.model.ChangeCategory; -import io.github.sam42r.semver.changelog.model.VersionInfo; +import io.github.sam42r.semver.model.analyze.Issue; +import io.github.sam42r.semver.model.changelog.VersionInfo; +import io.github.sam42r.semver.model.analyze.AnalyzedCommit; +import io.github.sam42r.semver.model.analyze.ChangeCategory; +import io.github.sam42r.semver.model.analyze.SemVerChangeLevel; +import io.github.sam42r.semver.model.scm.Commit; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; @@ -69,33 +72,63 @@ private VersionInfo release(String version) { private List analyzedCommits() { return List.of( - AnalyzedCommit.builder() - .id("42") - .timestamp(Instant.EPOCH) - .author("JUnit") - .header("feat(scm): Lorem ipsum") - .body("* Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam") - .footer("refs #42") - .category(ChangeCategory.ADDED) - .build(), - AnalyzedCommit.builder() - .id("42") - .timestamp(Instant.EPOCH) - .author("JUnit") - .header("fix(scm): Lorem ipsum") - .body("* Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam") - .footer("refs #42") - .category(ChangeCategory.FIXED) - .build(), - AnalyzedCommit.builder() - .id("42") - .timestamp(Instant.EPOCH) - .author("JUnit") - .header("chore(scm): Lorem ipsum") - .body("* Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam") - .footer("refs #42") - .category(ChangeCategory.CHANGED) - .build() + new AnalyzedCommit( + new Commit("42", Instant.EPOCH, "JUnit", + """ + feat(scm): Lorem ipsum + + * Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam + + refs #42 + """), + "feat(scm): Lorem ipsum", + "* Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam", + "refs #42", + "feat", + ChangeCategory.ADDED, + "scm", + "Lorem ipsum", + SemVerChangeLevel.MINOR, + null + ), + new AnalyzedCommit( + new Commit("42", Instant.EPOCH, "JUnit", + """ + fix(scm): Lorem ipsum + + * Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam + + refs #42 + """), + "fix(scm): Lorem ipsum", + "* Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam", + "refs #42", + "fix", + ChangeCategory.FIXED, + "scm", + "Lorem ipsum", + SemVerChangeLevel.PATCH, + null + ), + new AnalyzedCommit( + new Commit("42", Instant.EPOCH, "JUnit", + """ + chore(scm): Lorem ipsum + + * Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam + + refs #42 + """), + "chore(scm): Lorem ipsum", + "* Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam", + "refs #42", + "chore", + ChangeCategory.CHANGED, + "scm", + "Lorem ipsum", + SemVerChangeLevel.NONE, + List.of(new Issue("42", "https://junit.org/test/42")) + ) ); } } diff --git a/semver-changelog-markup/src/main/java/io/github/sam42r/semver/changelog/MarkupRenderer.java b/semver-changelog-markup/src/main/java/io/github/sam42r/semver/changelog/MarkupRenderer.java index 16e7f86..1fab652 100644 --- a/semver-changelog-markup/src/main/java/io/github/sam42r/semver/changelog/MarkupRenderer.java +++ b/semver-changelog-markup/src/main/java/io/github/sam42r/semver/changelog/MarkupRenderer.java @@ -1,9 +1,9 @@ package io.github.sam42r.semver.changelog; import com.github.mustachejava.DefaultMustacheFactory; -import io.github.sam42r.semver.analyzer.model.AnalyzedCommit; -import io.github.sam42r.semver.analyzer.model.ChangeCategory; -import io.github.sam42r.semver.changelog.model.VersionInfo; +import io.github.sam42r.semver.model.changelog.VersionInfo; +import io.github.sam42r.semver.model.analyze.AnalyzedCommit; +import io.github.sam42r.semver.model.analyze.ChangeCategory; import lombok.NonNull; import lombok.RequiredArgsConstructor; import org.apache.commons.codec.digest.DigestUtils; @@ -55,7 +55,7 @@ public class MarkupRenderer implements ChangelogRenderer { ) { var categorizedCommits = analyzedCommits.stream() .collect(Collectors.toMap( - AnalyzedCommit::getCategory, + AnalyzedCommit::category, List::of, (v1, v2) -> Stream.of(v1, v2).flatMap(List::stream).toList() )); diff --git a/semver-changelog-markup/src/test/java/io/github/sam42r/semver/changelog/MarkupRendererTest.java b/semver-changelog-markup/src/test/java/io/github/sam42r/semver/changelog/MarkupRendererTest.java index e38191c..2195d26 100644 --- a/semver-changelog-markup/src/test/java/io/github/sam42r/semver/changelog/MarkupRendererTest.java +++ b/semver-changelog-markup/src/test/java/io/github/sam42r/semver/changelog/MarkupRendererTest.java @@ -1,8 +1,11 @@ package io.github.sam42r.semver.changelog; -import io.github.sam42r.semver.analyzer.model.AnalyzedCommit; -import io.github.sam42r.semver.analyzer.model.ChangeCategory; -import io.github.sam42r.semver.changelog.model.VersionInfo; +import io.github.sam42r.semver.model.analyze.AnalyzedCommit; +import io.github.sam42r.semver.model.analyze.ChangeCategory; +import io.github.sam42r.semver.model.analyze.Issue; +import io.github.sam42r.semver.model.analyze.SemVerChangeLevel; +import io.github.sam42r.semver.model.changelog.VersionInfo; +import io.github.sam42r.semver.model.scm.Commit; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -50,15 +53,15 @@ void shouldUpdateChangelog(@TempDir Path tempDir) throws IOException { changelog, """ # Changelog - + header text - + - + ## v0.9.0 - 2024-01-01 - + ## Disclaimer - + footer text """, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING @@ -81,15 +84,25 @@ private VersionInfo release(String version) { private List analyzedCommits() { return List.of( - AnalyzedCommit.builder() - .id("42") - .timestamp(Instant.EPOCH) - .author("JUnit") - .header("fix(scm): set clean commit message") - .body("* added scope for commit messages") - .footer("refs #42") - .category(ChangeCategory.FIXED) - .build() + new AnalyzedCommit( + new Commit("42", Instant.EPOCH, "JUnit", + """ + fix(scm): set clean commit message + + * added scope for commit messages + + refs #42 + """), + "fix(scm): set clean commit message", + "* added scope for commit messages", + "refs #42", + "fix", + ChangeCategory.FIXED, + "scm", + "set clean commit message", + SemVerChangeLevel.PATCH, + List.of(new Issue("42", "https://junit.org/test/42")) + ) ); } } diff --git a/semver-maven-model/pom.xml b/semver-maven-model/pom.xml new file mode 100644 index 0000000..3b274f6 --- /dev/null +++ b/semver-maven-model/pom.xml @@ -0,0 +1,41 @@ + + + 4.0.0 + + + io.github.sam42r + semver-parent + 1.8.0 + + + semver-maven-model + ${project.groupId}:${project.artifactId} + + + 19 + 19 + UTF-8 + + + + + org.projectlombok + lombok + provided + + + + org.junit.jupiter + junit-jupiter + test + + + org.assertj + assertj-core + test + + + + \ No newline at end of file diff --git a/semver-maven-model/src/main/java/io/github/sam42r/semver/model/analyze/AnalyzedCommit.java b/semver-maven-model/src/main/java/io/github/sam42r/semver/model/analyze/AnalyzedCommit.java new file mode 100644 index 0000000..5764dfa --- /dev/null +++ b/semver-maven-model/src/main/java/io/github/sam42r/semver/model/analyze/AnalyzedCommit.java @@ -0,0 +1,26 @@ +package io.github.sam42r.semver.model.analyze; + +import io.github.sam42r.semver.model.scm.Commit; + +import java.util.List; +import java.util.function.Predicate; + +public record AnalyzedCommit( + Commit commit, + String header, + String body, + String footer, + String type, + ChangeCategory category, + String scope, + String subject, + SemVerChangeLevel level, + List issues +) { + public static final Predicate isBugfix = analyzedCommit -> + SemVerChangeLevel.PATCH.equals(analyzedCommit.level()); + public static final Predicate isFeature = analyzedCommit -> + SemVerChangeLevel.MINOR.equals(analyzedCommit.level()); + public static final Predicate isBreaking = analyzedCommit -> + SemVerChangeLevel.MAJOR.equals(analyzedCommit.level()); +} diff --git a/semver-analyzer-api/src/main/java/io/github/sam42r/semver/analyzer/model/ChangeCategory.java b/semver-maven-model/src/main/java/io/github/sam42r/semver/model/analyze/ChangeCategory.java similarity index 66% rename from semver-analyzer-api/src/main/java/io/github/sam42r/semver/analyzer/model/ChangeCategory.java rename to semver-maven-model/src/main/java/io/github/sam42r/semver/model/analyze/ChangeCategory.java index 28c26e0..5949a8d 100644 --- a/semver-analyzer-api/src/main/java/io/github/sam42r/semver/analyzer/model/ChangeCategory.java +++ b/semver-maven-model/src/main/java/io/github/sam42r/semver/model/analyze/ChangeCategory.java @@ -1,4 +1,4 @@ -package io.github.sam42r.semver.analyzer.model; +package io.github.sam42r.semver.model.analyze; public enum ChangeCategory { ADDED, CHANGED, DEPRECATED, REMOVED, FIXED, SECURITY, OTHER; diff --git a/semver-maven-model/src/main/java/io/github/sam42r/semver/model/analyze/Issue.java b/semver-maven-model/src/main/java/io/github/sam42r/semver/model/analyze/Issue.java new file mode 100644 index 0000000..a3801fc --- /dev/null +++ b/semver-maven-model/src/main/java/io/github/sam42r/semver/model/analyze/Issue.java @@ -0,0 +1,4 @@ +package io.github.sam42r.semver.model.analyze; + +public record Issue(String id, String url) { +} diff --git a/semver-analyzer-api/src/main/java/io/github/sam42r/semver/analyzer/model/SemVerChangeLevel.java b/semver-maven-model/src/main/java/io/github/sam42r/semver/model/analyze/SemVerChangeLevel.java similarity index 88% rename from semver-analyzer-api/src/main/java/io/github/sam42r/semver/analyzer/model/SemVerChangeLevel.java rename to semver-maven-model/src/main/java/io/github/sam42r/semver/model/analyze/SemVerChangeLevel.java index d1e7503..49e0ec7 100644 --- a/semver-analyzer-api/src/main/java/io/github/sam42r/semver/analyzer/model/SemVerChangeLevel.java +++ b/semver-maven-model/src/main/java/io/github/sam42r/semver/model/analyze/SemVerChangeLevel.java @@ -1,4 +1,4 @@ -package io.github.sam42r.semver.analyzer.model; +package io.github.sam42r.semver.model.analyze; /** * The semantic versioning change level. diff --git a/semver-changelog-api/src/main/java/io/github/sam42r/semver/changelog/model/VersionInfo.java b/semver-maven-model/src/main/java/io/github/sam42r/semver/model/changelog/VersionInfo.java similarity index 60% rename from semver-changelog-api/src/main/java/io/github/sam42r/semver/changelog/model/VersionInfo.java rename to semver-maven-model/src/main/java/io/github/sam42r/semver/model/changelog/VersionInfo.java index 8291cfe..7040066 100644 --- a/semver-changelog-api/src/main/java/io/github/sam42r/semver/changelog/model/VersionInfo.java +++ b/semver-maven-model/src/main/java/io/github/sam42r/semver/model/changelog/VersionInfo.java @@ -1,4 +1,4 @@ -package io.github.sam42r.semver.changelog.model; +package io.github.sam42r.semver.model.changelog; public record VersionInfo(String version, String date, String message) { } diff --git a/semver-maven-model/src/main/java/io/github/sam42r/semver/model/release/ReleaseInfo.java b/semver-maven-model/src/main/java/io/github/sam42r/semver/model/release/ReleaseInfo.java new file mode 100644 index 0000000..eea7b1f --- /dev/null +++ b/semver-maven-model/src/main/java/io/github/sam42r/semver/model/release/ReleaseInfo.java @@ -0,0 +1,6 @@ +package io.github.sam42r.semver.model.release; + +import java.time.LocalDateTime; + +public record ReleaseInfo(String tagName, String name, String description, LocalDateTime time) { +} diff --git a/semver-maven-model/src/main/java/io/github/sam42r/semver/model/scm/Commit.java b/semver-maven-model/src/main/java/io/github/sam42r/semver/model/scm/Commit.java new file mode 100644 index 0000000..93365ff --- /dev/null +++ b/semver-maven-model/src/main/java/io/github/sam42r/semver/model/scm/Commit.java @@ -0,0 +1,6 @@ +package io.github.sam42r.semver.model.scm; + +import java.time.Instant; + +public record Commit(String id, Instant timestamp, String author, String message) { +} diff --git a/semver-scm-api/src/main/java/io/github/sam42r/semver/scm/util/RemoteUtil.java b/semver-maven-model/src/main/java/io/github/sam42r/semver/model/scm/Remote.java similarity index 50% rename from semver-scm-api/src/main/java/io/github/sam42r/semver/scm/util/RemoteUtil.java rename to semver-maven-model/src/main/java/io/github/sam42r/semver/model/scm/Remote.java index e968e10..1cfb7a5 100644 --- a/semver-scm-api/src/main/java/io/github/sam42r/semver/scm/util/RemoteUtil.java +++ b/semver-maven-model/src/main/java/io/github/sam42r/semver/model/scm/Remote.java @@ -1,20 +1,14 @@ -package io.github.sam42r.semver.scm.util; - -import io.github.sam42r.semver.scm.model.Remote; -import lombok.AccessLevel; -import lombok.NoArgsConstructor; -import lombok.NonNull; +package io.github.sam42r.semver.model.scm; import java.net.URI; -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public final class RemoteUtil { +public record Remote(String url, String scheme, String host, String group, String project) { - public static @NonNull Remote parseUrl(@NonNull String url) { - return url.startsWith("http") ? parseHttpRemoteUrl(url) : parseSshRemoteUrl(url); + public static Remote of(String url) { + return url.startsWith("http") ? parseHttpUrl(url) : parseSshUrl(url); } - private static @NonNull Remote parseHttpRemoteUrl(@NonNull String url) { + private static Remote parseHttpUrl(String url) { var uri = URI.create(url); var lastSlashIndex = uri.getPath().lastIndexOf("/"); @@ -28,16 +22,10 @@ public final class RemoteUtil { var hostAndPort = uri.getHost().concat(uri.getPort() > 0 ? ":%d".formatted(uri.getPort()) : ""); - return Remote.builder() - .url(url) - .scheme(uri.getScheme()) - .host(hostAndPort) - .group(group) - .project(project) - .build(); + return new Remote(url, uri.getScheme(), hostAndPort, group, project); } - private static @NonNull Remote parseSshRemoteUrl(@NonNull String url) { + private static Remote parseSshUrl(String url) { var startIndex = url.contains("@") ? url.indexOf("@") + 1 : 0; var lastColonIndex = url.lastIndexOf(":"); @@ -53,12 +41,6 @@ public final class RemoteUtil { project = project.substring(0, project.lastIndexOf(".")); } - return Remote.builder() - .url(url) - .scheme("https") - .host(hostAndPort) - .group(group) - .project(project) - .build(); + return new Remote(url, "https", hostAndPort, group, project); } } diff --git a/semver-maven-model/src/main/java/io/github/sam42r/semver/model/scm/Tag.java b/semver-maven-model/src/main/java/io/github/sam42r/semver/model/scm/Tag.java new file mode 100644 index 0000000..f310b9b --- /dev/null +++ b/semver-maven-model/src/main/java/io/github/sam42r/semver/model/scm/Tag.java @@ -0,0 +1,4 @@ +package io.github.sam42r.semver.model.scm; + +public record Tag(String name, String commitId) { +} diff --git a/semver-maven-model/src/test/java/io/github/sam42r/semver/model/scm/RemoteTest.java b/semver-maven-model/src/test/java/io/github/sam42r/semver/model/scm/RemoteTest.java new file mode 100644 index 0000000..0813a1d --- /dev/null +++ b/semver-maven-model/src/test/java/io/github/sam42r/semver/model/scm/RemoteTest.java @@ -0,0 +1,33 @@ +package io.github.sam42r.semver.model.scm; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.assertj.core.api.Assertions.assertThat; + +class RemoteTest { + + @ParameterizedTest + @ValueSource(strings = { + "git@github.com:JUnit/test.git", + "https://github.com/JUnit/test.git" + }) + void shouldReadRemoteGithub(String url) { + var actual = Remote.of(url); + + assertThat(actual).isEqualTo( + new Remote(url, "https", "github.com", "JUnit", "test")); + } + + @ParameterizedTest + @ValueSource(strings = { + "git@gitlab.local:10022:JUnit/subgroup/test.git", + "https://gitlab.local:10022/JUnit/subgroup/test.git" + }) + void shouldReadRemoteGitlab(String url) { + var actual = Remote.of(url); + + assertThat(actual).isEqualTo( + new Remote(url, "https", "gitlab.local:10022", "JUnit/subgroup", "test")); + } +} diff --git a/semver-maven-plugin/pom.xml b/semver-maven-plugin/pom.xml index 26f3ef4..6d52e34 100644 --- a/semver-maven-plugin/pom.xml +++ b/semver-maven-plugin/pom.xml @@ -22,6 +22,12 @@ + + io.github.sam42r + semver-maven-model + ${project.version} + + io.github.sam42r semver-scm-api diff --git a/semver-maven-plugin/src/main/java/io/github/sam42r/semver/SemanticReleaseMojo.java b/semver-maven-plugin/src/main/java/io/github/sam42r/semver/SemanticReleaseMojo.java index eacb425..f132dad 100644 --- a/semver-maven-plugin/src/main/java/io/github/sam42r/semver/SemanticReleaseMojo.java +++ b/semver-maven-plugin/src/main/java/io/github/sam42r/semver/SemanticReleaseMojo.java @@ -3,19 +3,19 @@ import io.github.sam24r.semver.release.ReleaseException; import io.github.sam24r.semver.release.ReleasePublisher; import io.github.sam24r.semver.release.ReleasePublisherFactory; -import io.github.sam24r.semver.release.model.ReleaseInfo; import io.github.sam42r.semver.analyzer.CommitAnalyzer; import io.github.sam42r.semver.analyzer.CommitAnalyzerFactory; -import io.github.sam42r.semver.analyzer.model.AnalyzedCommit; import io.github.sam42r.semver.changelog.ChangelogRenderer; import io.github.sam42r.semver.changelog.ChangelogRendererFactory; -import io.github.sam42r.semver.changelog.model.VersionInfo; import io.github.sam42r.semver.model.Version; +import io.github.sam42r.semver.model.analyze.AnalyzedCommit; +import io.github.sam42r.semver.model.changelog.VersionInfo; +import io.github.sam42r.semver.model.release.ReleaseInfo; +import io.github.sam42r.semver.model.scm.Commit; +import io.github.sam42r.semver.model.scm.Tag; import io.github.sam42r.semver.scm.SCMException; import io.github.sam42r.semver.scm.SCMProvider; import io.github.sam42r.semver.scm.SCMProviderFactory; -import io.github.sam42r.semver.scm.model.Commit; -import io.github.sam42r.semver.scm.model.Tag; import io.github.sam42r.semver.util.PomHelper; import io.github.sam42r.semver.util.TagVersionComparator; import lombok.NonNull; @@ -236,11 +236,11 @@ private LatestReleaseInfo getLatestRelease(SCMProvider scmProvider) throws MojoE var commits = scmProvider.readCommits(null); var latestTagOpt = tags.max(new TagVersionComparator(tagFormat)); - var latestCommitOpt = latestTagOpt.map(Tag::getCommitId) - .or(() -> commits.min(Comparator.comparing(Commit::getTimestamp)).map(Commit::getId)); + var latestCommitOpt = latestTagOpt.map(Tag::commitId) + .or(() -> commits.min(Comparator.comparing(Commit::timestamp)).map(Commit::id)); return new LatestReleaseInfo( - latestTagOpt.map(Tag::getName), + latestTagOpt.map(Tag::name), latestCommitOpt ); } catch (SCMException e) { @@ -320,16 +320,16 @@ private void notify( var remote = scmProvider.getRemote(); releasePublisher.publish( - remote.getScheme(), - remote.getHost(), - remote.getGroup(), - remote.getProject(), - ReleaseInfo.builder() - .time(LocalDateTime.now().truncatedTo(ChronoUnit.DAYS)) - .tagName(version.toTag()) - .name(version.toString()) - //.description("TODO") - .build() + remote.scheme(), + remote.host(), + remote.group(), + remote.project(), + new ReleaseInfo( + version.toTag(), + version.toString(), + null, // TODO + LocalDateTime.now().truncatedTo(ChronoUnit.DAYS) + ) ); } catch (SCMException | ReleaseException e) { throw new MojoExecutionException(e.getMessage(), e.getCause()); diff --git a/semver-maven-plugin/src/main/java/io/github/sam42r/semver/util/TagVersionComparator.java b/semver-maven-plugin/src/main/java/io/github/sam42r/semver/util/TagVersionComparator.java index 3d9007c..a6ae38d 100644 --- a/semver-maven-plugin/src/main/java/io/github/sam42r/semver/util/TagVersionComparator.java +++ b/semver-maven-plugin/src/main/java/io/github/sam42r/semver/util/TagVersionComparator.java @@ -1,7 +1,7 @@ package io.github.sam42r.semver.util; import io.github.sam42r.semver.model.Version; -import io.github.sam42r.semver.scm.model.Tag; +import io.github.sam42r.semver.model.scm.Tag; import lombok.RequiredArgsConstructor; import java.util.Comparator; @@ -17,8 +17,8 @@ public int compare(Tag o1, Tag o2) { .thenComparingInt(Version::getMinor) .thenComparingInt(Version::getPatch) .compare( - Version.of(o1.getName(), tagFormat), - Version.of(o2.getName(), tagFormat) + Version.of(o1.name(), tagFormat), + Version.of(o2.name(), tagFormat) ); } } diff --git a/semver-maven-plugin/src/test/java/io/github/sam42r/semver/util/BundleHelper.java b/semver-maven-plugin/src/test/java/io/github/sam42r/semver/util/BundleHelper.java index 9723ad0..c80b279 100644 --- a/semver-maven-plugin/src/test/java/io/github/sam42r/semver/util/BundleHelper.java +++ b/semver-maven-plugin/src/test/java/io/github/sam42r/semver/util/BundleHelper.java @@ -13,6 +13,7 @@ public class BundleHelper { private static final String[] MODULES = new String[]{ + "semver-maven-model", "semver-analyzer-api", "semver-analyzer-conventional", "semver-analyzer-gitmoji", diff --git a/semver-maven-plugin/src/test/java/io/github/sam42r/semver/util/TagVersionComparatorTest.java b/semver-maven-plugin/src/test/java/io/github/sam42r/semver/util/TagVersionComparatorTest.java index d8e800e..ab2f167 100644 --- a/semver-maven-plugin/src/test/java/io/github/sam42r/semver/util/TagVersionComparatorTest.java +++ b/semver-maven-plugin/src/test/java/io/github/sam42r/semver/util/TagVersionComparatorTest.java @@ -1,7 +1,7 @@ package io.github.sam42r.semver.util; import io.github.sam42r.semver.model.Version; -import io.github.sam42r.semver.scm.model.Tag; +import io.github.sam42r.semver.model.scm.Tag; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -22,18 +22,18 @@ void setUp() { @Test void shouldGetMax() { var tags = List.of( - Tag.builder().name("v0.10.0").build(), - Tag.builder().name("v0.9.0").build(), - Tag.builder().name("v0.1.0").build(), - Tag.builder().name("v0.0.10").build(), - Tag.builder().name("v0.0.9").build(), - Tag.builder().name("v0.0.1").build() + new Tag("v0.10.0", null), + new Tag("v0.9.0", null), + new Tag("v0.1.0", null), + new Tag("v0.0.10", null), + new Tag("v0.0.9", null), + new Tag("v0.0.1", null) ); - var alphabeticMax = tags.stream().max(Comparator.comparing(Tag::getName)); - assertThat(alphabeticMax).contains(Tag.builder().name("v0.9.0").build()); + var alphabeticMax = tags.stream().max(Comparator.comparing(Tag::name)); + assertThat(alphabeticMax).contains(new Tag("v0.9.0", null)); var semanticVersionMax = tags.stream().max(uut); - assertThat(semanticVersionMax).contains(Tag.builder().name("v0.10.0").build()); + assertThat(semanticVersionMax).contains(new Tag("v0.10.0", null)); } } diff --git a/semver-release-api/pom.xml b/semver-release-api/pom.xml index 8fd16fb..72a4292 100644 --- a/semver-release-api/pom.xml +++ b/semver-release-api/pom.xml @@ -20,6 +20,11 @@ + + io.github.sam42r + semver-maven-model + ${project.version} + org.projectlombok lombok diff --git a/semver-release-api/src/main/java/io/github/sam24r/semver/release/ReleasePublisher.java b/semver-release-api/src/main/java/io/github/sam24r/semver/release/ReleasePublisher.java index 42e4f34..4b2932a 100644 --- a/semver-release-api/src/main/java/io/github/sam24r/semver/release/ReleasePublisher.java +++ b/semver-release-api/src/main/java/io/github/sam24r/semver/release/ReleasePublisher.java @@ -1,6 +1,6 @@ package io.github.sam24r.semver.release; -import io.github.sam24r.semver.release.model.ReleaseInfo; +import io.github.sam42r.semver.model.release.ReleaseInfo; import lombok.NonNull; public interface ReleasePublisher { diff --git a/semver-release-api/src/main/java/io/github/sam24r/semver/release/model/ReleaseInfo.java b/semver-release-api/src/main/java/io/github/sam24r/semver/release/model/ReleaseInfo.java deleted file mode 100644 index 5ae8ace..0000000 --- a/semver-release-api/src/main/java/io/github/sam24r/semver/release/model/ReleaseInfo.java +++ /dev/null @@ -1,20 +0,0 @@ -package io.github.sam24r.semver.release.model; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.time.LocalDateTime; - -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class ReleaseInfo { - - private String tagName; - private String name; - private String description; - private LocalDateTime time; -} diff --git a/semver-release-github/src/main/java/io/github/sam42r/semver/release/DefaultGithubPublisher.java b/semver-release-github/src/main/java/io/github/sam42r/semver/release/DefaultGithubPublisher.java index a250696..8a43751 100644 --- a/semver-release-github/src/main/java/io/github/sam42r/semver/release/DefaultGithubPublisher.java +++ b/semver-release-github/src/main/java/io/github/sam42r/semver/release/DefaultGithubPublisher.java @@ -2,7 +2,7 @@ import io.github.sam24r.semver.release.ReleaseException; import io.github.sam24r.semver.release.ReleasePublisher; -import io.github.sam24r.semver.release.model.ReleaseInfo; +import io.github.sam42r.semver.model.release.ReleaseInfo; import lombok.NonNull; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -39,9 +39,9 @@ public void publish( var repository = gitHub.getRepository("%s/%s".formatted(group, project)); - var release = repository.createRelease(releaseInfo.getTagName()) - .name(releaseInfo.getName()) - .body(releaseInfo.getDescription()) + var release = repository.createRelease(releaseInfo.tagName()) + .name(releaseInfo.name()) + .body(releaseInfo.description()) .create(); log.debug("Released {}", release.getHtmlUrl()); diff --git a/semver-release-github/src/test/java/io/github/sam42r/semver/release/GithubPublisherTest.java b/semver-release-github/src/test/java/io/github/sam42r/semver/release/GithubPublisherTest.java index 33c2c62..10805e5 100644 --- a/semver-release-github/src/test/java/io/github/sam42r/semver/release/GithubPublisherTest.java +++ b/semver-release-github/src/test/java/io/github/sam42r/semver/release/GithubPublisherTest.java @@ -2,7 +2,7 @@ import io.github.sam24r.semver.release.ReleaseException; import io.github.sam24r.semver.release.ReleasePublisher; -import io.github.sam24r.semver.release.model.ReleaseInfo; +import io.github.sam42r.semver.model.release.ReleaseInfo; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -84,12 +84,12 @@ void shouldCreateRelease(MockServerClient client) throws ReleaseException { """) ); - var release = ReleaseInfo.builder() - .tagName("v1.0.0") - .name("v1.0.0") - .description("# Release v1.0.0") - .time(LocalDateTime.now()) - .build(); + var release = new ReleaseInfo( + "v1.0.0", + "v1.0.0", + "# Release v1.0.0", + LocalDateTime.now() + ); uut.publish( "http", @@ -150,12 +150,12 @@ void shouldThrowOnHttp422(MockServerClient client) { .withBody("endpoint has been spammed") ); - var release = ReleaseInfo.builder() - .tagName("v1.0.0") - .name("v1.0.0") - .description("# Release v1.0.0") - .time(LocalDateTime.now()) - .build(); + var release = new ReleaseInfo( + "v1.0.0", + "v1.0.0", + "# Release v1.0.0", + LocalDateTime.now() + ); assertThatThrownBy(() -> uut.publish( "http", @@ -181,7 +181,7 @@ void shouldThrowOnHttp422(MockServerClient client) { "http,localhost:42,JUnit,,project" }) void shouldThrowOnNullValue(String scheme, String instance, String group, String project, String field) { - var releaseInfo = ReleaseInfo.builder().build(); + var releaseInfo = new ReleaseInfo(null,null,null,null); assertThatThrownBy(() -> uut.publish(scheme, instance, group, project, releaseInfo)) .isInstanceOf(NullPointerException.class) .hasMessage("%s is marked non-null but is null".formatted(field)); diff --git a/semver-release-gitlab/src/main/java/io/github/sam42r/semver/release/GitlabPublisher.java b/semver-release-gitlab/src/main/java/io/github/sam42r/semver/release/GitlabPublisher.java index 68ba331..0d583f9 100644 --- a/semver-release-gitlab/src/main/java/io/github/sam42r/semver/release/GitlabPublisher.java +++ b/semver-release-gitlab/src/main/java/io/github/sam42r/semver/release/GitlabPublisher.java @@ -3,7 +3,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.github.sam24r.semver.release.ReleaseException; import io.github.sam24r.semver.release.ReleasePublisher; -import io.github.sam24r.semver.release.model.ReleaseInfo; +import io.github.sam42r.semver.model.release.ReleaseInfo; import io.github.sam42r.semver.release.model.GitlabRelease; import lombok.NonNull; import lombok.RequiredArgsConstructor; @@ -69,9 +69,9 @@ public void publish( private GitlabRelease generatePayload(ReleaseInfo releaseInfo) { return GitlabRelease.builder() - .tagName(releaseInfo.getTagName()) - .name(releaseInfo.getName()) - .description(releaseInfo.getDescription()) + .tagName(releaseInfo.tagName()) + .name(releaseInfo.name()) + .description(releaseInfo.description()) .build(); } } diff --git a/semver-release-gitlab/src/test/java/io/github/sam42r/semver/release/GitlabPublisherTest.java b/semver-release-gitlab/src/test/java/io/github/sam42r/semver/release/GitlabPublisherTest.java index 3822639..0161f2b 100644 --- a/semver-release-gitlab/src/test/java/io/github/sam42r/semver/release/GitlabPublisherTest.java +++ b/semver-release-gitlab/src/test/java/io/github/sam42r/semver/release/GitlabPublisherTest.java @@ -2,7 +2,7 @@ import io.github.sam24r.semver.release.ReleaseException; import io.github.sam24r.semver.release.ReleasePublisher; -import io.github.sam24r.semver.release.model.ReleaseInfo; +import io.github.sam42r.semver.model.release.ReleaseInfo; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -47,12 +47,12 @@ void shouldCreateRelease(MockServerClient client) throws ReleaseException { """) ); - var release = ReleaseInfo.builder() - .tagName("v1.0.0") - .name("v1.0.0") - .description("# Release v1.0.0") - .time(LocalDateTime.now()) - .build(); + var release = new ReleaseInfo( + "v1.0.0", + "v1.0.0", + "# Release v1.0.0", + LocalDateTime.now() + ); uut.publish( "http", @@ -76,12 +76,12 @@ void shouldThrowOnHttp500(MockServerClient client) { .withBody("Internal server error") ); - var release = ReleaseInfo.builder() - .tagName("v1.0.0") - .name("v1.0.0") - .description("# Release v1.0.0") - .time(LocalDateTime.now()) - .build(); + var release = new ReleaseInfo( + "v1.0.0", + "v1.0.0", + "# Release v1.0.0", + LocalDateTime.now() + ); assertThatThrownBy(() -> uut.publish( "http", diff --git a/semver-scm-api/pom.xml b/semver-scm-api/pom.xml index 5b75c5c..e4c841e 100644 --- a/semver-scm-api/pom.xml +++ b/semver-scm-api/pom.xml @@ -20,6 +20,12 @@ + + io.github.sam42r + semver-maven-model + ${project.version} + + org.apache.maven.scm maven-scm-api diff --git a/semver-scm-api/src/main/java/io/github/sam42r/semver/scm/AbstractScmProvider.java b/semver-scm-api/src/main/java/io/github/sam42r/semver/scm/AbstractScmProvider.java index 951d562..726429a 100644 --- a/semver-scm-api/src/main/java/io/github/sam42r/semver/scm/AbstractScmProvider.java +++ b/semver-scm-api/src/main/java/io/github/sam42r/semver/scm/AbstractScmProvider.java @@ -1,9 +1,8 @@ package io.github.sam42r.semver.scm; -import io.github.sam42r.semver.scm.model.Commit; -import io.github.sam42r.semver.scm.model.Remote; -import io.github.sam42r.semver.scm.model.Tag; -import io.github.sam42r.semver.scm.util.RemoteUtil; +import io.github.sam42r.semver.model.scm.Commit; +import io.github.sam42r.semver.model.scm.Remote; +import io.github.sam42r.semver.model.scm.Tag; import lombok.AccessLevel; import lombok.Getter; import lombok.NonNull; @@ -77,12 +76,11 @@ protected AbstractScmProvider( var changeLogScmResult = scmManager.changeLog(changeLogScmRequest); return changeLogScmResult.getChangeLog().getChangeSets().stream() - .map(v -> Commit.builder() - .id(v.getRevision()) - .timestamp(v.getDate().toInstant()) - .author(v.getAuthor()) - .message(v.getComment()) - .build()); + .map(v -> new Commit( + v.getRevision(), + v.getDate().toInstant(), + v.getAuthor(), + v.getComment())); } catch (ScmException e) { throw new SCMException(e); } @@ -98,10 +96,9 @@ protected AbstractScmProvider( return changeLogScmResult.getChangeLog().getChangeSets().stream() .filter(hasTag) - .map(v -> Tag.builder() - .name(v.getTags().get(0)) - .commitId(v.getRevision()) - .build()); + .map(v -> new Tag( + v.getTags().get(0), + v.getRevision())); } catch (ScmException e) { throw new SCMException(e); } @@ -141,7 +138,7 @@ public void addFile(@NonNull Path file) throws SCMException { var tagScmResult = scmManager.tag(repository, new ScmFileSet(path.toFile()), name); assert tagScmResult.isSuccess(); - return readTags().filter(v -> name.equals(v.getName())).findFirst().orElseThrow(); + return readTags().filter(v -> name.equals(v.name())).findFirst().orElseThrow(); } catch (ScmException e) { throw new SCMException(e); } @@ -149,7 +146,7 @@ public void addFile(@NonNull Path file) throws SCMException { @Override public @NonNull Remote getRemote() throws SCMException { - return RemoteUtil.parseUrl(getRemoteUrl().orElseThrow()); + return Remote.of(getRemoteUrl().orElseThrow()); } protected ScmRepository getScmRepository() throws SCMException { diff --git a/semver-scm-api/src/main/java/io/github/sam42r/semver/scm/SCMProvider.java b/semver-scm-api/src/main/java/io/github/sam42r/semver/scm/SCMProvider.java index b9b7112..140217d 100644 --- a/semver-scm-api/src/main/java/io/github/sam42r/semver/scm/SCMProvider.java +++ b/semver-scm-api/src/main/java/io/github/sam42r/semver/scm/SCMProvider.java @@ -1,8 +1,8 @@ package io.github.sam42r.semver.scm; -import io.github.sam42r.semver.scm.model.Commit; -import io.github.sam42r.semver.scm.model.Remote; -import io.github.sam42r.semver.scm.model.Tag; +import io.github.sam42r.semver.model.scm.Commit; +import io.github.sam42r.semver.model.scm.Remote; +import io.github.sam42r.semver.model.scm.Tag; import lombok.NonNull; import java.nio.file.Path; diff --git a/semver-scm-api/src/main/java/io/github/sam42r/semver/scm/model/Commit.java b/semver-scm-api/src/main/java/io/github/sam42r/semver/scm/model/Commit.java deleted file mode 100644 index 735ba00..0000000 --- a/semver-scm-api/src/main/java/io/github/sam42r/semver/scm/model/Commit.java +++ /dev/null @@ -1,20 +0,0 @@ -package io.github.sam42r.semver.scm.model; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -import java.time.Instant; - -@Data -@SuperBuilder -@NoArgsConstructor -@AllArgsConstructor -public class Commit { - - private String id; - private Instant timestamp; - private String author; - private String message; -} diff --git a/semver-scm-api/src/main/java/io/github/sam42r/semver/scm/model/Remote.java b/semver-scm-api/src/main/java/io/github/sam42r/semver/scm/model/Remote.java deleted file mode 100644 index f0498b9..0000000 --- a/semver-scm-api/src/main/java/io/github/sam42r/semver/scm/model/Remote.java +++ /dev/null @@ -1,19 +0,0 @@ -package io.github.sam42r.semver.scm.model; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class Remote { - - private String url; - private String scheme; - private String host; - private String group; - private String project; -} diff --git a/semver-scm-api/src/main/java/io/github/sam42r/semver/scm/model/Tag.java b/semver-scm-api/src/main/java/io/github/sam42r/semver/scm/model/Tag.java deleted file mode 100644 index 15a592a..0000000 --- a/semver-scm-api/src/main/java/io/github/sam42r/semver/scm/model/Tag.java +++ /dev/null @@ -1,16 +0,0 @@ -package io.github.sam42r.semver.scm.model; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class Tag { - - private String name; - private String commitId; -} diff --git a/semver-scm-api/src/test/java/io/github/sam42r/semver/scm/util/RemoteUtilTest.java b/semver-scm-api/src/test/java/io/github/sam42r/semver/scm/util/RemoteUtilTest.java deleted file mode 100644 index de964ec..0000000 --- a/semver-scm-api/src/test/java/io/github/sam42r/semver/scm/util/RemoteUtilTest.java +++ /dev/null @@ -1,44 +0,0 @@ -package io.github.sam42r.semver.scm.util; - -import io.github.sam42r.semver.scm.model.Remote; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; - -import static org.assertj.core.api.Assertions.assertThat; - -class RemoteUtilTest { - - @ParameterizedTest - @ValueSource(strings = { - "git@github.com:JUnit/test.git", - "https://github.com/JUnit/test.git" - }) - void shouldReadRemoteGithub(String url) { - var actual = RemoteUtil.parseUrl(url); - - assertThat(actual).isEqualTo(Remote.builder() - .url(url) - .scheme("https") - .host("github.com") - .group("JUnit") - .project("test") - .build()); - } - - @ParameterizedTest - @ValueSource(strings = { - "git@gitlab.local:10022:JUnit/subgroup/test.git", - "https://gitlab.local:10022/JUnit/subgroup/test.git" - }) - void shouldReadRemoteGitlab(String url) { - var actual = RemoteUtil.parseUrl(url); - - assertThat(actual).isEqualTo(Remote.builder() - .url(url) - .scheme("https") - .host("gitlab.local:10022") - .group("JUnit/subgroup") - .project("test") - .build()); - } -} diff --git a/semver-scm-git/src/main/java/io/github/sam42r/semver/scm/GitProvider.java b/semver-scm-git/src/main/java/io/github/sam42r/semver/scm/GitProvider.java deleted file mode 100644 index d9fafda..0000000 --- a/semver-scm-git/src/main/java/io/github/sam42r/semver/scm/GitProvider.java +++ /dev/null @@ -1,281 +0,0 @@ -package io.github.sam42r.semver.scm; - - -import com.jcraft.jsch.JSch; -import com.jcraft.jsch.JSchException; -import com.jcraft.jsch.Session; -import io.github.sam42r.semver.scm.model.Commit; -import io.github.sam42r.semver.scm.model.Remote; -import io.github.sam42r.semver.scm.model.Tag; -import io.github.sam42r.semver.scm.util.RemoteUtil; -import lombok.AccessLevel; -import lombok.NonNull; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.SystemUtils; -import org.eclipse.jgit.api.Git; -import org.eclipse.jgit.api.errors.GitAPIException; -import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.Ref; -import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.storage.file.FileRepositoryBuilder; -import org.eclipse.jgit.transport.*; -import org.eclipse.jgit.transport.ssh.jsch.JschConfigSessionFactory; -import org.eclipse.jgit.transport.ssh.jsch.OpenSshConfig; - -import java.io.File; -import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.time.Instant; -import java.util.List; -import java.util.Optional; -import java.util.function.Predicate; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; - -/** - * {@link SCMProvider} for git. - * - * @author Sam42R - * @deprecated since switching to Maven-SCM {@link DefaultGitProvider} is used instead - */ -@Slf4j -@RequiredArgsConstructor(access = AccessLevel.PROTECTED) -@Deprecated(since = "1.5.0", forRemoval = true) -public class GitProvider implements SCMProvider { - - private final Path repositoryPath; - private final String username; - private final String password; - - private Repository repository; - - @Override - public @NonNull Stream readCommits(String fromCommitId) throws SCMException { - var repository = getRepository(); - try (var git = new Git(repository)) { - var logCommand = git.log(); - - if (fromCommitId != null) { - logCommand = logCommand.addRange( - ObjectId.fromString(fromCommitId), - git.getRepository().resolve("HEAD")); - } - - return StreamSupport.stream(logCommand.call().spliterator(), false) - .map(v -> Commit.builder() - .id(v.getId().getName()) - .timestamp(Instant.ofEpochSecond(v.getCommitTime())) - .author(v.getAuthorIdent().getName()) - .message(v.getFullMessage()) - .build()); - } catch (IOException | GitAPIException e) { - throw new SCMException(e.getMessage(), e); - } - } - - @Override - public @NonNull Stream readTags() throws SCMException { - var repository = getRepository(); - try (var git = new Git(repository)) { - return git.tagList().call().stream() - .map(v -> Tag.builder() - .name(v.getName().replace("refs/tags/", "")) - .commitId(getObjectId(repository, v).getName()) - .build()); - } catch (GitAPIException e) { - throw new SCMException(e); - } - } - - @Override - public void addFile(@NonNull Path file) throws SCMException { - var repository = getRepository(); - try (var git = new Git(repository)) { - // note: jGit seems to accept slash as file separator only; therefore we have to replace the platform file - // separator to make it run on windows also - git.add().addFilepattern(repositoryPath.relativize(file).toString().replace(File.separatorChar, '/')).call(); - } catch (GitAPIException e) { - throw new SCMException(e); - } - } - - @Override - public @NonNull Commit commit(@NonNull String message) throws SCMException { - var repository = getRepository(); - try (var git = new Git(repository)) { - var commit = git.commit().setMessage(message).call(); - return Commit.builder() - .id(commit.getId().getName()) - .timestamp(Instant.ofEpochSecond(commit.getCommitTime())) - .author(commit.getAuthorIdent().getName()) - .message(commit.getFullMessage()) - .build(); - } catch (GitAPIException e) { - throw new SCMException(e); - } - } - - @Override - public @NonNull Tag tag(@NonNull String name) throws SCMException { - var repository = getRepository(); - try (var git = new Git(repository)) { - var tag = git.tag().setName(name).call(); - return Tag.builder() - .name(tag.getName().replace("refs/tags/", "")) - .commitId(getObjectId(repository, tag).getName()) - .build(); - } catch (GitAPIException e) { - throw new SCMException(e); - } - } - - @Override - public String push(boolean force) throws SCMException { - var repository = getRepository(); - try (var git = new Git(repository)) { - var pushBranchResults = git.push() - .setTransportConfigCallback(this::configureTransport) - .setRemote("origin") - .setForce(force) - .call(); - - var pushTagResults = git.push() - .setTransportConfigCallback(this::configureTransport) - .setRemote("origin") - .setForce(force) - .setPushTags() // pushes tags only - .call(); - - return Stream.concat( - StreamSupport.stream(pushBranchResults.spliterator(), false), - StreamSupport.stream(pushTagResults.spliterator(), false) - ) - .map(PushResult::getMessages) - .collect(Collectors.joining(System.lineSeparator())); - } catch (GitAPIException e) { - throw new SCMException(e); - } - } - - @Override - public @NonNull Remote getRemote() throws SCMException { - var repository = getRepository(); - try (var git = new Git(repository)) { - var remoteConfigs = git.remoteList().call(); - var url = remoteConfigs.stream() - .filter(v -> "origin".equals(v.getName())) - .map(RemoteConfig::getURIs) - .flatMap(List::stream) - .map(URIish::toString) - .findAny() - .orElseThrow(); - return RemoteUtil.parseUrl(url); - } catch (GitAPIException e) { - throw new SCMException(e); - } - } - - private Repository getRepository() throws SCMException { - if (repository == null) { - try { - var gitDirectory = repositoryPath.resolve(".git"); - if (Files.notExists(gitDirectory) || !Files.isDirectory(gitDirectory) || !Files.isReadable(gitDirectory)) { - throw new SCMException("Could not find git repository"); - } - repository = FileRepositoryBuilder.create(gitDirectory.toFile()); - } catch (IOException e) { - throw new SCMException(e); - } - } - return repository; - } - - private ObjectId getObjectId(Repository repository, Ref ref) { - try { - var peel = repository.getRefDatabase().peel(ref); - return Optional.ofNullable(peel.getPeeledObjectId()).orElse(ref.getObjectId()); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - private void configureTransport(@NonNull Transport transport) { - if (transport instanceof SshTransport sshTransport) { - configureSshTransport(sshTransport); - } else if (transport instanceof HttpTransport httpTransport) { - configureHttpTransport(httpTransport); - } else { - log.warn("Could not configure transport '{}'", transport.getClass().getSimpleName()); - } - } - - private void configureSshTransport(@NonNull SshTransport sshTransport) { - log.debug("Configure SshTransport with SSH keys"); - - var jschConfigSessionFactory = new JschConfigSessionFactory() { - @Override - protected void configureJSch(JSch jsch) { - super.configureJSch(jsch); - - Predicate isKeyFile = path -> path.getFileName().toString().toLowerCase().startsWith("id_"); - Predicate isPublicKeyFile = path -> path.getFileName().toString().toLowerCase().endsWith(".pub"); - - var sshDirectory = SystemUtils.getUserHome().toPath().resolve(".ssh"); - try (var sshConfigFiles = Files.walk(sshDirectory)) { - var sshKeyFiles = sshConfigFiles.filter(isKeyFile).toList(); - - var publicKeyPath = sshKeyFiles.stream() - .filter(isPublicKeyFile) - .sorted() - .findFirst(); - var publicKeyBytes = publicKeyPath.map(this::readAllBytes) - .orElseThrow(() -> new IllegalStateException("Could not find public key file")); - - var privateKeyPath = sshKeyFiles.stream() - .filter(Predicate.not(isPublicKeyFile)) - .sorted() - .findFirst(); - var privateKeyBytes = privateKeyPath.map(this::readAllBytes) - .orElseThrow(() -> new IllegalStateException("Could not find private key file")); - - var privateKeyPassword = Optional.ofNullable(password).map(String::getBytes).orElse(null); - - jsch.addIdentity(null, privateKeyBytes, publicKeyBytes, privateKeyPassword); - } catch (IOException | JSchException e) { - throw new RuntimeException(e); - } - } - - @Override - protected void configure(OpenSshConfig.Host hc, Session session) { - // do nothing - // session.setPassword("***"); // NOT supported by GitHub - } - - private byte[] readAllBytes(@NonNull Path path) { - try { - return Files.readAllBytes(path); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - }; - sshTransport.setSshSessionFactory(jschConfigSessionFactory); - } - - - private void configureHttpTransport(@NonNull HttpTransport httpTransport) { - log.debug("Configure HttpTransport with username and password"); - - var usernameToSet = Optional.ofNullable(username).orElse(SystemUtils.getUserName()); - var passwordToSet = Optional.ofNullable(password).orElse(""); - - httpTransport.setCredentialsProvider( - new UsernamePasswordCredentialsProvider(usernameToSet, passwordToSet) - ); - } -} diff --git a/semver-scm-git/src/main/java/io/github/sam42r/semver/scm/GitProviderFactory.java b/semver-scm-git/src/main/java/io/github/sam42r/semver/scm/GitProviderFactory.java deleted file mode 100644 index 7fa6346..0000000 --- a/semver-scm-git/src/main/java/io/github/sam42r/semver/scm/GitProviderFactory.java +++ /dev/null @@ -1,22 +0,0 @@ -package io.github.sam42r.semver.scm; - -import lombok.NoArgsConstructor; -import lombok.NonNull; - -import javax.inject.Named; -import java.nio.file.Path; - - -/** - * @deprecated since switching to Maven-SCM {@link DefaultGitProviderFactory} is used instead - */ -@Named("Git(deprecated)") -@NoArgsConstructor -@Deprecated(since = "1.5.0", forRemoval = true) -public class GitProviderFactory implements SCMProviderFactory { - - @Override - public @NonNull GitProvider getInstance(@NonNull Path path, String username, String password) { - return new GitProvider(path, username, password); - } -} diff --git a/semver-scm-git/src/test/java/io/github/sam42r/semver/scm/AbstractGitProviderTest.java b/semver-scm-git/src/test/java/io/github/sam42r/semver/scm/AbstractGitProviderTest.java deleted file mode 100644 index 255d198..0000000 --- a/semver-scm-git/src/test/java/io/github/sam42r/semver/scm/AbstractGitProviderTest.java +++ /dev/null @@ -1,130 +0,0 @@ -package io.github.sam42r.semver.scm; - -import io.github.sam42r.semver.scm.model.Commit; -import io.github.sam42r.semver.scm.model.Remote; -import io.github.sam42r.semver.scm.model.Tag; -import org.eclipse.jgit.api.Git; -import org.eclipse.jgit.api.errors.GitAPIException; -import org.eclipse.jgit.lib.UserConfig; -import org.eclipse.jgit.transport.URIish; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; - -import java.io.IOException; -import java.net.URISyntaxException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.time.Instant; - -import static org.assertj.core.api.Assertions.assertThat; - -abstract class AbstractGitProviderTest { - - @TempDir - private Path tempDirectory; - private SCMProvider uut; - - @BeforeEach - void setup() { - uut = getUut(tempDirectory); - } - - abstract SCMProvider getUut(Path tempDirectory); - - @Test - void shouldReadCommits() throws GitAPIException, IOException, SCMException { - try (var git = Git.init().setDirectory(tempDirectory.toFile()).call()) { - Files.writeString(tempDirectory.resolve("README.md"), "#JUnit"); - git.add().addFilepattern("README.md").call(); - var expected = git.commit().setMessage("Test commit").call(); - - var actual = uut.readCommits(null); - assertThat(actual).containsExactly( - Commit.builder() - .id(expected.getId().getName()) - .author(git.getRepository().getConfig().get(UserConfig.KEY).getAuthorName()) - .timestamp(Instant.ofEpochSecond(expected.getCommitTime())) - .message("Test commit") - .build() - ); - } - } - - @Test - void shouldReadTags() throws GitAPIException, IOException, SCMException { - try (var git = Git.init().setDirectory(tempDirectory.toFile()).call()) { - Files.writeString(tempDirectory.resolve("README.md"), "#JUnit"); - git.add().addFilepattern("README.md").call(); - var expected = git.commit().setMessage("Test commit").call(); - git.tag().setName("v1.0.0").call(); - - var actual = uut.readTags(); - assertThat(actual).containsExactly( - Tag.builder() - .name("v1.0.0") - .commitId(expected.getId().getName()) - .build() - ); - } - } - - @Test - void shouldAddFiles() throws GitAPIException, IOException, SCMException { - try (var git = Git.init().setDirectory(tempDirectory.toFile()).call()) { - var file = Files.writeString(tempDirectory.resolve("README.md"), "#JUnit"); - var directory = Files.createDirectory(tempDirectory.resolve("directory")); - var anotherFile = Files.writeString(directory.resolve("file.dat"), "JUnit"); - - uut.addFile(file); - uut.addFile(anotherFile); - - var actual = git.status().call(); - assertThat(actual.getAdded()).hasSize(2); - } - } - - @ParameterizedTest - @ValueSource(strings = { - "git@github.com:JUnit/test.git", - "https://github.com/JUnit/test.git" - }) - void shouldReadGithubRemote(String url) throws GitAPIException, URISyntaxException, SCMException { - try (var git = Git.init().setDirectory(tempDirectory.toFile()).call()) { - git.remoteAdd().setName("origin").setUri(new URIish(url)).call(); - - var actual = uut.getRemote(); - - assertThat(actual).isEqualTo(Remote.builder() - .url(url) - .scheme("https") - .host("github.com") - .group("JUnit") - .project("test") - .build()); - } - } - - @ParameterizedTest - @ValueSource(strings = { - "git@gitlab.local:10022:JUnit/subgroup/test.git", - "https://gitlab.local:10022/JUnit/subgroup/test.git" - }) - void shouldReadGitlabRemote(String url) throws GitAPIException, URISyntaxException, SCMException { - try (var git = Git.init().setDirectory(tempDirectory.toFile()).call()) { - git.remoteAdd().setName("origin").setUri(new URIish(url)).call(); - - var actual = uut.getRemote(); - - assertThat(actual).isEqualTo(Remote.builder() - .url(url) - .scheme("https") - .host("gitlab.local:10022") - .group("JUnit/subgroup") - .project("test") - .build()); - } - } -} diff --git a/semver-scm-git/src/test/java/io/github/sam42r/semver/scm/DefaultGitProviderTest.java b/semver-scm-git/src/test/java/io/github/sam42r/semver/scm/DefaultGitProviderTest.java index b655e26..51b3921 100644 --- a/semver-scm-git/src/test/java/io/github/sam42r/semver/scm/DefaultGitProviderTest.java +++ b/semver-scm-git/src/test/java/io/github/sam42r/semver/scm/DefaultGitProviderTest.java @@ -1,12 +1,128 @@ package io.github.sam42r.semver.scm; +import io.github.sam42r.semver.model.scm.Commit; +import io.github.sam42r.semver.model.scm.Remote; +import io.github.sam42r.semver.model.scm.Tag; +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.lib.UserConfig; +import org.eclipse.jgit.transport.URIish; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.file.Files; import java.nio.file.Path; +import java.time.Instant; + +import static org.assertj.core.api.Assertions.assertThat; + +class DefaultGitProviderTest { + + @TempDir + private Path tempDirectory; + private SCMProvider uut; + + @BeforeEach + void setup() { + uut = new DefaultGitProviderFactory().getInstance(tempDirectory, null, null); + } + + @Test + void shouldReadCommits() throws GitAPIException, IOException, SCMException { + try (var git = Git.init().setDirectory(tempDirectory.toFile()).call()) { + Files.writeString(tempDirectory.resolve("README.md"), "#JUnit"); + git.add().addFilepattern("README.md").call(); + var expected = git.commit().setMessage("Test commit").call(); + + var actual = uut.readCommits(null); + assertThat(actual).containsExactly( + new Commit( + expected.getId().getName(), + Instant.ofEpochSecond(expected.getCommitTime()), + git.getRepository().getConfig().get(UserConfig.KEY).getAuthorName(), + "Test commit") + ); + } + } + + @Test + void shouldReadTags() throws GitAPIException, IOException, SCMException { + try (var git = Git.init().setDirectory(tempDirectory.toFile()).call()) { + Files.writeString(tempDirectory.resolve("README.md"), "#JUnit"); + git.add().addFilepattern("README.md").call(); + var expected = git.commit().setMessage("Test commit").call(); + git.tag().setName("v1.0.0").call(); + + var actual = uut.readTags(); + assertThat(actual).containsExactly( + new Tag( + "v1.0.0", + expected.getId().getName()) + ); + } + } + + @Test + void shouldAddFiles() throws GitAPIException, IOException, SCMException { + try (var git = Git.init().setDirectory(tempDirectory.toFile()).call()) { + var file = Files.writeString(tempDirectory.resolve("README.md"), "#JUnit"); + var directory = Files.createDirectory(tempDirectory.resolve("directory")); + var anotherFile = Files.writeString(directory.resolve("file.dat"), "JUnit"); + + uut.addFile(file); + uut.addFile(anotherFile); + + var actual = git.status().call(); + assertThat(actual.getAdded()).hasSize(2); + } + } + + @ParameterizedTest + @ValueSource(strings = { + "git@github.com:JUnit/test.git", + "https://github.com/JUnit/test.git" + }) + void shouldReadGithubRemote(String url) throws GitAPIException, URISyntaxException, SCMException { + try (var git = Git.init().setDirectory(tempDirectory.toFile()).call()) { + git.remoteAdd().setName("origin").setUri(new URIish(url)).call(); + + var actual = uut.getRemote(); + + assertThat(actual).isEqualTo( + new Remote( + url, + "https", + "github.com", + "JUnit", + "test") + ); + } + } + + @ParameterizedTest + @ValueSource(strings = { + "git@gitlab.local:10022:JUnit/subgroup/test.git", + "https://gitlab.local:10022/JUnit/subgroup/test.git" + }) + void shouldReadGitlabRemote(String url) throws GitAPIException, URISyntaxException, SCMException { + try (var git = Git.init().setDirectory(tempDirectory.toFile()).call()) { + git.remoteAdd().setName("origin").setUri(new URIish(url)).call(); -class DefaultGitProviderTest extends AbstractGitProviderTest { + var actual = uut.getRemote(); - @Override - SCMProvider getUut(Path tempDirectory) { - return new DefaultGitProviderFactory() - .getInstance(tempDirectory, null, null); + assertThat(actual).isEqualTo( + new Remote( + url, + "https", + "gitlab.local:10022", + "JUnit/subgroup", + "test") + ); + } } } diff --git a/semver-scm-git/src/test/java/io/github/sam42r/semver/scm/GitProviderTest.java b/semver-scm-git/src/test/java/io/github/sam42r/semver/scm/GitProviderTest.java deleted file mode 100644 index ccfffde..0000000 --- a/semver-scm-git/src/test/java/io/github/sam42r/semver/scm/GitProviderTest.java +++ /dev/null @@ -1,29 +0,0 @@ -package io.github.sam42r.semver.scm; - -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -import java.io.IOException; -import java.nio.file.Path; - -import static org.assertj.core.api.Assertions.assertThat; - -class GitProviderTest extends AbstractGitProviderTest { - - @Override - SCMProvider getUut(Path tempDirectory) { - return new GitProviderFactory() - .getInstance(tempDirectory, null, null); - } - - @Test - @Disabled("local testing only") - void shouldPushToRemote() throws IOException, SCMException { - var path = Path.of("..").toRealPath(); - - var scmProvider = new GitProvider(path, null, "***"); - - var actual = scmProvider.push(false); - assertThat(actual).isNotNull(); - } -} diff --git a/semver-scm-mercurial/src/main/java/io/github/sam42r/semver/scm/HgProvider.java b/semver-scm-mercurial/src/main/java/io/github/sam42r/semver/scm/HgProvider.java index 5efc2fe..fd6d99a 100644 --- a/semver-scm-mercurial/src/main/java/io/github/sam42r/semver/scm/HgProvider.java +++ b/semver-scm-mercurial/src/main/java/io/github/sam42r/semver/scm/HgProvider.java @@ -1,8 +1,8 @@ package io.github.sam42r.semver.scm; -import io.github.sam42r.semver.scm.model.Commit; -import io.github.sam42r.semver.scm.model.Remote; -import io.github.sam42r.semver.scm.model.Tag; +import io.github.sam42r.semver.model.scm.Commit; +import io.github.sam42r.semver.model.scm.Remote; +import io.github.sam42r.semver.model.scm.Tag; import lombok.NonNull; import org.apache.commons.lang3.NotImplementedException; diff --git a/semver-scm-subversion/src/main/java/io/github/sam42r/semver/scm/SvnProvider.java b/semver-scm-subversion/src/main/java/io/github/sam42r/semver/scm/SvnProvider.java index 2705dd6..2f09205 100644 --- a/semver-scm-subversion/src/main/java/io/github/sam42r/semver/scm/SvnProvider.java +++ b/semver-scm-subversion/src/main/java/io/github/sam42r/semver/scm/SvnProvider.java @@ -1,8 +1,8 @@ package io.github.sam42r.semver.scm; -import io.github.sam42r.semver.scm.model.Commit; -import io.github.sam42r.semver.scm.model.Remote; -import io.github.sam42r.semver.scm.model.Tag; +import io.github.sam42r.semver.model.scm.Commit; +import io.github.sam42r.semver.model.scm.Remote; +import io.github.sam42r.semver.model.scm.Tag; import lombok.NonNull; import org.apache.commons.lang3.NotImplementedException; From 5893676877b8b67f86596cb4cb5658501a8f9569 Mon Sep 17 00:00:00 2001 From: Rene R Date: Sat, 31 Jan 2026 18:51:09 +0100 Subject: [PATCH 2/4] :sparkles: generate issue links #41 --- .../semver/analyzer/CommitAnalyzer.java | 4 +- .../analyzer/ConventionalCommitAnalyzer.java | 39 ++++++++----- .../ConventionalCommitAnalyzerTest.java | 57 +++++++++++-------- .../analyzer/GitMojiCommitAnalyzer.java | 18 +++--- .../analyzer/GitMojiCommitAnalyzerTest.java | 33 +++++++---- .../resources/templates/default/default.html | 7 ++- .../resources/templates/missive/missive.html | 8 ++- .../resources/templates/spinal/spinal.html | 8 ++- .../semver/changelog/changelog.mustache | 14 ++--- .../semver/model/release/ProviderSpec.java | 10 ++++ .../sam42r/semver/SemanticReleaseMojo.java | 5 +- .../semver/SemanticReleaseMojoTest.java | 21 +++++-- .../semver/release/ReleasePublisher.java | 3 + .../release/DefaultGithubPublisher.java | 8 +++ .../semver/release/GitlabPublisher.java | 8 +++ 15 files changed, 165 insertions(+), 78 deletions(-) create mode 100644 semver-maven-model/src/main/java/io/github/sam42r/semver/model/release/ProviderSpec.java diff --git a/semver-analyzer-api/src/main/java/io/github/sam42r/semver/analyzer/CommitAnalyzer.java b/semver-analyzer-api/src/main/java/io/github/sam42r/semver/analyzer/CommitAnalyzer.java index 08be05e..24030cd 100644 --- a/semver-analyzer-api/src/main/java/io/github/sam42r/semver/analyzer/CommitAnalyzer.java +++ b/semver-analyzer-api/src/main/java/io/github/sam42r/semver/analyzer/CommitAnalyzer.java @@ -1,7 +1,9 @@ package io.github.sam42r.semver.analyzer; import io.github.sam42r.semver.model.analyze.AnalyzedCommit; +import io.github.sam42r.semver.model.release.ProviderSpec; import io.github.sam42r.semver.model.scm.Commit; +import io.github.sam42r.semver.model.scm.Remote; import lombok.NonNull; import java.util.List; @@ -12,7 +14,7 @@ */ public interface CommitAnalyzer { - @NonNull List analyzeCommits(@NonNull List commits); + @NonNull List analyzeCommits(@NonNull List commits, @NonNull Remote remote, ProviderSpec providerSpec); @NonNull String generateReleaseCommitMessage(@NonNull String version); } diff --git a/semver-analyzer-conventional/src/main/java/io/github/sam42r/semver/analyzer/ConventionalCommitAnalyzer.java b/semver-analyzer-conventional/src/main/java/io/github/sam42r/semver/analyzer/ConventionalCommitAnalyzer.java index b493840..0611c9a 100644 --- a/semver-analyzer-conventional/src/main/java/io/github/sam42r/semver/analyzer/ConventionalCommitAnalyzer.java +++ b/semver-analyzer-conventional/src/main/java/io/github/sam42r/semver/analyzer/ConventionalCommitAnalyzer.java @@ -3,8 +3,11 @@ import io.github.sam42r.semver.analyzer.model.Configuration; import io.github.sam42r.semver.model.analyze.AnalyzedCommit; import io.github.sam42r.semver.model.analyze.ChangeCategory; +import io.github.sam42r.semver.model.analyze.Issue; import io.github.sam42r.semver.model.analyze.SemVerChangeLevel; +import io.github.sam42r.semver.model.release.ProviderSpec; import io.github.sam42r.semver.model.scm.Commit; +import io.github.sam42r.semver.model.scm.Remote; import lombok.AccessLevel; import lombok.NonNull; import lombok.RequiredArgsConstructor; @@ -13,6 +16,7 @@ import java.io.IOException; import java.io.StringReader; import java.io.UncheckedIOException; +import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.regex.Pattern; @@ -25,6 +29,7 @@ public class ConventionalCommitAnalyzer implements CommitAnalyzer { private static final String COMMIT_HEADER_PATTERN = "(?([a-z]*))(?(\\([a-z]*\\)))?(?(!))?(?(: .*))"; + private static final String COMMIT_FOOTER_PATTERN = "(?(#\\d*))"; private final Configuration configuration; @@ -34,13 +39,12 @@ public class ConventionalCommitAnalyzer implements CommitAnalyzer { } @Override - public @NonNull List analyzeCommits(@NonNull List commits) { - return commits.stream().map(this::analyzeCommit).toList(); + public @NonNull List analyzeCommits(@NonNull List commits, @NonNull Remote remote, ProviderSpec providerSpec) { + return commits.stream().map(commit -> analyzeCommit(commit, remote, providerSpec)).toList(); } - @SuppressWarnings("MismatchedQueryAndUpdateOfStringBuilder") - private AnalyzedCommit analyzeCommit(@NonNull Commit commit) { + private AnalyzedCommit analyzeCommit(@NonNull Commit commit, @NonNull Remote remote, ProviderSpec providerSpec) { var headerBuilder = new StringBuilder(); var bodyBuilder = new StringBuilder(); var footerBuilder = new StringBuilder(); @@ -69,22 +73,29 @@ private AnalyzedCommit analyzeCommit(@NonNull Commit commit) { var body = bodyBuilder.toString().trim(); var footer = footerBuilder.toString().trim(); - var pattern = Pattern.compile(COMMIT_HEADER_PATTERN); - var matcher = pattern.matcher(header); + var headerMatcher = Pattern.compile(COMMIT_HEADER_PATTERN).matcher(header); - if (header.isEmpty() || !matcher.find()) { + if (header.isEmpty() || !headerMatcher.find()) { return new AnalyzedCommit(commit, null, null, null, null, null, null, null, null, null); } - var type = matcher.group("TYPE"); - var scope = Optional.ofNullable(matcher.group("SCOPE")) + var type = headerMatcher.group("TYPE"); + var scope = Optional.ofNullable(headerMatcher.group("SCOPE")) .map(v -> v.replace("(", "")) .map(v -> v.replace(")", "")) .orElse(null); - var breaking = Optional.ofNullable(matcher.group("BREAKING")); - var description = matcher.group("DESCRIPTION").replaceFirst(":", "").trim(); + var breaking = Optional.ofNullable(headerMatcher.group("BREAKING")); + var description = headerMatcher.group("DESCRIPTION").replaceFirst(":", "").trim(); + + var footerMatcher = Pattern.compile(COMMIT_FOOTER_PATTERN).matcher(footer); - // TODO search for issues in footer + var refs = footerMatcher.find() ? + Optional.ofNullable(footerMatcher.group("REF")) + .map(v -> v.replace("#", "")) + .map(String::trim) + .map(List::of) + .orElse(Collections.emptyList()) : + Collections.emptyList(); return new AnalyzedCommit( commit, @@ -96,7 +107,9 @@ private AnalyzedCommit analyzeCommit(@NonNull Commit commit) { scope, description, breaking.isPresent() || footer.contains("BREAKING CHANGE") ? SemVerChangeLevel.MAJOR : getLevel(type), - null + refs.stream() + .map(ref -> new Issue(ref, providerSpec.issueUrl(remote, ref))) + .toList() ); } diff --git a/semver-analyzer-conventional/src/test/java/io/github/sam42r/semver/analyzer/ConventionalCommitAnalyzerTest.java b/semver-analyzer-conventional/src/test/java/io/github/sam42r/semver/analyzer/ConventionalCommitAnalyzerTest.java index ff8af23..87441ed 100644 --- a/semver-analyzer-conventional/src/test/java/io/github/sam42r/semver/analyzer/ConventionalCommitAnalyzerTest.java +++ b/semver-analyzer-conventional/src/test/java/io/github/sam42r/semver/analyzer/ConventionalCommitAnalyzerTest.java @@ -2,8 +2,11 @@ import io.github.sam42r.semver.model.analyze.AnalyzedCommit; import io.github.sam42r.semver.model.analyze.ChangeCategory; +import io.github.sam42r.semver.model.analyze.Issue; import io.github.sam42r.semver.model.analyze.SemVerChangeLevel; +import io.github.sam42r.semver.model.release.ProviderSpec; import io.github.sam42r.semver.model.scm.Commit; +import io.github.sam42r.semver.model.scm.Remote; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -24,15 +27,18 @@ void setup() { @Test void shouldFindFix() { var actual = uut.analyzeCommits(List.of( - new Commit("42", Instant.EPOCH, "JUnit", - """ - fix(scm): set clean commit message - - * added scope for commit messages - - refs #42 - """) - )); + new Commit("42", Instant.EPOCH, "JUnit", + """ + fix(scm): set clean commit message + + * added scope for commit messages + + refs #42 + """) + ), + Remote.of("git@github.com:Sam42R/semver-maven-plugin.git"), + new ProviderSpec("%s://%s/%s/%s/%s") + ); assertThat(actual).containsExactly( new AnalyzedCommit( @@ -52,25 +58,28 @@ void shouldFindFix() { "scm", "set clean commit message", SemVerChangeLevel.PATCH, - null) + List.of(new Issue("42", "https://github.com/Sam42R/semver-maven-plugin/42"))) ); } @Test void shouldFindBreakingChanges() { var actual = uut.analyzeCommits(List.of( - new Commit("42", Instant.EPOCH, "JUnit", - """ - fix(scm): set clean commit message - - * added scope for commit messages - - BREAKING CHANGE: breaks everything - refs #42 - """), - new Commit("42", Instant.EPOCH, "JUnit", - "fix(scm)!: set clean commit message") - )); + new Commit("42", Instant.EPOCH, "JUnit", + """ + fix(scm): set clean commit message + + * added scope for commit messages + + BREAKING CHANGE: breaks everything + refs #42 + """), + new Commit("42", Instant.EPOCH, "JUnit", + "fix(scm)!: set clean commit message") + ), + Remote.of("git@github.com:Sam42R/semver-maven-plugin.git"), + new ProviderSpec("%s://%s/%s/%s/%s") + ); assertThat(actual).containsExactly( new AnalyzedCommit( @@ -91,7 +100,7 @@ void shouldFindBreakingChanges() { "scm", "set clean commit message", SemVerChangeLevel.MAJOR, - null), + List.of(new Issue("42", "https://github.com/Sam42R/semver-maven-plugin/42"))), new AnalyzedCommit( new Commit("42", Instant.EPOCH, "JUnit", "fix(scm)!: set clean commit message"), "fix(scm)!: set clean commit message", @@ -102,7 +111,7 @@ void shouldFindBreakingChanges() { "scm", "set clean commit message", SemVerChangeLevel.MAJOR, - null) + List.of()) ); } } diff --git a/semver-analyzer-gitmoji/src/main/java/io/github/sam42r/semver/analyzer/GitMojiCommitAnalyzer.java b/semver-analyzer-gitmoji/src/main/java/io/github/sam42r/semver/analyzer/GitMojiCommitAnalyzer.java index 56bed3d..c35fa27 100644 --- a/semver-analyzer-gitmoji/src/main/java/io/github/sam42r/semver/analyzer/GitMojiCommitAnalyzer.java +++ b/semver-analyzer-gitmoji/src/main/java/io/github/sam42r/semver/analyzer/GitMojiCommitAnalyzer.java @@ -5,7 +5,9 @@ import io.github.sam42r.semver.model.analyze.ChangeCategory; import io.github.sam42r.semver.model.analyze.Issue; import io.github.sam42r.semver.model.analyze.SemVerChangeLevel; +import io.github.sam42r.semver.model.release.ProviderSpec; import io.github.sam42r.semver.model.scm.Commit; +import io.github.sam42r.semver.model.scm.Remote; import lombok.AccessLevel; import lombok.NonNull; import lombok.RequiredArgsConstructor; @@ -27,16 +29,16 @@ public class GitMojiCommitAnalyzer implements CommitAnalyzer { private final Configuration configuration; @Override - public @NonNull List analyzeCommits(@NonNull List commits) { - return commits.stream().map(this::analyzeCommit).toList(); + public @NonNull String generateReleaseCommitMessage(@NonNull String version) { + return "%s: bump version %s".formatted(configuration.getRelease(), version); } @Override - public @NonNull String generateReleaseCommitMessage(@NonNull String version) { - return "%s: bump version %s".formatted(configuration.getRelease(), version); + public @NonNull List analyzeCommits(@NonNull List commits, @NonNull Remote remote, ProviderSpec providerSpec) { + return commits.stream().map(commit -> analyzeCommit(commit, remote, providerSpec)).toList(); } - private AnalyzedCommit analyzeCommit(@NonNull Commit commit) { + private AnalyzedCommit analyzeCommit(@NonNull Commit commit, @NonNull Remote remote, ProviderSpec providerSpec) { var pattern = Pattern.compile(COMMIT_MESSAGE_PATTERN); var matcher = pattern.matcher(commit.message()); @@ -52,7 +54,7 @@ private AnalyzedCommit analyzeCommit(@NonNull Commit commit) { .map(String::trim) .orElse(null); var message = matcher.group("MESSAGE").trim(); - var ref = Optional.ofNullable(matcher.group("REF")) + var refs = Optional.ofNullable(matcher.group("REF")) .map(v -> v.replace("#", "")) .map(String::trim) .map(List::of) @@ -68,7 +70,9 @@ private AnalyzedCommit analyzeCommit(@NonNull Commit commit) { scope, message, getLevel(intention), - ref.stream().map(v -> new Issue(v, "")).toList() + refs.stream() + .map(ref -> new Issue(ref, providerSpec.issueUrl(remote, ref))) + .toList() ); } diff --git a/semver-analyzer-gitmoji/src/test/java/io/github/sam42r/semver/analyzer/GitMojiCommitAnalyzerTest.java b/semver-analyzer-gitmoji/src/test/java/io/github/sam42r/semver/analyzer/GitMojiCommitAnalyzerTest.java index e758bd5..590afad 100644 --- a/semver-analyzer-gitmoji/src/test/java/io/github/sam42r/semver/analyzer/GitMojiCommitAnalyzerTest.java +++ b/semver-analyzer-gitmoji/src/test/java/io/github/sam42r/semver/analyzer/GitMojiCommitAnalyzerTest.java @@ -4,7 +4,9 @@ import io.github.sam42r.semver.model.analyze.ChangeCategory; import io.github.sam42r.semver.model.analyze.Issue; import io.github.sam42r.semver.model.analyze.SemVerChangeLevel; +import io.github.sam42r.semver.model.release.ProviderSpec; import io.github.sam42r.semver.model.scm.Commit; +import io.github.sam42r.semver.model.scm.Remote; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -25,9 +27,12 @@ void setup() { @Test void shouldFindFixAndFeat() { var actual = uut.analyzeCommits(List.of( - new Commit("42.1", Instant.EPOCH, "JUnit", ":bug: (scm) set clean commit message #42"), - new Commit("42.2", Instant.EPOCH, "JUnit", ":sparkles: add awesome things") - )); + new Commit("42.1", Instant.EPOCH, "JUnit", ":bug: (scm) set clean commit message #42"), + new Commit("42.2", Instant.EPOCH, "JUnit", ":sparkles: add awesome things") + ), + Remote.of("git@github.com:Sam42R/semver-maven-plugin.git"), + new ProviderSpec("%s://%s/%s/%s/%s") + ); assertThat(actual).containsExactlyInAnyOrder( new AnalyzedCommit( @@ -40,7 +45,7 @@ void shouldFindFixAndFeat() { "scm", "set clean commit message", SemVerChangeLevel.PATCH, - List.of(new Issue("42", ""))), + List.of(new Issue("42", "https://github.com/Sam42R/semver-maven-plugin/42"))), new AnalyzedCommit( new Commit("42.2", Instant.EPOCH, "JUnit", ":sparkles: add awesome things"), ":sparkles: add awesome things", @@ -51,15 +56,18 @@ void shouldFindFixAndFeat() { null, "add awesome things", SemVerChangeLevel.MINOR, - null) + List.of()) ); } @Test void shouldFindBreaking() { var actual = uut.analyzeCommits(List.of( - new Commit("42.3", Instant.EPOCH, "JUnit", ":boom: (void): break some glass #42") - )); + new Commit("42.3", Instant.EPOCH, "JUnit", ":boom: (void): break some glass #42") + ), + Remote.of("git@github.com:Sam42R/semver-maven-plugin.git"), + new ProviderSpec("%s://%s/%s/%s/%s") + ); assertThat(actual).containsExactly( new AnalyzedCommit( @@ -72,15 +80,18 @@ void shouldFindBreaking() { "void", "break some glass", SemVerChangeLevel.MAJOR, - List.of(new Issue("42",""))) + List.of(new Issue("42", "https://github.com/Sam42R/semver-maven-plugin/42"))) ); } @Test void shouldFindOther() { var actual = uut.analyzeCommits(List.of( - new Commit("42.4", Instant.EPOCH, "JUnit", ":white_check_mark: add test for something") - )); + new Commit("42.4", Instant.EPOCH, "JUnit", ":white_check_mark: add test for something") + ), + Remote.of("git@github.com:Sam42R/semver-maven-plugin.git"), + new ProviderSpec("%s://%s/%s/%s/%s") + ); assertThat(actual).containsExactly( new AnalyzedCommit( @@ -93,7 +104,7 @@ void shouldFindOther() { null, "add test for something", SemVerChangeLevel.NONE, - null) + List.of()) ); } } diff --git a/semver-changelog-html/src/main/resources/templates/default/default.html b/semver-changelog-html/src/main/resources/templates/default/default.html index 72f82d2..638cad0 100644 --- a/semver-changelog-html/src/main/resources/templates/default/default.html +++ b/semver-changelog-html/src/main/resources/templates/default/default.html @@ -47,8 +47,11 @@

Changelog

    -
  • - Lorem ipsum +
  • + Lorem ipsum + +  () +
diff --git a/semver-changelog-html/src/main/resources/templates/missive/missive.html b/semver-changelog-html/src/main/resources/templates/missive/missive.html index 924483c..96d3f3c 100644 --- a/semver-changelog-html/src/main/resources/templates/missive/missive.html +++ b/semver-changelog-html/src/main/resources/templates/missive/missive.html @@ -62,9 +62,11 @@

Changelog

-
- Lorem ipsum + + Lorem ipsum + + () +
diff --git a/semver-changelog-html/src/main/resources/templates/spinal/spinal.html b/semver-changelog-html/src/main/resources/templates/spinal/spinal.html index f3222cc..7192a78 100644 --- a/semver-changelog-html/src/main/resources/templates/spinal/spinal.html +++ b/semver-changelog-html/src/main/resources/templates/spinal/spinal.html @@ -54,9 +54,11 @@

-

- Lorem ipsum +

+ Lorem ipsum + + () +

diff --git a/semver-changelog-markup/src/main/resources/io/github/sam42r/semver/changelog/changelog.mustache b/semver-changelog-markup/src/main/resources/io/github/sam42r/semver/changelog/changelog.mustache index 2a34b52..c9913aa 100644 --- a/semver-changelog-markup/src/main/resources/io/github/sam42r/semver/changelog/changelog.mustache +++ b/semver-changelog-markup/src/main/resources/io/github/sam42r/semver/changelog/changelog.mustache @@ -9,43 +9,43 @@ ### Added {{/hasAdded}} {{#added}} -- {{header}} +- {{header}} {{#issues}}([#{{id}}]({{url}})){{/issues}} {{/added}} {{#hasChanges}} ### Changed {{/hasChanges}} {{#changes}} -- {{header}} +- {{header}} {{#issues}}([#{{id}}]({{url}})){{/issues}} {{/changes}} {{#hasDeprecated}} ### Deprecated {{/hasDeprecated}} {{#deprecated}} -- {{header}} +- {{header}} {{#issues}}([#{{id}}]({{url}})){{/issues}} {{/deprecated}} {{#hasRemoved}} ### Removed {{/hasRemoved}} {{#removed}} -- {{header}} +- {{header}} {{#issues}}([#{{id}}]({{url}})){{/issues}} {{/removed}} {{#hasPatches}} ### Fixed {{/hasPatches}} {{#patches}} -- {{header}} +- {{header}} {{#issues}}([#{{id}}]({{url}})){{/issues}} {{/patches}} {{#hasSecurity}} ### Security {{/hasSecurity}} {{#securities}} -- {{header}} +- {{header}} {{#issues}}([#{{id}}]({{url}})){{/issues}} {{/securities}} {{#hasOthers}} ### Others {{/hasOthers}} {{#others}} -- {{header}} +- {{header}} {{#issues}}([#{{id}}]({{url}})){{/issues}} {{/others}} {{#renderFooter}} diff --git a/semver-maven-model/src/main/java/io/github/sam42r/semver/model/release/ProviderSpec.java b/semver-maven-model/src/main/java/io/github/sam42r/semver/model/release/ProviderSpec.java new file mode 100644 index 0000000..d08a484 --- /dev/null +++ b/semver-maven-model/src/main/java/io/github/sam42r/semver/model/release/ProviderSpec.java @@ -0,0 +1,10 @@ +package io.github.sam42r.semver.model.release; + +import io.github.sam42r.semver.model.scm.Remote; + +public record ProviderSpec(String issuePattern) { + + public String issueUrl(Remote remote, String id) { + return issuePattern().formatted(remote.scheme(), remote.host(), remote.group(), remote.project(), id); + } +} diff --git a/semver-maven-plugin/src/main/java/io/github/sam42r/semver/SemanticReleaseMojo.java b/semver-maven-plugin/src/main/java/io/github/sam42r/semver/SemanticReleaseMojo.java index f132dad..b18ba02 100644 --- a/semver-maven-plugin/src/main/java/io/github/sam42r/semver/SemanticReleaseMojo.java +++ b/semver-maven-plugin/src/main/java/io/github/sam42r/semver/SemanticReleaseMojo.java @@ -131,7 +131,7 @@ public void execute() throws MojoExecutionException, MojoFailureException { .orElse(Version.of(0, 0, 0, tagFormat)); getLog().debug("Actual version: '%s'".formatted(latestVersion.toString())); - var analyzedCommits = analyzeCommits(scmProvider, commitAnalyzer, latestCommit); + var analyzedCommits = analyzeCommits(scmProvider, commitAnalyzer, releasePublisher, latestCommit); var majorCount = analyzedCommits.stream().filter(AnalyzedCommit.isBreaking).count(); var minorCount = analyzedCommits.stream().filter(AnalyzedCommit.isFeature).count(); @@ -251,11 +251,12 @@ private LatestReleaseInfo getLatestRelease(SCMProvider scmProvider) throws MojoE private List analyzeCommits( SCMProvider scmProvider, CommitAnalyzer commitAnalyzer, + ReleasePublisher releasePublisher, String latestCommit ) throws MojoExecutionException { try { var commits = scmProvider.readCommits(latestCommit); - return commitAnalyzer.analyzeCommits(commits.toList()); + return commitAnalyzer.analyzeCommits(commits.toList(), scmProvider.getRemote(), releasePublisher.providerSpec()); } catch (SCMException e) { throw new MojoExecutionException(e.getMessage(), e.getCause()); } diff --git a/semver-maven-plugin/src/test/java/io/github/sam42r/semver/SemanticReleaseMojoTest.java b/semver-maven-plugin/src/test/java/io/github/sam42r/semver/SemanticReleaseMojoTest.java index 71f03fb..8e62369 100644 --- a/semver-maven-plugin/src/test/java/io/github/sam42r/semver/SemanticReleaseMojoTest.java +++ b/semver-maven-plugin/src/test/java/io/github/sam42r/semver/SemanticReleaseMojoTest.java @@ -12,12 +12,14 @@ import org.apache.maven.project.MavenProject; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.transport.URIish; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import java.io.IOException; +import java.net.URISyntaxException; import java.nio.file.Files; import java.nio.file.Path; import java.util.HashMap; @@ -72,7 +74,7 @@ void setup() throws IOException { @Test @SuppressWarnings("unchecked") - void shouldFindNoLatestRelease() throws GitAPIException, MojoExecutionException, MojoFailureException { + void shouldFindNoLatestRelease() throws GitAPIException, MojoExecutionException, MojoFailureException, URISyntaxException { try (var git = initializeGitRepository(tmp)) { git.add().addFilepattern("pom.xml").call(); git.commit().setMessage("Initial commit").call(); @@ -85,7 +87,7 @@ void shouldFindNoLatestRelease() throws GitAPIException, MojoExecutionException, @Test @SuppressWarnings("unchecked") - void shouldFindLatestRelease() throws IOException, GitAPIException, MojoExecutionException, MojoFailureException { + void shouldFindLatestRelease() throws IOException, GitAPIException, MojoExecutionException, MojoFailureException, URISyntaxException { try (var git = initializeGitRepository(tmp)) { git.add().addFilepattern("pom.xml").call(); git.commit().setMessage("Initial commit").call(); @@ -102,7 +104,7 @@ void shouldFindLatestRelease() throws IOException, GitAPIException, MojoExecutio } @Test - void shouldThrowWithEmptyGitRepository() throws GitAPIException { + void shouldThrowWithEmptyGitRepository() throws GitAPIException, URISyntaxException { try (var ignored = initializeGitRepository(tmp)) { assertThatThrownBy(() -> uut.execute()) .isInstanceOf(MojoExecutionException.class) @@ -117,8 +119,17 @@ void shouldThrowWithMissingGitRepository() { .hasMessageStartingWith("Could not find git configuration"); } - private Git initializeGitRepository(Path path) throws GitAPIException { - return Git.init().setDirectory(path.toFile()).call(); + private Git initializeGitRepository(Path path) throws GitAPIException, URISyntaxException { + var git = Git.init() + .setDirectory(path.toFile()) + .call(); + + git.remoteAdd() + .setName("origin") + .setUri(new URIish("https://junit.org/test/project")) + .call(); + + return git; } private Path createFile(Path path, String filename, String content) throws IOException { diff --git a/semver-release-api/src/main/java/io/github/sam24r/semver/release/ReleasePublisher.java b/semver-release-api/src/main/java/io/github/sam24r/semver/release/ReleasePublisher.java index 4b2932a..df970c1 100644 --- a/semver-release-api/src/main/java/io/github/sam24r/semver/release/ReleasePublisher.java +++ b/semver-release-api/src/main/java/io/github/sam24r/semver/release/ReleasePublisher.java @@ -1,5 +1,6 @@ package io.github.sam24r.semver.release; +import io.github.sam42r.semver.model.release.ProviderSpec; import io.github.sam42r.semver.model.release.ReleaseInfo; import lombok.NonNull; @@ -12,4 +13,6 @@ void publish( @NonNull String project, @NonNull ReleaseInfo releaseInfo ) throws ReleaseException; + + ProviderSpec providerSpec(); } diff --git a/semver-release-github/src/main/java/io/github/sam42r/semver/release/DefaultGithubPublisher.java b/semver-release-github/src/main/java/io/github/sam42r/semver/release/DefaultGithubPublisher.java index 8a43751..c025500 100644 --- a/semver-release-github/src/main/java/io/github/sam42r/semver/release/DefaultGithubPublisher.java +++ b/semver-release-github/src/main/java/io/github/sam42r/semver/release/DefaultGithubPublisher.java @@ -2,6 +2,7 @@ import io.github.sam24r.semver.release.ReleaseException; import io.github.sam24r.semver.release.ReleasePublisher; +import io.github.sam42r.semver.model.release.ProviderSpec; import io.github.sam42r.semver.model.release.ReleaseInfo; import lombok.NonNull; import lombok.RequiredArgsConstructor; @@ -49,4 +50,11 @@ public void publish( throw new ReleaseException(e); } } + + @Override + public ProviderSpec providerSpec() { + return new ProviderSpec( + "%s://%s/%s/%s/issues/%s" + ); + } } diff --git a/semver-release-gitlab/src/main/java/io/github/sam42r/semver/release/GitlabPublisher.java b/semver-release-gitlab/src/main/java/io/github/sam42r/semver/release/GitlabPublisher.java index 0d583f9..ce446af 100644 --- a/semver-release-gitlab/src/main/java/io/github/sam42r/semver/release/GitlabPublisher.java +++ b/semver-release-gitlab/src/main/java/io/github/sam42r/semver/release/GitlabPublisher.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.github.sam24r.semver.release.ReleaseException; import io.github.sam24r.semver.release.ReleasePublisher; +import io.github.sam42r.semver.model.release.ProviderSpec; import io.github.sam42r.semver.model.release.ReleaseInfo; import io.github.sam42r.semver.release.model.GitlabRelease; import lombok.NonNull; @@ -74,4 +75,11 @@ private GitlabRelease generatePayload(ReleaseInfo releaseInfo) { .description(releaseInfo.description()) .build(); } + + @Override + public ProviderSpec providerSpec() { + return new ProviderSpec( + "%s://%s/%s/%s/-/issues/%s" + ); + } } From 1b5862e3a614b2253a5ee5018de648c0a1ac4229 Mon Sep 17 00:00:00 2001 From: Rene R Date: Mon, 9 Feb 2026 10:42:26 +0100 Subject: [PATCH 3/4] :sparkles: generate commit links #41 --- .../analyzer/model/ConfigurationTest.java | 28 +++++----- .../analyzer/ConventionalCommitAnalyzer.java | 3 +- .../ConventionalCommitAnalyzerTest.java | 7 ++- .../analyzer/GitMojiCommitAnalyzer.java | 3 +- .../analyzer/GitMojiCommitAnalyzerTest.java | 10 +++- .../resources/templates/default/default.html | 4 ++ .../resources/templates/missive/missive.html | 4 ++ .../resources/templates/spinal/spinal.html | 4 ++ .../semver/changelog/HtmlRendererTest.java | 10 +++- .../semver/changelog/MarkupRenderer.java | 56 ++++++++++++++++--- .../sam42r/semver/changelog/model/Link.java | 4 ++ .../changelog/model/RenderedCommit.java | 6 ++ .../semver/changelog/changelog.mustache | 14 ++--- .../semver/changelog/MarkupRendererTest.java | 4 +- .../semver/model/analyze/AnalyzedCommit.java | 16 ++++++ .../semver/model/release/ProviderSpec.java | 6 +- .../release/DefaultGithubPublisher.java | 3 +- .../semver/release/GitlabPublisher.java | 3 +- 18 files changed, 142 insertions(+), 43 deletions(-) create mode 100644 semver-changelog-markup/src/main/java/io/github/sam42r/semver/changelog/model/Link.java create mode 100644 semver-changelog-markup/src/main/java/io/github/sam42r/semver/changelog/model/RenderedCommit.java diff --git a/semver-analyzer-api/src/test/java/io/github/sam42r/semver/analyzer/model/ConfigurationTest.java b/semver-analyzer-api/src/test/java/io/github/sam42r/semver/analyzer/model/ConfigurationTest.java index 4f82c49..74de068 100644 --- a/semver-analyzer-api/src/test/java/io/github/sam42r/semver/analyzer/model/ConfigurationTest.java +++ b/semver-analyzer-api/src/test/java/io/github/sam42r/semver/analyzer/model/ConfigurationTest.java @@ -20,13 +20,13 @@ void shouldReadConfigFromClasspath() { Configuration::getRelease, Configuration::getItems) .containsExactly( "R0", List.of( - new AnalyzedCommit(null, null, null, null, "D1", ChangeCategory.DEPRECATED, null, null, SemVerChangeLevel.NONE, null), - new AnalyzedCommit(null, null, null, null, "R1", ChangeCategory.REMOVED, null, null, SemVerChangeLevel.NONE, null), - new AnalyzedCommit(null, null, null, null, "F1", ChangeCategory.FIXED, null, null, SemVerChangeLevel.PATCH, null), - new AnalyzedCommit(null, null, null, null, "S1", ChangeCategory.SECURITY, null, null, SemVerChangeLevel.PATCH, null), - new AnalyzedCommit(null, null, null, null, "A1", ChangeCategory.ADDED, null, null, SemVerChangeLevel.MINOR, null), - new AnalyzedCommit(null, null, null, null, "C1", ChangeCategory.CHANGED, null, null, SemVerChangeLevel.MINOR, null), - new AnalyzedCommit(null, null, null, null, "B1", ChangeCategory.OTHER, null, null, SemVerChangeLevel.MAJOR, null) + new AnalyzedCommit(null, null, null, null, null, "D1", ChangeCategory.DEPRECATED, null, null, SemVerChangeLevel.NONE, null), + new AnalyzedCommit(null, null, null, null, null, "R1", ChangeCategory.REMOVED, null, null, SemVerChangeLevel.NONE, null), + new AnalyzedCommit(null, null, null, null, null, "F1", ChangeCategory.FIXED, null, null, SemVerChangeLevel.PATCH, null), + new AnalyzedCommit(null, null, null, null, null, "S1", ChangeCategory.SECURITY, null, null, SemVerChangeLevel.PATCH, null), + new AnalyzedCommit(null, null, null, null, null, "A1", ChangeCategory.ADDED, null, null, SemVerChangeLevel.MINOR, null), + new AnalyzedCommit(null, null, null, null, null, "C1", ChangeCategory.CHANGED, null, null, SemVerChangeLevel.MINOR, null), + new AnalyzedCommit(null, null, null, null, null, "B1", ChangeCategory.OTHER, null, null, SemVerChangeLevel.MAJOR, null) ) ); } @@ -41,13 +41,13 @@ void shouldReadConfigFromFile() { Configuration::getRelease, Configuration::getItems) .containsExactly( "R0", List.of( - new AnalyzedCommit(null, null, null, null, "D1", ChangeCategory.DEPRECATED, null, null, SemVerChangeLevel.NONE, null), - new AnalyzedCommit(null, null, null, null, "R1", ChangeCategory.REMOVED, null, null, SemVerChangeLevel.NONE, null), - new AnalyzedCommit(null, null, null, null, "F1", ChangeCategory.FIXED, null, null, SemVerChangeLevel.PATCH, null), - new AnalyzedCommit(null, null, null, null, "S1", ChangeCategory.SECURITY, null, null, SemVerChangeLevel.PATCH, null), - new AnalyzedCommit(null, null, null, null, "A1", ChangeCategory.ADDED, null, null, SemVerChangeLevel.MINOR, null), - new AnalyzedCommit(null, null, null, null, "C1", ChangeCategory.CHANGED, null, null, SemVerChangeLevel.MINOR, null), - new AnalyzedCommit(null, null, null, null, "B1", ChangeCategory.OTHER, null, null, SemVerChangeLevel.MAJOR, null) + new AnalyzedCommit(null, null, null, null, null, "D1", ChangeCategory.DEPRECATED, null, null, SemVerChangeLevel.NONE, null), + new AnalyzedCommit(null, null, null, null, null, "R1", ChangeCategory.REMOVED, null, null, SemVerChangeLevel.NONE, null), + new AnalyzedCommit(null, null, null, null, null, "F1", ChangeCategory.FIXED, null, null, SemVerChangeLevel.PATCH, null), + new AnalyzedCommit(null, null, null, null, null, "S1", ChangeCategory.SECURITY, null, null, SemVerChangeLevel.PATCH, null), + new AnalyzedCommit(null, null, null, null, null, "A1", ChangeCategory.ADDED, null, null, SemVerChangeLevel.MINOR, null), + new AnalyzedCommit(null, null, null, null, null, "C1", ChangeCategory.CHANGED, null, null, SemVerChangeLevel.MINOR, null), + new AnalyzedCommit(null, null, null, null, null, "B1", ChangeCategory.OTHER, null, null, SemVerChangeLevel.MAJOR, null) ) ); } diff --git a/semver-analyzer-conventional/src/main/java/io/github/sam42r/semver/analyzer/ConventionalCommitAnalyzer.java b/semver-analyzer-conventional/src/main/java/io/github/sam42r/semver/analyzer/ConventionalCommitAnalyzer.java index 0611c9a..74e7c67 100644 --- a/semver-analyzer-conventional/src/main/java/io/github/sam42r/semver/analyzer/ConventionalCommitAnalyzer.java +++ b/semver-analyzer-conventional/src/main/java/io/github/sam42r/semver/analyzer/ConventionalCommitAnalyzer.java @@ -76,7 +76,7 @@ private AnalyzedCommit analyzeCommit(@NonNull Commit commit, @NonNull Remote rem var headerMatcher = Pattern.compile(COMMIT_HEADER_PATTERN).matcher(header); if (header.isEmpty() || !headerMatcher.find()) { - return new AnalyzedCommit(commit, null, null, null, null, null, null, null, null, null); + return new AnalyzedCommit(commit, null, null, null, null, null, null, null, null, null, null); } var type = headerMatcher.group("TYPE"); @@ -99,6 +99,7 @@ private AnalyzedCommit analyzeCommit(@NonNull Commit commit, @NonNull Remote rem return new AnalyzedCommit( commit, + providerSpec.issueUrl(remote, commit.id()), header, body, footer, diff --git a/semver-analyzer-conventional/src/test/java/io/github/sam42r/semver/analyzer/ConventionalCommitAnalyzerTest.java b/semver-analyzer-conventional/src/test/java/io/github/sam42r/semver/analyzer/ConventionalCommitAnalyzerTest.java index 87441ed..897dcd8 100644 --- a/semver-analyzer-conventional/src/test/java/io/github/sam42r/semver/analyzer/ConventionalCommitAnalyzerTest.java +++ b/semver-analyzer-conventional/src/test/java/io/github/sam42r/semver/analyzer/ConventionalCommitAnalyzerTest.java @@ -37,7 +37,7 @@ void shouldFindFix() { """) ), Remote.of("git@github.com:Sam42R/semver-maven-plugin.git"), - new ProviderSpec("%s://%s/%s/%s/%s") + new ProviderSpec("%s://%s/%s/%s/%s", "%s://%s/%s/%s/%s") ); assertThat(actual).containsExactly( @@ -50,6 +50,7 @@ void shouldFindFix() { refs #42 """), + "https://github.com/Sam42R/semver-maven-plugin/42", "fix(scm): set clean commit message", "* added scope for commit messages", "refs #42", @@ -78,7 +79,7 @@ void shouldFindBreakingChanges() { "fix(scm)!: set clean commit message") ), Remote.of("git@github.com:Sam42R/semver-maven-plugin.git"), - new ProviderSpec("%s://%s/%s/%s/%s") + new ProviderSpec("%s://%s/%s/%s/%s", "%s://%s/%s/%s/%s") ); assertThat(actual).containsExactly( @@ -92,6 +93,7 @@ void shouldFindBreakingChanges() { BREAKING CHANGE: breaks everything refs #42 """), + "https://github.com/Sam42R/semver-maven-plugin/42", "fix(scm): set clean commit message", "* added scope for commit messages", "BREAKING CHANGE: breaks everything%srefs #42".formatted(System.lineSeparator()), @@ -103,6 +105,7 @@ void shouldFindBreakingChanges() { List.of(new Issue("42", "https://github.com/Sam42R/semver-maven-plugin/42"))), new AnalyzedCommit( new Commit("42", Instant.EPOCH, "JUnit", "fix(scm)!: set clean commit message"), + "https://github.com/Sam42R/semver-maven-plugin/42", "fix(scm)!: set clean commit message", "", "", diff --git a/semver-analyzer-gitmoji/src/main/java/io/github/sam42r/semver/analyzer/GitMojiCommitAnalyzer.java b/semver-analyzer-gitmoji/src/main/java/io/github/sam42r/semver/analyzer/GitMojiCommitAnalyzer.java index c35fa27..ed0ba03 100644 --- a/semver-analyzer-gitmoji/src/main/java/io/github/sam42r/semver/analyzer/GitMojiCommitAnalyzer.java +++ b/semver-analyzer-gitmoji/src/main/java/io/github/sam42r/semver/analyzer/GitMojiCommitAnalyzer.java @@ -43,7 +43,7 @@ private AnalyzedCommit analyzeCommit(@NonNull Commit commit, @NonNull Remote rem var matcher = pattern.matcher(commit.message()); if (!commit.message().startsWith(":") || !matcher.find()) { - return new AnalyzedCommit(commit, null, null, null, null, null, null, null, null, null); + return new AnalyzedCommit(commit, null, null, null, null, null, null, null, null, null, null); } var intention = matcher.group("INTENTION"); @@ -62,6 +62,7 @@ private AnalyzedCommit analyzeCommit(@NonNull Commit commit, @NonNull Remote rem return new AnalyzedCommit( commit, + providerSpec.commitUrl(remote, commit.id()), commit.message(), null, null, diff --git a/semver-analyzer-gitmoji/src/test/java/io/github/sam42r/semver/analyzer/GitMojiCommitAnalyzerTest.java b/semver-analyzer-gitmoji/src/test/java/io/github/sam42r/semver/analyzer/GitMojiCommitAnalyzerTest.java index 590afad..a601b27 100644 --- a/semver-analyzer-gitmoji/src/test/java/io/github/sam42r/semver/analyzer/GitMojiCommitAnalyzerTest.java +++ b/semver-analyzer-gitmoji/src/test/java/io/github/sam42r/semver/analyzer/GitMojiCommitAnalyzerTest.java @@ -31,12 +31,13 @@ void shouldFindFixAndFeat() { new Commit("42.2", Instant.EPOCH, "JUnit", ":sparkles: add awesome things") ), Remote.of("git@github.com:Sam42R/semver-maven-plugin.git"), - new ProviderSpec("%s://%s/%s/%s/%s") + new ProviderSpec("%s://%s/%s/%s/%s", "%s://%s/%s/%s/%s") ); assertThat(actual).containsExactlyInAnyOrder( new AnalyzedCommit( new Commit("42.1", Instant.EPOCH, "JUnit", ":bug: (scm) set clean commit message #42"), + "https://github.com/Sam42R/semver-maven-plugin/42.1", ":bug: (scm) set clean commit message #42", null, null, @@ -48,6 +49,7 @@ void shouldFindFixAndFeat() { List.of(new Issue("42", "https://github.com/Sam42R/semver-maven-plugin/42"))), new AnalyzedCommit( new Commit("42.2", Instant.EPOCH, "JUnit", ":sparkles: add awesome things"), + "https://github.com/Sam42R/semver-maven-plugin/42.2", ":sparkles: add awesome things", null, null, @@ -66,12 +68,13 @@ void shouldFindBreaking() { new Commit("42.3", Instant.EPOCH, "JUnit", ":boom: (void): break some glass #42") ), Remote.of("git@github.com:Sam42R/semver-maven-plugin.git"), - new ProviderSpec("%s://%s/%s/%s/%s") + new ProviderSpec("%s://%s/%s/%s/%s", "%s://%s/%s/%s/%s") ); assertThat(actual).containsExactly( new AnalyzedCommit( new Commit("42.3", Instant.EPOCH, "JUnit", ":boom: (void): break some glass #42"), + "https://github.com/Sam42R/semver-maven-plugin/42.3", ":boom: (void): break some glass #42", null, null, @@ -90,12 +93,13 @@ void shouldFindOther() { new Commit("42.4", Instant.EPOCH, "JUnit", ":white_check_mark: add test for something") ), Remote.of("git@github.com:Sam42R/semver-maven-plugin.git"), - new ProviderSpec("%s://%s/%s/%s/%s") + new ProviderSpec("%s://%s/%s/%s/%s", "%s://%s/%s/%s/%s") ); assertThat(actual).containsExactly( new AnalyzedCommit( new Commit("42.4", Instant.EPOCH, "JUnit", ":white_check_mark: add test for something"), + "https://github.com/Sam42R/semver-maven-plugin/42.4", ":white_check_mark: add test for something", null, null, diff --git a/semver-changelog-html/src/main/resources/templates/default/default.html b/semver-changelog-html/src/main/resources/templates/default/default.html index 638cad0..2d709dc 100644 --- a/semver-changelog-html/src/main/resources/templates/default/default.html +++ b/semver-changelog-html/src/main/resources/templates/default/default.html @@ -52,6 +52,10 @@

Changelog

 () + +   + diff --git a/semver-changelog-html/src/main/resources/templates/missive/missive.html b/semver-changelog-html/src/main/resources/templates/missive/missive.html index 96d3f3c..79f5417 100644 --- a/semver-changelog-html/src/main/resources/templates/missive/missive.html +++ b/semver-changelog-html/src/main/resources/templates/missive/missive.html @@ -66,6 +66,10 @@

Changelog

Lorem ipsum () + + + diff --git a/semver-changelog-html/src/main/resources/templates/spinal/spinal.html b/semver-changelog-html/src/main/resources/templates/spinal/spinal.html index 7192a78..7e120e8 100644 --- a/semver-changelog-html/src/main/resources/templates/spinal/spinal.html +++ b/semver-changelog-html/src/main/resources/templates/spinal/spinal.html @@ -59,6 +59,10 @@

() + +   +

diff --git a/semver-changelog-html/src/test/java/io/github/sam42r/semver/changelog/HtmlRendererTest.java b/semver-changelog-html/src/test/java/io/github/sam42r/semver/changelog/HtmlRendererTest.java index b502c0b..97591ad 100644 --- a/semver-changelog-html/src/test/java/io/github/sam42r/semver/changelog/HtmlRendererTest.java +++ b/semver-changelog-html/src/test/java/io/github/sam42r/semver/changelog/HtmlRendererTest.java @@ -6,6 +6,7 @@ import io.github.sam42r.semver.model.analyze.ChangeCategory; import io.github.sam42r.semver.model.analyze.SemVerChangeLevel; import io.github.sam42r.semver.model.scm.Commit; +import org.apache.commons.codec.digest.DigestUtils; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; @@ -73,7 +74,7 @@ private VersionInfo release(String version) { private List analyzedCommits() { return List.of( new AnalyzedCommit( - new Commit("42", Instant.EPOCH, "JUnit", + new Commit(DigestUtils.sha256Hex("42.1"), Instant.EPOCH, "JUnit", """ feat(scm): Lorem ipsum @@ -81,6 +82,7 @@ private List analyzedCommits() { refs #42 """), + null, "feat(scm): Lorem ipsum", "* Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam", "refs #42", @@ -92,7 +94,7 @@ private List analyzedCommits() { null ), new AnalyzedCommit( - new Commit("42", Instant.EPOCH, "JUnit", + new Commit(DigestUtils.sha256Hex("42.2"), Instant.EPOCH, "JUnit", """ fix(scm): Lorem ipsum @@ -100,6 +102,7 @@ private List analyzedCommits() { refs #42 """), + null, "fix(scm): Lorem ipsum", "* Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam", "refs #42", @@ -111,7 +114,7 @@ private List analyzedCommits() { null ), new AnalyzedCommit( - new Commit("42", Instant.EPOCH, "JUnit", + new Commit(DigestUtils.sha256Hex("42.3"), Instant.EPOCH, "JUnit", """ chore(scm): Lorem ipsum @@ -119,6 +122,7 @@ private List analyzedCommits() { refs #42 """), + "https://junit.org/test/%s".formatted(DigestUtils.sha256Hex("42.3")), "chore(scm): Lorem ipsum", "* Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam", "refs #42", diff --git a/semver-changelog-markup/src/main/java/io/github/sam42r/semver/changelog/MarkupRenderer.java b/semver-changelog-markup/src/main/java/io/github/sam42r/semver/changelog/MarkupRenderer.java index 1fab652..a1da05e 100644 --- a/semver-changelog-markup/src/main/java/io/github/sam42r/semver/changelog/MarkupRenderer.java +++ b/semver-changelog-markup/src/main/java/io/github/sam42r/semver/changelog/MarkupRenderer.java @@ -1,17 +1,21 @@ package io.github.sam42r.semver.changelog; import com.github.mustachejava.DefaultMustacheFactory; -import io.github.sam42r.semver.model.changelog.VersionInfo; +import io.github.sam42r.semver.changelog.model.Link; +import io.github.sam42r.semver.changelog.model.RenderedCommit; import io.github.sam42r.semver.model.analyze.AnalyzedCommit; import io.github.sam42r.semver.model.analyze.ChangeCategory; +import io.github.sam42r.semver.model.changelog.VersionInfo; import lombok.NonNull; import lombok.RequiredArgsConstructor; +import org.apache.commons.codec.binary.StringUtils; import org.apache.commons.codec.digest.DigestUtils; import java.io.*; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.stream.Collectors; @@ -65,25 +69,32 @@ public class MarkupRenderer implements ChangelogRenderer { context.put("release", versionInfo); context.put("hasAdded", categorizedCommits.containsKey(ChangeCategory.ADDED)); - context.put("added", categorizedCommits.get(ChangeCategory.ADDED)); + context.put("added", categorizedCommits.getOrDefault(ChangeCategory.ADDED, Collections.emptyList()) + .stream().map(this::renderedCommit).toList()); context.put("hasChanges", categorizedCommits.containsKey(ChangeCategory.CHANGED)); - context.put("changes", categorizedCommits.get(ChangeCategory.CHANGED)); + context.put("changes", categorizedCommits.getOrDefault(ChangeCategory.CHANGED, Collections.emptyList()) + .stream().map(this::renderedCommit).toList()); context.put("hasDeprecated", categorizedCommits.containsKey(ChangeCategory.DEPRECATED)); - context.put("deprecated", categorizedCommits.get(ChangeCategory.DEPRECATED)); + context.put("deprecated", categorizedCommits.getOrDefault(ChangeCategory.DEPRECATED, Collections.emptyList()) + .stream().map(this::renderedCommit).toList()); context.put("hasRemoved", categorizedCommits.containsKey(ChangeCategory.REMOVED)); - context.put("removed", categorizedCommits.get(ChangeCategory.REMOVED)); + context.put("removed", categorizedCommits.getOrDefault(ChangeCategory.REMOVED, Collections.emptyList()) + .stream().map(this::renderedCommit).toList()); context.put("hasPatches", categorizedCommits.containsKey(ChangeCategory.FIXED)); - context.put("patches", categorizedCommits.get(ChangeCategory.FIXED)); + context.put("patches", categorizedCommits.getOrDefault(ChangeCategory.FIXED, Collections.emptyList()) + .stream().map(this::renderedCommit).toList()); context.put("hasSecurity", categorizedCommits.containsKey(ChangeCategory.SECURITY)); - context.put("securities", categorizedCommits.get(ChangeCategory.SECURITY)); + context.put("securities", categorizedCommits.getOrDefault(ChangeCategory.SECURITY, Collections.emptyList()) + .stream().map(this::renderedCommit).toList()); context.put("hasOthers", categorizedCommits.containsKey(ChangeCategory.OTHER)); - context.put("others", categorizedCommits.get(ChangeCategory.OTHER)); + context.put("others", categorizedCommits.getOrDefault(ChangeCategory.OTHER, Collections.emptyList()) + .stream().map(this::renderedCommit).toList()); context.put("renderHeader", !alreadyExists); context.put("renderFooter", !alreadyExists); @@ -106,4 +117,33 @@ public class MarkupRenderer implements ChangelogRenderer { throw new UncheckedIOException(e); } } + + /** + * Transform {@link AnalyzedCommit} to Mustache compliant {@link RenderedCommit}.
+ * (Since Mustache is more or less logic less we have to use rendering optimized model) + * + * @param analyzedCommit the {@link AnalyzedCommit} to transform + * @return Mustache compliant {@link RenderedCommit} + */ + private RenderedCommit renderedCommit(AnalyzedCommit analyzedCommit) { + return new RenderedCommit( + analyzedCommit.header(), + analyzedCommit.url() == null || analyzedCommit.url().isBlank() ? + null : + new Link(trimToLength(analyzedCommit.commit().id(), 7), analyzedCommit.url()), + analyzedCommit.issues() == null || analyzedCommit.issues().isEmpty() ? + Collections.emptyList() : + analyzedCommit.issues().stream() + .filter(v -> v.url() != null && !v.url().isBlank()) + .map(v -> new Link(v.id(), v.url())) + .toList() + ); + } + + private String trimToLength(String string, int length) { + if (string != null && string.length() > length) { + return string.substring(0, length); + } + return string; + } } diff --git a/semver-changelog-markup/src/main/java/io/github/sam42r/semver/changelog/model/Link.java b/semver-changelog-markup/src/main/java/io/github/sam42r/semver/changelog/model/Link.java new file mode 100644 index 0000000..3d4a51d --- /dev/null +++ b/semver-changelog-markup/src/main/java/io/github/sam42r/semver/changelog/model/Link.java @@ -0,0 +1,4 @@ +package io.github.sam42r.semver.changelog.model; + +public record Link(String name, String href) { +} diff --git a/semver-changelog-markup/src/main/java/io/github/sam42r/semver/changelog/model/RenderedCommit.java b/semver-changelog-markup/src/main/java/io/github/sam42r/semver/changelog/model/RenderedCommit.java new file mode 100644 index 0000000..1205f66 --- /dev/null +++ b/semver-changelog-markup/src/main/java/io/github/sam42r/semver/changelog/model/RenderedCommit.java @@ -0,0 +1,6 @@ +package io.github.sam42r.semver.changelog.model; + +import java.util.List; + +public record RenderedCommit(String message, Link reference, List issues) { +} diff --git a/semver-changelog-markup/src/main/resources/io/github/sam42r/semver/changelog/changelog.mustache b/semver-changelog-markup/src/main/resources/io/github/sam42r/semver/changelog/changelog.mustache index c9913aa..711885e 100644 --- a/semver-changelog-markup/src/main/resources/io/github/sam42r/semver/changelog/changelog.mustache +++ b/semver-changelog-markup/src/main/resources/io/github/sam42r/semver/changelog/changelog.mustache @@ -9,43 +9,43 @@ ### Added {{/hasAdded}} {{#added}} -- {{header}} {{#issues}}([#{{id}}]({{url}})){{/issues}} +- {{message}} {{#issues}}([#{{name}}]({{href}})){{/issues}} {{#reference}}[{{name}}]({{href}}){{/reference}} {{/added}} {{#hasChanges}} ### Changed {{/hasChanges}} {{#changes}} -- {{header}} {{#issues}}([#{{id}}]({{url}})){{/issues}} +- {{message}} {{#issues}}([#{{name}}]({{href}})){{/issues}} {{#reference}}[{{name}}]({{href}}){{/reference}} {{/changes}} {{#hasDeprecated}} ### Deprecated {{/hasDeprecated}} {{#deprecated}} -- {{header}} {{#issues}}([#{{id}}]({{url}})){{/issues}} +- {{message}} {{#issues}}([#{{name}}]({{href}})){{/issues}} {{#reference}}[{{name}}]({{href}}){{/reference}} {{/deprecated}} {{#hasRemoved}} ### Removed {{/hasRemoved}} {{#removed}} -- {{header}} {{#issues}}([#{{id}}]({{url}})){{/issues}} +- {{message}} {{#issues}}([#{{name}}]({{href}})){{/issues}} {{#reference}}[{{name}}]({{href}}){{/reference}} {{/removed}} {{#hasPatches}} ### Fixed {{/hasPatches}} {{#patches}} -- {{header}} {{#issues}}([#{{id}}]({{url}})){{/issues}} +- {{message}} {{#issues}}([#{{name}}]({{href}})){{/issues}} {{#reference}}[{{name}}]({{href}}){{/reference}} {{/patches}} {{#hasSecurity}} ### Security {{/hasSecurity}} {{#securities}} -- {{header}} {{#issues}}([#{{id}}]({{url}})){{/issues}} +- {{message}} {{#issues}}([#{{name}}]({{href}})){{/issues}} {{#reference}}[{{name}}]({{href}}){{/reference}} {{/securities}} {{#hasOthers}} ### Others {{/hasOthers}} {{#others}} -- {{header}} {{#issues}}([#{{id}}]({{url}})){{/issues}} +- {{message}} {{#issues}}([#{{name}}]({{href}})){{/issues}} {{#reference}}[{{name}}]({{href}}){{/reference}} {{/others}} {{#renderFooter}} diff --git a/semver-changelog-markup/src/test/java/io/github/sam42r/semver/changelog/MarkupRendererTest.java b/semver-changelog-markup/src/test/java/io/github/sam42r/semver/changelog/MarkupRendererTest.java index 2195d26..3a95ed3 100644 --- a/semver-changelog-markup/src/test/java/io/github/sam42r/semver/changelog/MarkupRendererTest.java +++ b/semver-changelog-markup/src/test/java/io/github/sam42r/semver/changelog/MarkupRendererTest.java @@ -6,6 +6,7 @@ import io.github.sam42r.semver.model.analyze.SemVerChangeLevel; import io.github.sam42r.semver.model.changelog.VersionInfo; import io.github.sam42r.semver.model.scm.Commit; +import org.apache.commons.codec.digest.DigestUtils; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -85,7 +86,7 @@ private VersionInfo release(String version) { private List analyzedCommits() { return List.of( new AnalyzedCommit( - new Commit("42", Instant.EPOCH, "JUnit", + new Commit(DigestUtils.sha256Hex("42"), Instant.EPOCH, "JUnit", """ fix(scm): set clean commit message @@ -93,6 +94,7 @@ private List analyzedCommits() { refs #42 """), + "https://junit.org/test/%s".formatted(DigestUtils.sha256Hex("42")), "fix(scm): set clean commit message", "* added scope for commit messages", "refs #42", diff --git a/semver-maven-model/src/main/java/io/github/sam42r/semver/model/analyze/AnalyzedCommit.java b/semver-maven-model/src/main/java/io/github/sam42r/semver/model/analyze/AnalyzedCommit.java index 5764dfa..78b1147 100644 --- a/semver-maven-model/src/main/java/io/github/sam42r/semver/model/analyze/AnalyzedCommit.java +++ b/semver-maven-model/src/main/java/io/github/sam42r/semver/model/analyze/AnalyzedCommit.java @@ -5,8 +5,24 @@ import java.util.List; import java.util.function.Predicate; +/** + * Analyzed {@link Commit} based on commit-message analysis. + * + * @param commit scm {@link Commit} + * @param url provider specific commit URL + * @param header commit header + * @param body commit body + * @param footer commit footer + * @param type commit type + * @param category commit {@link ChangeCategory} + * @param scope commit scope + * @param subject commit subject + * @param level commit {@link SemVerChangeLevel} + * @param issues list of referenced {@link Issue}'s + */ public record AnalyzedCommit( Commit commit, + String url, String header, String body, String footer, diff --git a/semver-maven-model/src/main/java/io/github/sam42r/semver/model/release/ProviderSpec.java b/semver-maven-model/src/main/java/io/github/sam42r/semver/model/release/ProviderSpec.java index d08a484..b9f37a4 100644 --- a/semver-maven-model/src/main/java/io/github/sam42r/semver/model/release/ProviderSpec.java +++ b/semver-maven-model/src/main/java/io/github/sam42r/semver/model/release/ProviderSpec.java @@ -2,9 +2,13 @@ import io.github.sam42r.semver.model.scm.Remote; -public record ProviderSpec(String issuePattern) { +public record ProviderSpec(String issuePattern, String commitPattern) { public String issueUrl(Remote remote, String id) { return issuePattern().formatted(remote.scheme(), remote.host(), remote.group(), remote.project(), id); } + + public String commitUrl(Remote remote, String id) { + return commitPattern().formatted(remote.scheme(), remote.host(), remote.group(), remote.project(), id); + } } diff --git a/semver-release-github/src/main/java/io/github/sam42r/semver/release/DefaultGithubPublisher.java b/semver-release-github/src/main/java/io/github/sam42r/semver/release/DefaultGithubPublisher.java index c025500..375d44a 100644 --- a/semver-release-github/src/main/java/io/github/sam42r/semver/release/DefaultGithubPublisher.java +++ b/semver-release-github/src/main/java/io/github/sam42r/semver/release/DefaultGithubPublisher.java @@ -54,7 +54,8 @@ public void publish( @Override public ProviderSpec providerSpec() { return new ProviderSpec( - "%s://%s/%s/%s/issues/%s" + "%s://%s/%s/%s/issues/%s", + "%s://%s/%s/%s/commit/%s" ); } } diff --git a/semver-release-gitlab/src/main/java/io/github/sam42r/semver/release/GitlabPublisher.java b/semver-release-gitlab/src/main/java/io/github/sam42r/semver/release/GitlabPublisher.java index ce446af..28b8672 100644 --- a/semver-release-gitlab/src/main/java/io/github/sam42r/semver/release/GitlabPublisher.java +++ b/semver-release-gitlab/src/main/java/io/github/sam42r/semver/release/GitlabPublisher.java @@ -79,7 +79,8 @@ private GitlabRelease generatePayload(ReleaseInfo releaseInfo) { @Override public ProviderSpec providerSpec() { return new ProviderSpec( - "%s://%s/%s/%s/-/issues/%s" + "%s://%s/%s/%s/-/issues/%s", + "%s://%s/%s/%s@%s" ); } } From 585024289fdccf29c194038797cf766a7fbf961d Mon Sep 17 00:00:00 2001 From: Rene R Date: Mon, 9 Feb 2026 11:54:23 +0100 Subject: [PATCH 4/4] :sparkles: generate tag-compare links #41 --- .../ConventionalCommitAnalyzerTest.java | 4 +- .../analyzer/GitMojiCommitAnalyzerTest.java | 6 +-- .../resources/templates/default/default.html | 5 +-- .../resources/templates/missive/missive.html | 4 +- .../resources/templates/spinal/spinal.html | 5 +-- .../semver/changelog/HtmlRendererTest.java | 7 +++- .../semver/changelog/MarkupRenderer.java | 1 - .../semver/changelog/changelog.mustache | 2 +- .../semver/changelog/MarkupRendererTest.java | 11 ++++-- .../semver/model/changelog/VersionInfo.java | 2 +- .../semver/model/release/ProviderSpec.java | 6 ++- .../sam42r/semver/SemanticReleaseMojo.java | 37 +++++++++++++------ .../github/sam42r/semver/model/Version.java | 4 ++ .../release/DefaultGithubPublisher.java | 3 +- .../semver/release/GitlabPublisher.java | 3 +- 15 files changed, 65 insertions(+), 35 deletions(-) diff --git a/semver-analyzer-conventional/src/test/java/io/github/sam42r/semver/analyzer/ConventionalCommitAnalyzerTest.java b/semver-analyzer-conventional/src/test/java/io/github/sam42r/semver/analyzer/ConventionalCommitAnalyzerTest.java index 897dcd8..4b9984f 100644 --- a/semver-analyzer-conventional/src/test/java/io/github/sam42r/semver/analyzer/ConventionalCommitAnalyzerTest.java +++ b/semver-analyzer-conventional/src/test/java/io/github/sam42r/semver/analyzer/ConventionalCommitAnalyzerTest.java @@ -37,7 +37,7 @@ void shouldFindFix() { """) ), Remote.of("git@github.com:Sam42R/semver-maven-plugin.git"), - new ProviderSpec("%s://%s/%s/%s/%s", "%s://%s/%s/%s/%s") + new ProviderSpec("%s://%s/%s/%s/%s", "%s://%s/%s/%s/%s", "%s://%s/%s/%s/%s...%s") ); assertThat(actual).containsExactly( @@ -79,7 +79,7 @@ void shouldFindBreakingChanges() { "fix(scm)!: set clean commit message") ), Remote.of("git@github.com:Sam42R/semver-maven-plugin.git"), - new ProviderSpec("%s://%s/%s/%s/%s", "%s://%s/%s/%s/%s") + new ProviderSpec("%s://%s/%s/%s/%s", "%s://%s/%s/%s/%s", "%s://%s/%s/%s/%s...%s") ); assertThat(actual).containsExactly( diff --git a/semver-analyzer-gitmoji/src/test/java/io/github/sam42r/semver/analyzer/GitMojiCommitAnalyzerTest.java b/semver-analyzer-gitmoji/src/test/java/io/github/sam42r/semver/analyzer/GitMojiCommitAnalyzerTest.java index a601b27..cd2d0a7 100644 --- a/semver-analyzer-gitmoji/src/test/java/io/github/sam42r/semver/analyzer/GitMojiCommitAnalyzerTest.java +++ b/semver-analyzer-gitmoji/src/test/java/io/github/sam42r/semver/analyzer/GitMojiCommitAnalyzerTest.java @@ -31,7 +31,7 @@ void shouldFindFixAndFeat() { new Commit("42.2", Instant.EPOCH, "JUnit", ":sparkles: add awesome things") ), Remote.of("git@github.com:Sam42R/semver-maven-plugin.git"), - new ProviderSpec("%s://%s/%s/%s/%s", "%s://%s/%s/%s/%s") + new ProviderSpec("%s://%s/%s/%s/%s", "%s://%s/%s/%s/%s", "%s://%s/%s/%s/%s...%s") ); assertThat(actual).containsExactlyInAnyOrder( @@ -68,7 +68,7 @@ void shouldFindBreaking() { new Commit("42.3", Instant.EPOCH, "JUnit", ":boom: (void): break some glass #42") ), Remote.of("git@github.com:Sam42R/semver-maven-plugin.git"), - new ProviderSpec("%s://%s/%s/%s/%s", "%s://%s/%s/%s/%s") + new ProviderSpec("%s://%s/%s/%s/%s", "%s://%s/%s/%s/%s", "%s://%s/%s/%s/%s...%s") ); assertThat(actual).containsExactly( @@ -93,7 +93,7 @@ void shouldFindOther() { new Commit("42.4", Instant.EPOCH, "JUnit", ":white_check_mark: add test for something") ), Remote.of("git@github.com:Sam42R/semver-maven-plugin.git"), - new ProviderSpec("%s://%s/%s/%s/%s", "%s://%s/%s/%s/%s") + new ProviderSpec("%s://%s/%s/%s/%s", "%s://%s/%s/%s/%s", "%s://%s/%s/%s/%s...%s") ); assertThat(actual).containsExactly( diff --git a/semver-changelog-html/src/main/resources/templates/default/default.html b/semver-changelog-html/src/main/resources/templates/default/default.html index 2d709dc..fc0b6f3 100644 --- a/semver-changelog-html/src/main/resources/templates/default/default.html +++ b/semver-changelog-html/src/main/resources/templates/default/default.html @@ -20,9 +20,8 @@

Changelog

-

- v0.0.1 +

+ v0.0.1

- - v0.0.1 - + v0.0.1 (1970-01-01) diff --git a/semver-changelog-html/src/main/resources/templates/spinal/spinal.html b/semver-changelog-html/src/main/resources/templates/spinal/spinal.html index 7e120e8..86d0eaf 100644 --- a/semver-changelog-html/src/main/resources/templates/spinal/spinal.html +++ b/semver-changelog-html/src/main/resources/templates/spinal/spinal.html @@ -24,9 +24,8 @@

-
- v0.0.1 +
+ v0.0.1