Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/external-resources.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
## Third Party
- [Mustache Templates](https://github.com/mustache/mustache.github.com)
- [JGit Authentication](https://www.codeaffine.com/2014/12/09/jgit-authentication/)
- [SVNKit](https://svnkit.com/documentation.html)

## Others
- [Git documentation](https://git-scm.com/doc)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ private LatestReleaseInfo getLatestRelease(SCMProvider scmProvider) throws MojoE
var tags = scmProvider.readTags();
var commits = scmProvider.readCommits(null);

var latestTagOpt = tags.max(new TagVersionComparator(tagFormat));
var latestTagOpt = tags.filter(t -> Version.matchesPattern(t.getName(), tagFormat)).max(new TagVersionComparator(tagFormat));
var latestCommitOpt = latestTagOpt.map(Tag::getCommitId)
.or(() -> commits.min(Comparator.comparing(Commit::getTimestamp)).map(Commit::getId));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@
import org.apache.commons.text.StringSubstitutor;

import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* @author Sam42R
*/
@Getter
@EqualsAndHashCode
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public final class Version {
Expand Down Expand Up @@ -42,6 +44,18 @@ public static Version of(@NonNull String version) {
}

public static Version of(@NonNull String version, @NonNull String tagFormat) {
var matcher = getMatcher(version, tagFormat);
if (matcher.find()) {
return new Version(
Integer.parseInt(matcher.group(Type.MAJOR.name())),
Integer.parseInt(matcher.group(Type.MINOR.name())),
Integer.parseInt(matcher.group(Type.PATCH.name())),
tagFormat);
}
throw new IllegalArgumentException("Could not create version for '%s' with tag format '%s'".formatted(version, tagFormat));
}

private static Matcher getMatcher(String version, String tagFormat) {
if (!tagFormat.contains(VERSION_PLACEHOLDER)) {
throw new IllegalArgumentException(
"Given tag format '%s' does not contain required version placeholder '%s'".formatted(
Expand All @@ -55,15 +69,7 @@ public static Version of(@NonNull String version, @NonNull String tagFormat) {
PLACEHOLDER_SUFFIX);

var pattern = Pattern.compile(regex);
var matcher = pattern.matcher(version);
if (matcher.find()) {
return new Version(
Integer.parseInt(matcher.group(Type.MAJOR.name())),
Integer.parseInt(matcher.group(Type.MINOR.name())),
Integer.parseInt(matcher.group(Type.PATCH.name())),
tagFormat);
}
throw new IllegalArgumentException("Could not create version for '%s' with tag format '%s'".formatted(version, tagFormat));
return pattern.matcher(version);
}

public static Version of(@NonNull int major, int minor, int patch) {
Expand All @@ -74,6 +80,10 @@ public static Version of(@NonNull int major, int minor, int patch, String tagFor
return new Version(major, minor, patch, tagFormat);
}

public static boolean matchesPattern(String version, String tagFormat) {
return getMatcher(version, tagFormat).find();
}

public void increment(@NonNull Type type) {
if (Type.MAJOR.equals(type)) {
this.major++;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import org.junit.jupiter.api.Test;

import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

Expand Down Expand Up @@ -52,4 +54,19 @@ void shouldIncrement() {
assertThat(actual).hasToString("2.0.0");
assertThat(actual.toTag()).isEqualTo("v2.0.0");
}

@Test
void shouldFilterInvalidVersions() {
var tags = List.of("v0.0.1", "r-0-0-1", "test", "v0.0.2");

var actual = tags.stream()
.filter(v -> Version.matchesPattern(v, Version.TAG_FORMAT_DEFAULT))
.map(Version::of)
.toList();

assertThat(actual).containsExactlyInAnyOrder(
Version.of(0,0,1),
Version.of(0,0,2)
);
}
}
11 changes: 11 additions & 0 deletions semver-scm-api/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# SCM provider distribution

```mermaid
%%{init: {'theme': 'base', 'themeVariables': {'pie1': 'lightsteelblue', 'pie2': 'goldenrod', 'pie3': 'ghostwhite'}}}%%
pie title "SCM provider ditribution"
"git": 70
"svn": 15
"others": 15
```

source: [worldmetrics.org](https://worldmetrics.org/version-control-systems-industry-statistics/)
17 changes: 17 additions & 0 deletions semver-scm-api/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,21 @@
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.4</version>
<executions>
<execution>
<goals>
<goal>test-jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,7 @@
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NonNull;
import org.apache.maven.scm.ChangeSet;
import org.apache.maven.scm.ScmException;
import org.apache.maven.scm.ScmFileSet;
import org.apache.maven.scm.ScmRevision;
import org.apache.maven.scm.*;
import org.apache.maven.scm.command.changelog.ChangeLogScmRequest;
import org.apache.maven.scm.manager.BasicScmManager;
import org.apache.maven.scm.manager.NoSuchScmProviderException;
Expand All @@ -20,6 +17,10 @@
import org.apache.maven.scm.repository.ScmRepositoryException;

import java.nio.file.Path;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Comparator;
import java.util.Date;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Stream;
Expand All @@ -32,6 +33,8 @@
abstract class AbstractScmProvider implements SCMProvider {

private static final Predicate<ChangeSet> hasTag = changeSet -> changeSet.getTags() != null && !changeSet.getTags().isEmpty();
private static final Predicate<ChangeSet> hasTagPath = changeSet -> changeSet.getFiles() != null &&
!changeSet.getFiles().isEmpty() && changeSet.getFiles().get(0).getName().contains("/tags/");

private final Path path;
private final String username;
Expand All @@ -57,6 +60,10 @@ protected AbstractScmProvider(
this.scmManager.setScmProvider(providerType, provider);
}

protected Path getFileBase() {
return getPath();
}

@Override
public @NonNull Stream<Commit> readCommits(String fromCommitId) throws SCMException {
return readCommits(fromCommitId, null);
Expand All @@ -69,19 +76,23 @@ protected AbstractScmProvider(
var changeLogScmRequest = new ChangeLogScmRequest(repository, new ScmFileSet(path.toFile()));
if (fromCommitId != null) {
changeLogScmRequest.setStartRevision(new ScmRevision(fromCommitId));
} else {
changeLogScmRequest.setStartDate(Date.from(Instant.EPOCH));
}
if (toCommitId != null) {
changeLogScmRequest.setEndRevision(new ScmRevision(toCommitId));
}

var changeLogScmResult = scmManager.changeLog(changeLogScmRequest);
check(changeLogScmResult);

return changeLogScmResult.getChangeLog().getChangeSets().stream()
.filter(v -> v.getComment() != null)
.map(v -> Commit.builder()
.id(v.getRevision())
.timestamp(v.getDate().toInstant())
.timestamp(v.getDate().toInstant().truncatedTo(ChronoUnit.SECONDS))
.author(v.getAuthor())
.message(v.getComment())
.message(v.getComment().trim())
.build());
} catch (ScmException e) {
throw new SCMException(e);
Expand All @@ -94,26 +105,39 @@ protected AbstractScmProvider(
var repository = getScmRepository();

var changeLogScmRequest = new ChangeLogScmRequest(repository, new ScmFileSet(path.toFile()));
changeLogScmRequest.setStartDate(Date.from(Instant.EPOCH));

var changeLogScmResult = scmManager.changeLog(changeLogScmRequest);
check(changeLogScmResult);

return changeLogScmResult.getChangeLog().getChangeSets().stream()
.filter(hasTag)
.filter(hasTag.or(hasTagPath))
.map(v -> Tag.builder()
.name(v.getTags().get(0))
.name(getTag(v))
.commitId(v.getRevision())
.build());
} catch (ScmException e) {
throw new SCMException(e);
}
}

private String getTag(@NonNull ChangeSet changeSet) {
if (hasTag.test(changeSet)) {
return changeSet.getTags().get(0);
} else if (hasTagPath.test(changeSet)) {
var filename = changeSet.getFiles().get(0).getName();
return filename.substring(filename.lastIndexOf("/") + 1);
}
return null;
}

@Override
public void addFile(@NonNull Path file) throws SCMException {
try {
var repository = getScmRepository();

var addScmResult = scmManager.add(repository, new ScmFileSet(path.toFile(), file.toFile()));
assert addScmResult.isSuccess();
var addScmResult = scmManager.add(repository, new ScmFileSet(getFileBase().toFile(), getFileBase().relativize(file).toFile()));
check(addScmResult);
} catch (ScmException e) {
throw new SCMException(e);
}
Expand All @@ -125,9 +149,9 @@ public void addFile(@NonNull Path file) throws SCMException {
var repository = getScmRepository();

var checkInScmResult = scmManager.checkIn(repository, new ScmFileSet(path.toFile()), message);
var scmRevision = checkInScmResult.getScmRevision();
check(checkInScmResult);

return readCommits(scmRevision, scmRevision).findFirst().orElseThrow();
return readCommits(null).max(Comparator.comparing(Commit::getTimestamp)).orElseThrow();
} catch (ScmException e) {
throw new SCMException(e);
}
Expand All @@ -138,8 +162,8 @@ public void addFile(@NonNull Path file) throws SCMException {
try {
var repository = getScmRepository();

var tagScmResult = scmManager.tag(repository, new ScmFileSet(path.toFile()), name);
assert tagScmResult.isSuccess();
var tagScmResult = scmManager.tag(repository, new ScmFileSet(getFileBase().toFile()), name);
check(tagScmResult);

return readTags().filter(v -> name.equals(v.getName())).findFirst().orElseThrow();
} catch (ScmException e) {
Expand All @@ -163,5 +187,11 @@ protected ScmRepository getScmRepository() throws SCMException {
}
}

private void check(@NonNull ScmResult scmResult) throws SCMException {
if (!scmResult.isSuccess()) {
throw new SCMException(scmResult.getProviderMessage(), new IllegalStateException(scmResult.getCommandOutput()));
}
}

protected abstract Optional<String> getRemoteUrl() throws SCMException;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package io.github.sam42r.semver.scm;

import io.github.sam42r.semver.scm.AbstractScmProvider;
import io.github.sam42r.semver.scm.SCMException;
import io.github.sam42r.semver.scm.SCMProvider;
import org.apache.maven.scm.ScmException;
import org.apache.maven.scm.ScmFileSet;
import org.apache.maven.scm.command.status.StatusScmResult;
import org.junit.jupiter.api.io.TempDir;

import java.nio.file.Path;

public abstract class AbstractScmProviderTest {

@TempDir
protected Path tempDirectory;
protected SCMProvider uut;

protected StatusScmResult status() throws SCMException {
if (uut instanceof AbstractScmProvider abstractScmProvider) {
var path = abstractScmProvider.getPath();
var scmManager = abstractScmProvider.getScmManager();
var scmRepository = abstractScmProvider.getScmRepository();

try {
return scmManager.status(scmRepository, new ScmFileSet(path.toFile()));
} catch (ScmException e) {
throw new SCMException(e);
}
}
throw new IllegalArgumentException("Invalid SCMProvider '%s'".formatted(uut.getClass()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package io.github.sam42r.semver.scm;

import org.apache.maven.scm.ScmFile;
import org.apache.maven.scm.command.status.StatusScmResult;
import org.assertj.core.api.AbstractAssert;
import org.assertj.core.api.Assertions;

public class StatusScmResultAssert extends AbstractAssert<StatusScmResultAssert, StatusScmResult> {

protected StatusScmResultAssert(StatusScmResult statusScmResult) {
super(statusScmResult, StatusScmResultAssert.class);
}

public static StatusScmResultAssert assertThat(StatusScmResult statusScmResult) {
return new StatusScmResultAssert(statusScmResult);
}

public StatusScmResultAssert isSuccess() {
if (!actual.isSuccess()) {
failWithMessage("Expected result to be successful");
}
return this;
}

public StatusScmResultAssert hasChangedFiles() {
if (actual.getChangedFiles().isEmpty()) {
failWithMessage("Expected changed files to be not empty");
}
return this;
}

public StatusScmResultAssert hasNoChangedFiles() {
if (!actual.getChangedFiles().isEmpty()) {
failWithMessage("Expected changed files to be empty");
}
return this;
}

public StatusScmResultAssert containsChangedFilesExactlyInAnyOrder(ScmFile ... changedFiles) {
Assertions.assertThat(actual.getChangedFiles()).containsExactlyInAnyOrder(changedFiles);
return this;
}
}
Loading