fix #187 Add facilities to list and filter tags, find closest previous tag

This commit is contained in:
Simon Baslé
2020-02-21 20:41:45 +01:00
committed by Marcin Grzejszczak
parent e980b1f74e
commit 4b01dbea64
4 changed files with 225 additions and 1 deletions

View File

@@ -19,9 +19,13 @@ package releaser.internal.git;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
import com.jcraft.jsch.IdentityRepository;
import com.jcraft.jsch.JSch;
@@ -44,9 +48,12 @@ import org.eclipse.jgit.api.ResetCommand;
import org.eclipse.jgit.api.TransportConfigCallback;
import org.eclipse.jgit.api.errors.EmptyCommitException;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.CredentialsProvider;
import org.eclipse.jgit.transport.FetchResult;
import org.eclipse.jgit.transport.JschConfigSessionFactory;
@@ -247,11 +254,47 @@ class GitRepo {
}
}
/**
* List all the tags in the repository.
* @return a {@link List} of all the tags in the repository.
*/
Stream<String> listTags() {
try (Git git = this.gitFactory.open(file(this.basedir))) {
final RevWalk walk = new RevWalk(git.getRepository());
List<Ref> allTagsNewestFirst = git.tagList().call();
Collections.sort(allTagsNewestFirst, (Comparator<Ref>) (o1, o2) -> {
Date d1;
Date d2;
try {
d1 = walk.parseTag(o1.getObjectId()).getTaggerIdent().getWhen();
}
catch (IOException ioe) {
return 1; // put at end
}
try {
d2 = walk.parseTag(o2.getObjectId()).getTaggerIdent().getWhen();
}
catch (IOException ioe) {
return -1; // put ahead
}
return d2.compareTo(d1); // more recent first
});
return allTagsNewestFirst.stream()
.map(ref -> Repository.shortenRefName(ref.getName()));
}
catch (Exception e) {
throw new IllegalStateException("Unable to fetch git tags", e);
}
}
/**
* Look for a tag with the given name, and if not found looks for a branch.
*/
private Optional<Ref> findTagOrBranchHeadRevision(Git git, String tagOrBranch)
throws GitAPIException {
throws GitAPIException, IOException {
if (tagOrBranch.equals("HEAD")) {
return Optional.of(git.getRepository().exactRef(Constants.HEAD).getTarget());
}
final Optional<Ref> tag = git.tagList().call().stream()
.filter(ref -> ref.getName().equals("refs/tags/" + tagOrBranch))
.findFirst();

View File

@@ -23,7 +23,9 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.transport.URIish;
@@ -318,4 +320,15 @@ public class ProjectGitHandler implements Closeable {
CACHE.clear();
}
/**
* Find the tag names that match a {@link Pattern}.
* @param clonedProject the base dir for the cloned repository
* @param tagPattern the {@link Pattern} to use to filter tag names
* @return a {@link Stream} of the tags whose name match the given {@link Pattern}
*/
public Stream<String> findTagNamesMatching(File clonedProject, Pattern tagPattern) {
return gitRepo(clonedProject).listTags()
.filter(tagName -> tagPattern.matcher(tagName).matches());
}
}

View File

@@ -22,6 +22,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.regex.Pattern;
import org.apache.maven.model.Model;
@@ -266,6 +267,109 @@ public class ProjectVersion implements Comparable<ProjectVersion>, Serializable
return this.assertVersion().major;
}
/**
* Compute the previous patch's version, without any prefix or suffix (ie
* MAJOR.MINOR.PATCH only). If the patch number is already at 0, returns an empty
* {@link Optional} instead.
* @param prefix the prefix to prepend to the tag pattern (eg. if tags are in the form
* vVERSION)
* @param suffix the suffix to append to the tag pattern instead (eg. if tags are in
* the form VERSION.RELEASE). this replaces the suffix in the version if any.
* @return an {@link Optional} valued with the previous MAJOR.MINOR.PATCH, or empty if
* PATCH is already 0
* @see #computePreviousMinorTagPattern(String,String)
*/
public Optional<String> computePreviousPatchTag(String prefix, String suffix) {
SplitVersion splitVersion = this.assertVersion();
if (suffix.isEmpty()) {
suffix = splitVersion.suffix;
}
int patch;
try {
patch = Integer.parseInt(splitVersion.patch);
}
catch (NumberFormatException nfe) {
throw new IllegalArgumentException("Version " + this.version
+ " doesn't contain a numerical PATCH number", nfe);
}
if (patch == 0) {
return Optional.empty();
}
return Optional.of(prefix + splitVersion.major + splitVersion.delimiter
+ splitVersion.minor + splitVersion.delimiter + (patch - 1)
+ splitVersion.delimiter + suffix);
}
/**
* Compute a {@link Pattern} that allows to identify all the version tags of the
* previous MAJOR.MINOR (MAJOR.MINOR.*), provided MINOR is not 0. If MINOR is 0,
* {@link Optional#empty()} is returned.
* @param prefix the prefix to prepend to the tag pattern (eg. if tags are in the form
* vVERSION)
* @param suffix the suffix to append to the tag pattern (eg. if tags are in the form
* VERSION.RELEASE)
* @return a {@link Optional} of {@link Pattern} to identify all the versions in the
* previous MAJOR.MINOR, or empty if current MINOR is 0
* @see #computePreviousMajorTagPattern(String, String)
*/
public Optional<Pattern> computePreviousMinorTagPattern(String prefix,
String suffix) {
SplitVersion splitVersion = this.assertVersion();
if (suffix.isEmpty()) {
suffix = splitVersion.suffix;
}
String quotedSuffix = Pattern.quote(splitVersion.delimiter + suffix);
int minor;
try {
minor = Integer.parseInt(splitVersion.minor);
}
catch (NumberFormatException nfe) {
throw new IllegalArgumentException("Version " + this.version
+ " doesn't contain a numerical MINOR number", nfe);
}
if (minor == 0) {
return Optional.empty();
}
Pattern p = Pattern
.compile(Pattern
.quote(prefix + splitVersion.major + splitVersion.delimiter
+ (minor - 1) + splitVersion.delimiter)
+ "\\d+" + quotedSuffix);
return Optional.of(p);
}
/**
* Compute a {@link Pattern} that allows to identify all the version tags of the
* previous MAJOR. Throws {@link IllegalArgumentException} if MAJOR is 0.
* @param prefix the prefix to prepend to the tag pattern (eg. if tags are in the form
* vVERSION)
* @param suffix the suffix to append to the tag pattern (eg. if tags are in the form
* VERSION.RELEASE)
* @return a {@link Pattern} to identify all the versions in the previous MAJOR
*/
public Pattern computePreviousMajorTagPattern(String prefix, String suffix) {
SplitVersion splitVersion = this.assertVersion();
if (suffix.isEmpty()) {
suffix = splitVersion.suffix;
}
String quotedSuffix = Pattern.quote(splitVersion.delimiter + suffix);
int major;
try {
major = Integer.parseInt(splitVersion.major);
}
catch (NumberFormatException nfe) {
throw new IllegalArgumentException("Version " + this.version
+ " doesn't contain a numerical MAJOR number", nfe);
}
if (major == 0) {
throw new IllegalArgumentException(
"Cannot compute previous MAJOR pattern with MAJOR of 0");
}
return Pattern.compile(
Pattern.quote(prefix + (major - 1) + splitVersion.delimiter) + "\\d+"
+ Pattern.quote(splitVersion.delimiter) + "\\d+" + quotedSuffix);
}
public boolean isSnapshot() {
return this.version != null && this.version.contains("SNAPSHOT");
}

View File

@@ -27,6 +27,7 @@ import org.junit.Test;
import releaser.internal.git.GitRepoTests;
import releaser.internal.project.ProjectVersion;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.BDDAssertions.then;
import static org.assertj.core.api.BDDAssertions.thenThrownBy;
@@ -620,6 +621,69 @@ public class ProjectVersionTests {
then(projectVersion("1.0.0.BUILD-SNAPSHOT").releaseTagName()).isEmpty();
}
@Test
public void should_return_decremented_patch_when_patch_above_zero() {
then(projectVersion("1.2.3.RELEASE").computePreviousPatchTag("v", "RELEASE"))
.contains("v1.2.2.RELEASE");
}
@Test
public void should_use_current_suffix_if_no_forced_suffix() {
then(projectVersion("1.2.3.WHATEVER").computePreviousPatchTag("v", ""))
.contains("v1.2.2.WHATEVER");
}
@Test
public void should_replace_suffix() {
then(projectVersion("1.2.3.WHATEVER").computePreviousPatchTag("v", "RELEASE"))
.contains("v1.2.2.RELEASE");
}
@Test
public void should_return_empty_when_patch_at_zero() {
then(projectVersion("1.2.0.WHATEVER").computePreviousPatchTag("v", "RELEASE"))
.isEmpty();
}
@Test
public void should_return_minor_pattern_when_minor_above_zero() {
then(projectVersion("1.2.0.WHATEVER")
.computePreviousMinorTagPattern("v", "RELEASE").get().pattern())
.isEqualTo("\\Qv1.1.\\E\\d+\\Q.RELEASE\\E");
}
@Test
public void should_compute_minor_pattern_with_current_suffix_if_no_forced_suffix() {
then(projectVersion("1.2.0.WHATEVER").computePreviousMinorTagPattern("v", "")
.get().pattern()).isEqualTo("\\Qv1.1.\\E\\d+\\Q.WHATEVER\\E");
}
@Test
public void should_return_empty_minor_pattern_when_minor_zero() {
then(projectVersion("1.0.0.WHATEVER").computePreviousMinorTagPattern("v",
"RELEASE")).isEmpty();
}
@Test
public void should_return_major_pattern_when_major_above_zero() {
then(projectVersion("1.0.0.WHATEVER")
.computePreviousMajorTagPattern("v", "RELEASE").pattern())
.isEqualTo("\\Qv0.\\E\\d+\\Q.\\E\\d+\\Q.RELEASE\\E");
}
@Test
public void should_compute_major_pattern_with_current_suffix_if_no_forced_suffix() {
then(projectVersion("1.0.0.WHATEVER").computePreviousMajorTagPattern("v", "")
.pattern()).isEqualTo("\\Qv0.\\E\\d+\\Q.\\E\\d+\\Q.WHATEVER\\E");
}
@Test
public void should_throw_major_pattern_when_major_zero() {
assertThatIllegalArgumentException()
.isThrownBy(() -> projectVersion("0.0.1.WHATEVER")
.computePreviousMajorTagPattern("v", "RELEASE"));
}
private void thenPatternsForSnapshotMilestoneAndReleaseCandidateArePresent(
List<Pattern> unknownTypeOfVersion) {
then(unknownTypeOfVersion).isNotEmpty();