Skip to content

Commit

Permalink
\#960 prevent outdated versions from being released.
Browse files Browse the repository at this point in the history
  • Loading branch information
original-brownbear authored and rultor committed Mar 4, 2016
1 parent c610cb5 commit 0450116
Show file tree
Hide file tree
Showing 6 changed files with 319 additions and 21 deletions.
5 changes: 5 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,11 @@
<version>2.1</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-artifact</artifactId>
<version>3.3.9</version>
</dependency>
</dependencies>
<build>
<pluginManagement>
Expand Down
64 changes: 45 additions & 19 deletions src/main/java/com/rultor/agents/github/qtn/QnRelease.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@
import java.io.IOException;
import java.net.URI;
import java.util.ResourceBundle;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import lombok.EqualsAndHashCode;
import lombok.ToString;

Expand All @@ -49,7 +51,6 @@
* @author Yegor Bugayenko ([email protected])
* @version $Id$
* @since 1.3.6
* @checkstyle MultipleStringLiteralsCheck (500 lines)
*/
@Immutable
@ToString
Expand All @@ -65,31 +66,56 @@ public final class QnRelease implements Question {
@Override
public Req understand(final Comment.Smart comment,
final URI home) throws IOException {
new Answer(comment).post(
true,
String.format(
QnRelease.PHRASES.getString("QnRelease.start"),
home.toASCIIString()
)
);
final Issue issue = comment.issue();
Logger.info(
this, "release request found in %s#%d, comment #%d",
issue.repo().coordinates(), issue.number(), comment.number()
);
return new Req.Simple(
"release",
new ImmutableMap.Builder<String, String>()
.put("head_branch", "master")
.put(
"head",
final Req req;
final Matcher matcher = Pattern.compile(".*release.*`(.+)`.*")
.matcher(comment.body());
if (matcher.matches()) {
final String name = matcher.group(1);
final ReleaseTag release = new ReleaseTag(issue.repo(), name);
if (release.release()) {
new Answer(comment).post(
true,
String.format(
"[email protected]:%s.git",
comment.issue().repo().coordinates()
QnRelease.PHRASES.getString("QnRelease.start"),
home.toASCIIString()
)
)
.build()
);
);
req = new Req.Simple(
"release",
new ImmutableMap.Builder<String, String>()
.put("head_branch", "master")
.put(
"head",
String.format(
"[email protected]:%s.git",
comment.issue().repo().coordinates()
)
).build()
);
} else {
new Answer(comment).post(
false,
String.format(
QnRelease.PHRASES.getString("QnRelease.invalid-tag"),
name,
release.reference()
)
);
req = Req.EMPTY;
}
} else {
new Answer(comment).post(
false,
QnRelease.PHRASES.getString("QnRelease.missing-tag")
);
req = Req.EMPTY;
}
return req;
}

}
127 changes: 127 additions & 0 deletions src/main/java/com/rultor/agents/github/qtn/ReleaseTag.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/**
* Copyright (c) 2009-2015, rultor.com
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met: 1) Redistributions of source code must retain the above
* copyright notice, this list of conditions and the following
* disclaimer. 2) Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution. 3) Neither the name of the rultor.com nor
* the names of its contributors may be used to endorse or promote
* products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
* NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.rultor.agents.github.qtn;

import com.jcabi.aspects.Immutable;
import com.jcabi.github.Release;
import com.jcabi.github.Repo;
import com.jcabi.github.Smarts;
import java.io.IOException;
import java.util.regex.Pattern;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;

/**
* Release Tag validator, ensures not releasing already outdated tags.
*
* @author Armin Braun ([email protected])
* @version $Id$
* @since 1.62
*/
@Immutable
final class ReleaseTag {

/**
* Pattern matching semantically valid versions.
*/
private static final Pattern VERSION_PATTERN =
Pattern.compile("^(\\d+\\.)*(\\d+)$");

/**
* Github.
*/
private final transient Repo repo;

/**
* Tag-name of the release.
*/
private final transient String name;

/**
* Ctor.
* @param rpo Github repo
* @param version String release tag name
*/
ReleaseTag(final Repo rpo, final String version) {
this.repo = rpo;
this.name = version;
}

/**
* Checks if this tag can be released.
* A tag can be released if it is either not named as a semantically
* correct version or has a higher version number than all existing tags.
* @return Boolean
* @throws IOException on error.
*/
public boolean release() throws IOException {
return !ReleaseTag.valid(this.name)
|| ReleaseTag.newer(this.reference(), this.name);
}

/**
* Returns the tag name of the highest version from the repo.
* @return String name of the highest released version.
* @throws IOException on error.
*/
public String reference() throws IOException {
String tag = "0";
final Iterable<Release.Smart> rels =
new Smarts<>(this.repo.releases().iterate());
for (final Release.Smart rel : rels) {
final String version = rel.tag();
if (ReleaseTag.valid(version) && ReleaseTag.newer(tag, version)) {
tag = version;
}
}
return tag;
}

/**
* Checks that a tag is newer than a given reference.
* @param reference String
* @param tag String
* @return Boolean true if tag is new than reference.
*/
private static boolean newer(final String reference, final String tag) {
return new DefaultArtifactVersion(reference).compareTo(
new DefaultArtifactVersion(tag)
) < 0;
}

/**
* Checks tag string format being a valid release version.
* @param identifier String tag name.
* @return Boolean
*/
private static boolean valid(final String identifier) {
return ReleaseTag.VERSION_PATTERN.matcher(identifier).matches();
}

}
7 changes: 6 additions & 1 deletion src/main/resources/phrases_en_US.properties
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,12 @@ QnMerge.base-is-gone=Base repository is gone, can't merge into it
QnDeploy.start=OK, I'll try to deploy now. You can check the progress [here](%s)

QnRelease.start=OK, I will release it now. Please check the progress [here](%s)

QnRelease.missing-tag=No release tag specified, please provide a release version.\n \
More information on the release command can be found \
[here](http://doc.rultor.com/basics.html).
QnRelease.invalid-tag=Invalid release tag `%s` specified. There is already a \
release `%s` newer than the given release in this \
repository.
QnStop.stop=OK, I'll try to stop current task.

QnAskedBy.denied=Sorry, I accept such requests only from authorized commanders: %s \
Expand Down
48 changes: 47 additions & 1 deletion src/test/java/com/rultor/agents/github/qtn/QnReleaseTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,10 @@
import com.jcabi.github.Repo;
import com.jcabi.github.mock.MkGithub;
import com.jcabi.matchers.XhtmlMatchers;
import com.rultor.agents.github.Req;
import java.net.URI;
import org.hamcrest.MatcherAssert;
import org.hamcrest.Matchers;
import org.junit.Test;
import org.xembly.Directives;
import org.xembly.Xembler;
Expand All @@ -59,7 +61,7 @@ public final class QnReleaseTest {
public void buildsRequest() throws Exception {
final Repo repo = new MkGithub().randomRepo();
final Issue issue = repo.issues().create("", "");
issue.comments().post("release");
issue.comments().post("release `1.7`");
MatcherAssert.assertThat(
new Xembler(
new Directives().add("request").append(
Expand All @@ -77,4 +79,48 @@ public void buildsRequest() throws Exception {
);
}

/**
* QnRelease can deny release when tag is outdated.
* @throws Exception In case of error.
*/
@Test
public void denyOutdatedTag() throws Exception {
final Repo repo = new MkGithub().randomRepo();
final Issue issue = repo.issues().create("", "");
repo.releases().create("1.7");
issue.comments().post("release `1.6`");
MatcherAssert.assertThat(
new QnRelease().understand(
new Comment.Smart(issue.comments().get(1)), new URI("#")
),
Matchers.is(Req.EMPTY)
);
MatcherAssert.assertThat(
new Comment.Smart(issue.comments().get(2)).body(),
Matchers.containsString("There is already a release `1.7`")
);
issue.comments().post("release");
}

/**
* QnRelease can deny release when tag name is not given.
* @throws Exception In case of error.
*/
@Test
public void denyMissingTag() throws Exception {
final Issue issue = new MkGithub().randomRepo().issues()
.create("", "");
issue.comments().post("release");
MatcherAssert.assertThat(
new QnRelease().understand(
new Comment.Smart(issue.comments().get(1)), new URI("#")
),
Matchers.is(Req.EMPTY)
);
MatcherAssert.assertThat(
new Comment.Smart(issue.comments().get(2)).body(),
Matchers.containsString("No release tag specified")
);
}

}
Loading

0 comments on commit 0450116

Please sign in to comment.