diff --git a/pom.xml b/pom.xml index 14451bb..086a4c8 100644 --- a/pom.xml +++ b/pom.xml @@ -97,43 +97,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/main/java/org/springframework/data/release/announcement/AnnouncementOperations.java b/src/main/java/org/springframework/data/release/announcement/AnnouncementOperations.java new file mode 100644 index 0000000..d0975c4 --- /dev/null +++ b/src/main/java/org/springframework/data/release/announcement/AnnouncementOperations.java @@ -0,0 +1,82 @@ +/* + * Copyright 2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.release.announcement; + +import org.springframework.data.release.cli.StaticResources; +import org.springframework.data.release.maven.Artifact; +import org.springframework.data.release.model.Iteration; +import org.springframework.data.release.model.ModuleIteration; +import org.springframework.data.release.model.Project; +import org.springframework.data.release.model.ReleaseTrains; +import org.springframework.data.release.model.Train; +import org.springframework.util.Assert; + +/** + * @author Oliver Gierke + */ +public class AnnouncementOperations { + + /** + * Returns the project list and links to be included in the release announcement for the given {@link Train} and + * {@link Iteration}. + * + * @param train must not be {@literal null}. + * @param iteration must not be {@literal null}. + * @return + */ + public String getProjectBulletpoints(Train train, Iteration iteration) { + + Assert.notNull(train, "Train must not be null!"); + Assert.notNull(iteration, "Iteration must not be null!"); + + StringBuilder builder = new StringBuilder(); + + for (ModuleIteration module : train.getModuleIterations(iteration, ReleaseTrains.BUILD)) { + + Project project = module.getProject(); + + builder.append("* "); + builder.append(project.getFullName()).append(" "); + builder.append(module.getVersion()).append(" ").append(module.getIteration().getName()); + builder.append(" - "); + + Artifact artifact = new Artifact(module); + + builder.append(getMarkDownLink("Artifacts", artifact.getRootUrl())); + builder.append(" - "); + + StaticResources resources = new StaticResources(module); + + builder.append(getMarkDownLink("JavaDocs", resources.getJavaDocUrl())).append(" - "); + builder.append(getMarkDownLink("Documentation", resources.getDocumentationUrl())).append(" - "); + builder.append(getMarkDownLink("Changelog", resources.getChangelogUrl())); + + builder.append("\n"); + } + + return builder.toString(); + } + + private String getMarkDownLink(String name, String url) { + return String.format("[%s](%s)", name, url); + } + + public static void main(String[] args) { + + AnnouncementOperations operations = new AnnouncementOperations(); + System.out.println(operations.getProjectBulletpoints(ReleaseTrains.DIJKSTRA, Iteration.M1)); + } +} diff --git a/src/main/java/org/springframework/data/release/cli/JiraCommands.java b/src/main/java/org/springframework/data/release/cli/JiraCommands.java index eae1b32..e357fdd 100644 --- a/src/main/java/org/springframework/data/release/cli/JiraCommands.java +++ b/src/main/java/org/springframework/data/release/cli/JiraCommands.java @@ -58,7 +58,6 @@ public class JiraCommands implements CommandMarker { @CliCommand(value = "jira tickets") public String jira( - // @CliOption(key = { "", "train" }, mandatory = true, help = "The name of the release train.") String trainName, // @CliOption(key = "iteration", mandatory = true, help = "An iteration key (one of M1, RC1, GA).") String iterationName, // @CliOption(key = "for-current-user", specifiedDefaultValue = "true", unspecifiedDefaultValue = "false") boolean forCurrentUser) { @@ -72,4 +71,13 @@ public class JiraCommands implements CommandMarker { return connector.getTicketsFor(train, iteration, forCurrentUser ? credentials : null).toString(); } + + @CliCommand("changelog") + public String changelog(@CliOption(key = { "", "module" }, mandatory = true) String moduleName, @CliOption( + key = { "iteration" }, mandatory = true) String iterationName) { + + Train dijkstra = ReleaseTrains.DIJKSTRA; + return connector.getChangelogFor(dijkstra, dijkstra.getModule(moduleName), + dijkstra.getIterations().getIterationByName(iterationName)).toString(); + } } diff --git a/src/main/java/org/springframework/data/release/cli/ReleaseCommands.java b/src/main/java/org/springframework/data/release/cli/ReleaseCommands.java index 1e83624..54eae2d 100644 --- a/src/main/java/org/springframework/data/release/cli/ReleaseCommands.java +++ b/src/main/java/org/springframework/data/release/cli/ReleaseCommands.java @@ -15,19 +15,22 @@ */ package org.springframework.data.release.cli; -import java.io.IOException; - import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.release.git.GitOperations; +import org.springframework.data.release.git.Tags; import org.springframework.data.release.maven.MavenOperations; import org.springframework.data.release.maven.Pom; +import org.springframework.data.release.model.ArtifactVersion; +import org.springframework.data.release.model.Iteration; import org.springframework.data.release.model.Module; import org.springframework.data.release.model.Project; import org.springframework.data.release.model.ReleaseTrains; import org.springframework.data.release.model.Train; import org.springframework.shell.core.CommandMarker; import org.springframework.shell.core.annotation.CliCommand; +import org.springframework.shell.core.annotation.CliOption; import org.springframework.stereotype.Component; /** @@ -37,19 +40,25 @@ import org.springframework.stereotype.Component; @RequiredArgsConstructor(onConstructor = @__(@Autowired)) public class ReleaseCommands implements CommandMarker { - private final MavenOperations mavenOperations; + private final MavenOperations maven; + private final GitOperations git; @CliCommand("release predict") - public String predictTrainAndIteration() throws IOException { + public String predictTrainAndIteration() throws Exception { Project commons = ReleaseTrains.COMMONS; - Pom pom = mavenOperations.getMavenProject(commons); + Pom pom = maven.getMavenProject(commons); + + Tags tags = git.getTags(commons); + + ArtifactVersion version = tags.getLatest().toArtifactVersion(); + System.out.println(version); for (Train train : ReleaseTrains.TRAINS) { Module module = train.getModule(commons); - if (!pom.getVersion().startsWith(module.getVersion().toMajorMinorBugfix())) { + if (!pom.getVersion().toString().startsWith(module.getVersion().toMajorMinorBugfix())) { continue; } @@ -58,4 +67,36 @@ public class ReleaseCommands implements CommandMarker { return null; } + + /** + * Triggers the distribution of release artifacts for all projects. + * + * @param trainName + * @param iterationName + * @throws Exception + */ + @CliCommand("release distribute") + public void distribute(@CliOption(key = { "", "train" }, mandatory = true) String trainName, @CliOption( + key = "iteration", mandatory = true) String iterationName) throws Exception { + + Train train = ReleaseTrains.getTrainByName(trainName); + Iteration iteration = train.getIteration(iterationName); + + git.checkout(train, iteration); + maven.triggerDistributionBuild(train, iteration); + } + + @CliCommand("release prepare") + public void prepare(@CliOption(key = { "", "train" }, mandatory = true) String trainName, @CliOption( + key = "iteration", mandatory = true) String iterationName) throws Exception { + + Train train = ReleaseTrains.getTrainByName(trainName); + Iteration iteration = train.getIteration(iterationName); + + git.prepare(train, iteration); + + for (Module module : train) { + maven.prepareProject(train, iteration, module.getProject()); + } + } } diff --git a/src/main/java/org/springframework/data/release/cli/StaticResources.java b/src/main/java/org/springframework/data/release/cli/StaticResources.java new file mode 100644 index 0000000..6aa92dc --- /dev/null +++ b/src/main/java/org/springframework/data/release/cli/StaticResources.java @@ -0,0 +1,53 @@ +/* + * Copyright 2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.release.cli; + +import lombok.RequiredArgsConstructor; + +import org.springframework.data.release.model.ArtifactVersion; +import org.springframework.data.release.model.ModuleIteration; +import org.springframework.data.release.model.Project; + +/** + * @author Oliver Gierke + */ +@RequiredArgsConstructor +public class StaticResources { + + private static final String URL_TEMPLATE = "http://docs.spring.io/spring-data/%s/docs/%s"; + + private final String baseUrl; + + public StaticResources(ModuleIteration module) { + + Project project = module.getProject(); + ArtifactVersion version = ArtifactVersion.from(module); + + this.baseUrl = String.format(URL_TEMPLATE, project.getName().toLowerCase(), version); + } + + public String getDocumentationUrl() { + return baseUrl.concat("/reference/htmlsingle"); + } + + public String getJavaDocUrl() { + return baseUrl.concat("/api"); + } + + public String getChangelogUrl() { + return baseUrl.concat("/changelog.txt"); + } +} diff --git a/src/main/java/org/springframework/data/release/git/Branch.java b/src/main/java/org/springframework/data/release/git/Branch.java new file mode 100644 index 0000000..516e818 --- /dev/null +++ b/src/main/java/org/springframework/data/release/git/Branch.java @@ -0,0 +1,65 @@ +/* + * Copyright 2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.release.git; + +import lombok.EqualsAndHashCode; +import lombok.RequiredArgsConstructor; + +import org.springframework.data.release.model.IterationVersion; +import org.springframework.data.release.model.Version; +import org.springframework.util.Assert; + +/** + * Value type to represent an SCM branch. + * + * @author Oliver Gierke + */ +@RequiredArgsConstructor +@EqualsAndHashCode +public class Branch { + + private static final Branch MASTER = new Branch("master"); + + private final String name; + + /** + * Creates a new {@link Branch} from the given {@link IterationVersion}. + * + * @param iterationVersion must not be {@literal null}. + * @return + */ + public static Branch from(IterationVersion iterationVersion) { + + Assert.notNull(iterationVersion, "Iteration versoin must not be null!"); + + Version version = iterationVersion.getVersion(); + + if (iterationVersion.getIteration().isServiceIteration()) { + return new Branch(version.toString().concat(".x")); + } + + return MASTER; + } + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return name; + } +} diff --git a/src/main/java/org/springframework/data/release/git/GiCommands.java b/src/main/java/org/springframework/data/release/git/GiCommands.java index 2e8068d..1eb786e 100644 --- a/src/main/java/org/springframework/data/release/git/GiCommands.java +++ b/src/main/java/org/springframework/data/release/git/GiCommands.java @@ -15,13 +15,13 @@ */ package org.springframework.data.release.git; -import java.io.IOException; - import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.release.model.Iteration; import org.springframework.data.release.model.Project; import org.springframework.data.release.model.ReleaseTrains; +import org.springframework.data.release.model.Train; import org.springframework.shell.core.CommandMarker; import org.springframework.shell.core.annotation.CliCommand; import org.springframework.shell.core.annotation.CliOption; @@ -35,20 +35,30 @@ import org.springframework.util.StringUtils; @RequiredArgsConstructor(onConstructor = @__(@Autowired)) public class GiCommands implements CommandMarker { - private final GitOperations gitOperations; + private final GitOperations git; + + @CliCommand("git checkout") + public void checkout(@CliOption(key = { "", "train" }, mandatory = true) String trainName, @CliOption( + key = "iteration", mandatory = true) String iterationName) throws Exception { + + Train train = ReleaseTrains.getTrainByName(trainName); + Iteration iteration = train.getIteration(iterationName); + + git.checkout(train, iteration); + } @CliCommand("git update") - public void checkout(@CliOption(key = { "train" }, mandatory = true) String trainName) throws IOException, + public void checkout(@CliOption(key = { "", "train" }, mandatory = true) String trainName) throws Exception, InterruptedException { - gitOperations.update(ReleaseTrains.getProjectByName(trainName)); + git.update(ReleaseTrains.getTrainByName(trainName)); } @CliCommand("git tags") - public String tags(@CliOption(key = { "project" }, mandatory = true) String projectName) throws IOException { + public String tags(@CliOption(key = { "project" }, mandatory = true) String projectName) throws Exception { Project project = ReleaseTrains.getProjectByName(projectName); - return StringUtils.collectionToDelimitedString(gitOperations.getTags(project), "\n"); + return StringUtils.collectionToDelimitedString(git.getTags(project).asList(), "\n"); } } diff --git a/src/main/java/org/springframework/data/release/git/GitOperations.java b/src/main/java/org/springframework/data/release/git/GitOperations.java index 130d858..de4711f 100644 --- a/src/main/java/org/springframework/data/release/git/GitOperations.java +++ b/src/main/java/org/springframework/data/release/git/GitOperations.java @@ -19,21 +19,28 @@ import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.Future; import java.util.logging.Logger; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.release.io.CommandExecution; +import org.springframework.data.release.io.CommandResult; import org.springframework.data.release.io.OsCommandOperations; import org.springframework.data.release.io.Workspace; +import org.springframework.data.release.model.ArtifactVersion; +import org.springframework.data.release.model.Iteration; import org.springframework.data.release.model.Module; +import org.springframework.data.release.model.ModuleIteration; import org.springframework.data.release.model.Project; import org.springframework.data.release.model.Train; import org.springframework.shell.support.logging.HandlerUtils; import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; /** + * Component to execut Git related operations. + * * @author Oliver Gierke */ @Component @@ -50,20 +57,66 @@ public class GitOperations { return new GitProject(project, server); } - public void update(Train train) throws IOException, InterruptedException { + /** + * Checks out all projects of the given {@link Train} at the tags for the given {@link Iteration}. + * + * @param train + * @param iteration + * @throws Exception + */ + public void checkout(Train train, Iteration iteration) throws Exception { - List executions = new ArrayList<>(); + update(train); + + for (ModuleIteration module : train.getModuleIterations(iteration)) { + + Project project = module.getProject(); + ArtifactVersion artifactVersion = ArtifactVersion.from(module); + + Tag tag = findTagFor(project, artifactVersion); + + if (tag == null) { + throw new IllegalStateException(String.format("No tag found for version %s of project %s, aborting.", + artifactVersion, project)); + } + + osCommandOperations.executeCommand(String.format("git checkout %s", tag), project).get(); + } + + LOGGER.info(String.format("Successfully checked out iteration %s for release train %s.", iteration.getName(), + train.getName())); + } + + public void prepare(Train train, Iteration iteration) throws Exception { + + for (ModuleIteration module : train.getModuleIterations(iteration)) { + + Branch branch = Branch.from(module); + + update(module.getProject()); + + String checkoutCommand = String.format("git checkout %s", branch); + osCommandOperations.executeCommand(checkoutCommand, module.getProject()).get(); + + String updateCommand = String.format("git pull origin %s", branch); + osCommandOperations.executeCommand(updateCommand, module.getProject()).get(); + } + } + + public void update(Train train) throws Exception { + + List> executions = new ArrayList<>(); for (Module module : train) { executions.add(update(module.getProject())); } - for (CommandExecution execution : executions) { - execution.waitForResult(); + for (Future execution : executions) { + execution.get(); } } - public CommandExecution update(Project project) throws IOException { + public Future update(Project project) throws Exception { GitProject gitProject = new GitProject(project, server); String repositoryName = gitProject.getRepositoryName(); @@ -71,28 +124,54 @@ public class GitOperations { if (workspace.hasProjectDirectory(project)) { LOGGER.info(String.format("Found existing repository %s. Obtaining latest changes…", repositoryName)); - return osCommandOperations.executeCommand("git pull origin master", project); + + return osCommandOperations.executeCommand("git checkout master && git fetch --tags && git pull origin master", + project); } else { - File projectDirectory = workspace.getProjectDirectory(project); - LOGGER.info(String.format("No repository found for project %s. Cloning repository from %s…", repositoryName, gitProject.getProjectUri())); - return osCommandOperations.executeCommand(String.format("git clone %s %s", gitProject.getProjectUri(), - projectDirectory.getName())); + + File projectDirectory = workspace.getProjectDirectory(project); + String command = String.format("git clone %s %s", gitProject.getProjectUri(), projectDirectory.getName()); + + return osCommandOperations.executeCommand(command); } } - public List getTags(Project project) throws IOException { + public Tags getTags(Project project) throws Exception { - CommandExecution command = osCommandOperations.executeCommand("git tag -l", project); + String result = osCommandOperations.executeForResult("git tag -l", project); List tags = new ArrayList<>(); - for (String line : command.waitAndGetOutput().split("\n")) { - tags.add(new Tag(line)); + for (String line : result.split("\n")) { + + if (!StringUtils.isEmpty(line)) { + tags.add(new Tag(line)); + } } - return tags; + return new Tags(tags); + } + + /** + * Returns the {@link Tag} that represents the {@link ArtifactVersion} of the given {@link Project}. + * + * @param project + * @param version + * @return + * @throws IOException + */ + private Tag findTagFor(Project project, ArtifactVersion version) throws Exception { + + for (Tag tag : getTags(project)) { + + if (tag.toArtifactVersion().equals(version)) { + return tag; + } + } + + return null; } } diff --git a/src/main/java/org/springframework/data/release/git/GitProject.java b/src/main/java/org/springframework/data/release/git/GitProject.java index 5a65558..543b8fb 100644 --- a/src/main/java/org/springframework/data/release/git/GitProject.java +++ b/src/main/java/org/springframework/data/release/git/GitProject.java @@ -32,10 +32,20 @@ public class GitProject { private final Project project; private final GitServer server; + /** + * Returns the name of the repository the project is using. + * + * @return + */ public String getRepositoryName() { return String.format("%s-%s", PROJECT_PREFIX, project.getName().toLowerCase()); } + /** + * Returns the URI of the {@link Project}'s repository. + * + * @return + */ public String getProjectUri() { return server.getUri() + getRepositoryName(); } diff --git a/src/main/java/org/springframework/data/release/git/Tag.java b/src/main/java/org/springframework/data/release/git/Tag.java index 4010eb1..77172c6 100644 --- a/src/main/java/org/springframework/data/release/git/Tag.java +++ b/src/main/java/org/springframework/data/release/git/Tag.java @@ -15,13 +15,51 @@ */ package org.springframework.data.release.git; -import lombok.Value; +import lombok.EqualsAndHashCode; +import lombok.RequiredArgsConstructor; + +import org.springframework.data.release.model.ArtifactVersion; /** + * Value object to represent an SCM tag. + * * @author Oliver Gierke */ -@Value -public class Tag { +@RequiredArgsConstructor +@EqualsAndHashCode +public class Tag implements Comparable { private final String name; + + /** + * Returns the part of the name of the tag that is suitable to derive a version from the tag. Will transparently strip + * a {@code v} prefix from the name. + * + * @return + */ + private String getVersionSource() { + return name.startsWith("v") ? name.substring(1) : name; + } + + public ArtifactVersion toArtifactVersion() { + return ArtifactVersion.parse(getVersionSource()); + } + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return name; + } + + /* + * (non-Javadoc) + * @see java.lang.Comparable#compareTo(java.lang.Object) + */ + @Override + public int compareTo(Tag that) { + return that.name.compareTo(this.name); + } } diff --git a/src/main/java/org/springframework/data/release/git/Tags.java b/src/main/java/org/springframework/data/release/git/Tags.java new file mode 100644 index 0000000..231b68c --- /dev/null +++ b/src/main/java/org/springframework/data/release/git/Tags.java @@ -0,0 +1,78 @@ +/* + * Copyright 2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.release.git; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import lombok.EqualsAndHashCode; + +import org.springframework.util.Assert; + +/** + * Value object to represent a collection of {@link Tag}s. + * + * @author Oliver Gierke + */ +@EqualsAndHashCode +public class Tags implements Iterable { + + private final List tags; + + /** + * Creates a new {@link Tags} instance for the given {@link List} of {@link Tag}s. + * + * @param source must not be {@literal null}. + */ + Tags(List source) { + + Assert.notNull(source, "Tags must not be null!"); + + List tags = new ArrayList<>(source); + Collections.sort(tags); + + this.tags = Collections.unmodifiableList(tags); + } + + /** + * Returns the latest {@link Tag}. + * + * @return + */ + public Tag getLatest() { + return tags.get(0); + } + + /** + * Returns all {@link Tag}s as {@link List}. + * + * @return + */ + public List asList() { + return tags; + } + + /* + * (non-Javadoc) + * @see java.lang.Iterable#iterator() + */ + @Override + public Iterator iterator() { + return tags.iterator(); + } +} diff --git a/src/main/java/org/springframework/data/release/io/CommandExecution.java b/src/main/java/org/springframework/data/release/io/CommandExecution.java deleted file mode 100644 index 84d5946..0000000 --- a/src/main/java/org/springframework/data/release/io/CommandExecution.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2014 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.release.io; - -import java.io.StringWriter; - -import lombok.RequiredArgsConstructor; - -import org.apache.commons.exec.DefaultExecuteResultHandler; -import org.apache.commons.io.IOUtils; -import org.springframework.beans.factory.annotation.Autowired; - -/** - * @author Oliver Gierke - */ -@RequiredArgsConstructor(onConstructor = @__(@Autowired)) -public class CommandExecution { - - private final DefaultExecuteResultHandler resultHandler; - private final StringWriter writer; - - private String output; - - public Exception getException() { - return resultHandler.getException(); - } - - public int getExitValue() { - return resultHandler.getExitValue(); - } - - public String waitAndGetOutput() { - - if (output != null) { - return output; - } - - try { - waitForResult(); - } catch (InterruptedException e) { - throw new IllegalStateException(e); - } - - this.output = writer.toString(); - IOUtils.closeQuietly(writer); - - return output; - } - - public void waitForResult() throws InterruptedException { - resultHandler.waitFor(); - } -} diff --git a/src/test/java/org/springframework/data/release/io/OsConfigurationIntegrationTests.java b/src/main/java/org/springframework/data/release/io/CommandResult.java similarity index 62% rename from src/test/java/org/springframework/data/release/io/OsConfigurationIntegrationTests.java rename to src/main/java/org/springframework/data/release/io/CommandResult.java index cc8b87d..e8c2b48 100644 --- a/src/test/java/org/springframework/data/release/io/OsConfigurationIntegrationTests.java +++ b/src/main/java/org/springframework/data/release/io/CommandResult.java @@ -15,22 +15,19 @@ */ package org.springframework.data.release.io; -import java.io.File; - -import org.junit.Ignore; -import org.junit.Test; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import lombok.Value; /** * @author Oliver Gierke */ -public class OsConfigurationIntegrationTests { +@Value +public class CommandResult { - @Test - @Ignore - public void testname() { + private final int status; + private final String output; + private final Exception exception; - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(OsConfiguration.class); - File file = new File(context.getEnvironment().getProperty("io.workDir")); + public boolean hasError() { + return status != 0; } } diff --git a/src/main/java/org/springframework/data/release/io/CommonsExecOsCommandOperations.java b/src/main/java/org/springframework/data/release/io/CommonsExecOsCommandOperations.java new file mode 100644 index 0000000..a028585 --- /dev/null +++ b/src/main/java/org/springframework/data/release/io/CommonsExecOsCommandOperations.java @@ -0,0 +1,158 @@ +/* + * Copyright 2011-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.release.io; + +import java.io.File; +import java.io.IOException; +import java.io.StringWriter; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Future; +import java.util.logging.Logger; + +import javax.annotation.PostConstruct; + +import lombok.RequiredArgsConstructor; + +import org.apache.commons.exec.CommandLine; +import org.apache.commons.exec.DefaultExecuteResultHandler; +import org.apache.commons.exec.DefaultExecutor; +import org.apache.commons.exec.PumpStreamHandler; +import org.apache.commons.io.output.WriterOutputStream; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.release.git.GitOperations; +import org.springframework.data.release.model.Project; +import org.springframework.scheduling.annotation.Async; +import org.springframework.scheduling.annotation.AsyncResult; +import org.springframework.shell.support.logging.HandlerUtils; +import org.springframework.stereotype.Component; + +/** + * Implementation of {@link OsCommandOperations} interface. + * + * @author Stefan Schmidt + * @author Oliver Gierke + * @since 1.2.0 + */ +@Component +@RequiredArgsConstructor(onConstructor = @__(@Autowired)) +class CommonsExecOsCommandOperations implements OsCommandOperations { + + private static final Logger LOGGER = HandlerUtils.getLogger(GitOperations.class); + private static final String PREFIX_TEMPLATE = "%s > %s"; + + private static final Map ENVIRONMENT = new HashMap<>(); + + private final Workspace workspace; + + /* + * (non-Javadoc) + * @see org.springframework.shell.commands.OsOperations#executeCommand(java.lang.String) + */ + @Async + @Override + public Future executeCommand(String command) throws IOException { + return executeCommand((String) null, command); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.release.io.OsCommandOperations#executeCommand(java.lang.String, org.springframework.data.release.model.Project) + */ + @Async + @Override + public Future executeCommand(String command, Project project) throws IOException { + + LOGGER.info(String.format(PREFIX_TEMPLATE, project.getName(), command)); + + return executeCommand(command, workspace.getProjectDirectory(project), true); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.release.io.OsCommandOperations#executeAndListen(org.springframework.data.release.model.Project, java.lang.String) + */ + @Override + public Future executeWithOutput(String command, Project project) throws IOException { + + LOGGER.info(String.format(PREFIX_TEMPLATE, project.getName(), command)); + + return executeCommand(command, workspace.getProjectDirectory(project), false); + } + + private Future executeCommand(String subfolder, String command) throws IOException { + + File workingDirectory = workspace.getWorkingDirectory(); + File executionDirectory = subfolder == null ? workingDirectory : new File(workingDirectory, subfolder); + + return executeCommand(command, executionDirectory, true); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.release.io.OsCommandOperations#executeForResult(java.lang.String, org.springframework.data.release.model.Project) + */ + @Async + @Override + public String executeForResult(String command, Project project) throws Exception { + return executeCommand(command, workspace.getProjectDirectory(project), true).get().getOutput(); + } + + private Future executeCommand(String command, File executionDirectory, boolean silent) + throws IOException { + + StringWriter writer = new StringWriter(); + DefaultExecuteResultHandler resultHandler = new DefaultExecuteResultHandler(); + + try (WriterOutputStream outputStream = new WriterOutputStream(writer)) { + + String outerCommand = "/bin/bash -lc"; + + CommandLine outer = CommandLine.parse(outerCommand); + outer.addArgument(command, false); + + DefaultExecutor executor = new DefaultExecutor(); + executor.setWorkingDirectory(executionDirectory); + executor.setStreamHandler(new PumpStreamHandler(silent ? outputStream : System.out, null)); + executor.execute(outer, ENVIRONMENT, resultHandler); + + resultHandler.waitFor(); + + } catch (InterruptedException e) { + throw new IllegalStateException(e); + } + + return new AsyncResult(new CommandResult(resultHandler.getExitValue(), writer.toString(), + resultHandler.getException())); + } + + /** + * Adds {@code JAVA_HOME} to the ENVIRONMENT variables lookuing up the path to a Java 7. + * + * @throws Exception + */ + @PostConstruct + public void initialize() throws Exception { + + String javaHome = executeCommand("/usr/libexec/java_home -F -v 1.7 -a x86_64 -d64").get().getOutput(); + + if (javaHome.endsWith("\n")) { + javaHome = javaHome.substring(0, javaHome.length() - 1); + } + + ENVIRONMENT.put("JAVA_HOME", javaHome); + } +} diff --git a/src/main/java/org/springframework/data/release/io/IoConfigAwareOsCommandOperations.java b/src/main/java/org/springframework/data/release/io/IoConfigAwareOsCommandOperations.java deleted file mode 100644 index a53c6f4..0000000 --- a/src/main/java/org/springframework/data/release/io/IoConfigAwareOsCommandOperations.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2011-2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.release.io; - -import java.io.File; -import java.io.IOException; -import java.io.StringWriter; - -import lombok.RequiredArgsConstructor; - -import org.apache.commons.exec.CommandLine; -import org.apache.commons.exec.DefaultExecuteResultHandler; -import org.apache.commons.exec.DefaultExecutor; -import org.apache.commons.exec.PumpStreamHandler; -import org.apache.commons.io.output.WriterOutputStream; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.release.model.Project; -import org.springframework.stereotype.Component; - -/** - * Implementation of {@link OsCommandOperations} interface. - * - * @author Stefan Schmidt - * @author Oliver Gierke - * @since 1.2.0 - */ -@Component -@RequiredArgsConstructor(onConstructor = @__(@Autowired)) -class IoConfigAwareOsCommandOperations implements OsCommandOperations { - - private final Workspace workspace; - - /* - * (non-Javadoc) - * @see org.springframework.shell.commands.OsOperations#executeCommand(java.lang.String) - */ - public CommandExecution executeCommand(String command) throws IOException { - return executeCommand(command, (String) null); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.release.io.OsCommandOperations#executeCommand(java.lang.String, org.springframework.data.release.model.Project) - */ - @Override - public CommandExecution executeCommand(String command, Project project) throws IOException { - return executeCommand(command, workspace.getProjectDirectory(project)); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.release.io.OsCommandOperations#executeCommand(java.lang.String, java.io.File) - */ - @Override - public CommandExecution executeCommand(String command, String subfolder) throws IOException { - - File workingDirectory = workspace.getWorkingDirectory(); - File executionDirectory = subfolder == null ? workingDirectory : new File(workingDirectory, subfolder); - - return executeCommand(command, executionDirectory); - } - - private CommandExecution executeCommand(String command, File executionDirectory) throws IOException { - - StringWriter writer = new StringWriter(); - WriterOutputStream outputStream = new WriterOutputStream(writer); - - CommandLine commandLine = CommandLine.parse(command); - DefaultExecuteResultHandler executeResultHandler = new DefaultExecuteResultHandler(); - - DefaultExecutor executor = new DefaultExecutor(); - executor.setWorkingDirectory(executionDirectory); - executor.setStreamHandler(new PumpStreamHandler(outputStream, null)); - executor.execute(commandLine, executeResultHandler); - - return new CommandExecution(executeResultHandler, writer); - } -} diff --git a/src/main/java/org/springframework/data/release/io/OsCommandOperations.java b/src/main/java/org/springframework/data/release/io/OsCommandOperations.java index 43fdf69..8d18e35 100644 --- a/src/main/java/org/springframework/data/release/io/OsCommandOperations.java +++ b/src/main/java/org/springframework/data/release/io/OsCommandOperations.java @@ -16,6 +16,7 @@ package org.springframework.data.release.io; import java.io.IOException; +import java.util.concurrent.Future; import org.springframework.data.release.model.Project; @@ -33,9 +34,11 @@ public interface OsCommandOperations { * @param command the command to execute * @throws IOException if an error occurs */ - CommandExecution executeCommand(String command) throws IOException; + Future executeCommand(String command) throws IOException; - CommandExecution executeCommand(String command, Project project) throws IOException; + Future executeCommand(String command, Project project) throws IOException; - CommandExecution executeCommand(String command, String subfolder) throws IOException; + Future executeWithOutput(String command, Project project) throws IOException; + + String executeForResult(String command, Project project) throws Exception; } diff --git a/src/main/java/org/springframework/data/release/io/Workspace.java b/src/main/java/org/springframework/data/release/io/Workspace.java index 21d79ac..6e924d2 100644 --- a/src/main/java/org/springframework/data/release/io/Workspace.java +++ b/src/main/java/org/springframework/data/release/io/Workspace.java @@ -28,8 +28,11 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.env.Environment; import org.springframework.data.release.model.Project; import org.springframework.stereotype.Component; +import org.springframework.util.Assert; /** + * Abstraction of the workspace that is used to work with the {@link Project}'s repositories, execute builds, etc. + * * @author Oliver Gierke */ @Component @@ -40,28 +43,61 @@ public class Workspace { private final Environment environment; + /** + * Returns the current working directory. + * + * @return + */ public File getWorkingDirectory() { String workDir = environment.getProperty("io.workDir"); return new File(workDir.replace("~", System.getProperty("user.home"))); } + /** + * Returns the directory for the given {@link Project}. + * + * @param project must not be {@literal null}. + * @return + */ public File getProjectDirectory(Project project) { + + Assert.notNull(project, "Project must not be null!"); return new File(getWorkingDirectory(), project.getName()); } + /** + * Returns whether the project directory for the given project already exists. + * + * @param project must not be {@literal null}. + * @return + */ public boolean hasProjectDirectory(Project project) { + + Assert.notNull(project, "Project must not be null!"); return getProjectDirectory(project).exists(); } + /** + * Returns a file with the given name relative to the working directory for the given {@link Project}. + * + * @param name must not be {@literal null} or empty. + * @param project must not be {@literal null}. + * @return + */ public File getFile(String name, Project project) { - return new File(new File(getWorkingDirectory(), project.getName()), name); - } - - public boolean exists(String subfolder) { - return new File(getWorkingDirectory(), subfolder).exists(); + + Assert.hasText(name, "Filename must not be null or empty!"); + Assert.notNull(project, "Project must not be null!"); + + return new File(getProjectDirectory(project), name); } + /** + * Initializes the working directory and creates the folders if necessary. + * + * @throws IOException + */ @PostConstruct public void setUp() throws IOException { diff --git a/src/main/java/org/springframework/data/release/jira/Changelog.java b/src/main/java/org/springframework/data/release/jira/Changelog.java new file mode 100644 index 0000000..e8f197f --- /dev/null +++ b/src/main/java/org/springframework/data/release/jira/Changelog.java @@ -0,0 +1,74 @@ +/* + * Copyright 2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.release.jira; + +import java.util.Date; +import java.util.Locale; + +import lombok.AccessLevel; +import lombok.EqualsAndHashCode; +import lombok.RequiredArgsConstructor; + +import org.springframework.data.release.model.Iteration; +import org.springframework.data.release.model.Module; +import org.springframework.format.datetime.DateFormatter; +import org.springframework.shell.support.util.OsUtils; + +/** + * @author Oliver Gierke + */ +@RequiredArgsConstructor(access = AccessLevel.PACKAGE) +@EqualsAndHashCode +public class Changelog { + + private final Module module; + private final Iteration iteration; + private final Tickets tickets; + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + + String headline = String.format("Changes in version %s (%s)", module.getVersion(), + new DateFormatter("YYYY-MM-dd").print(new Date(), Locale.US)); + + StringBuilder builder = new StringBuilder(headline).append(OsUtils.LINE_SEPARATOR); + + for (int i = 0; i < headline.length(); i++) { + builder.append("-"); + } + + builder.append(OsUtils.LINE_SEPARATOR); + + for (Ticket ticket : tickets) { + + String summary = ticket.getSummary(); + + builder.append("* ").append(ticket.getId()).append(" - ").append(summary); + + if (!summary.endsWith(".")) { + builder.append("."); + } + + builder.append(OsUtils.LINE_SEPARATOR); + } + + return builder.toString(); + } +} diff --git a/src/main/java/org/springframework/data/release/jira/JiraConnector.java b/src/main/java/org/springframework/data/release/jira/JiraConnector.java index ceb8146..e30f8d5 100644 --- a/src/main/java/org/springframework/data/release/jira/JiraConnector.java +++ b/src/main/java/org/springframework/data/release/jira/JiraConnector.java @@ -16,6 +16,7 @@ package org.springframework.data.release.jira; import org.springframework.data.release.model.Iteration; +import org.springframework.data.release.model.Module; import org.springframework.data.release.model.Train; /** @@ -37,4 +38,6 @@ public interface JiraConnector { void verifyBeforeRelease(Train train, Iteration iteration); void closeIteration(Train train, Iteration iteration, Credentials credentials); + + Changelog getChangelogFor(Train train, Module module, Iteration iteration); } diff --git a/src/main/java/org/springframework/data/release/jira/JqlQuery.java b/src/main/java/org/springframework/data/release/jira/JqlQuery.java index 268f8a0..bc71f64 100644 --- a/src/main/java/org/springframework/data/release/jira/JqlQuery.java +++ b/src/main/java/org/springframework/data/release/jira/JqlQuery.java @@ -22,6 +22,7 @@ import lombok.Value; import org.springframework.data.release.model.Iteration; import org.springframework.data.release.model.Module; +import org.springframework.data.release.model.ReleaseTrains; import org.springframework.data.release.model.Train; import org.springframework.util.StringUtils; @@ -49,6 +50,10 @@ class JqlQuery { for (Module module : train) { + if (ReleaseTrains.BUILD.equals(module.getProject())) { + continue; + } + JiraVersion version = new JiraVersion(module, train, iteration); parts.add(String.format(PROJECT_VERSION_TEMPLATE, module.getProject().getKey(), version)); } diff --git a/src/main/java/org/springframework/data/release/jira/RestJiraConnector.java b/src/main/java/org/springframework/data/release/jira/RestJiraConnector.java index cea4c09..11a74da 100644 --- a/src/main/java/org/springframework/data/release/jira/RestJiraConnector.java +++ b/src/main/java/org/springframework/data/release/jira/RestJiraConnector.java @@ -28,6 +28,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; import org.springframework.data.release.model.Iteration; +import org.springframework.data.release.model.Module; +import org.springframework.data.release.model.ReleaseTrains; import org.springframework.data.release.model.Train; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; @@ -145,4 +147,16 @@ class RestJiraConnector implements JiraConnector { // - mark version as releases // - if no next version exists, create } + + /* + * (non-Javadoc) + * @see org.springframework.data.release.jira.JiraConnector#getChangelogFor(org.springframework.data.release.model.Module, org.springframework.data.release.model.Iteration) + */ + @Override + public Changelog getChangelogFor(Train train, Module module, Iteration iteration) { + + Tickets tickets = getTicketsFor(ReleaseTrains.DIJKSTRA, iteration, null); + + return new Changelog(module, iteration, tickets); + } } diff --git a/src/main/java/org/springframework/data/release/maven/Artifact.java b/src/main/java/org/springframework/data/release/maven/Artifact.java new file mode 100644 index 0000000..cfc41a4 --- /dev/null +++ b/src/main/java/org/springframework/data/release/maven/Artifact.java @@ -0,0 +1,69 @@ +/* + * Copyright 2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.release.maven; + +import org.springframework.data.release.model.ArtifactVersion; +import org.springframework.data.release.model.ModuleIteration; +import org.springframework.data.release.model.ReleaseTrains; +import org.springframework.util.Assert; + +/** + * Value object to represent a Maven {@link Artifact}. + * + * @author Oliver Gierke + */ +public class Artifact { + + private static final GroupId GROUP_ID = new GroupId("org.springframework.data"); + + private final ModuleIteration module; + private final Repository repository; + private final ArtifactVersion version; + + /** + * Creates a new {@link Artifact} for the given {@link ModuleIteration}. + * + * @param module must not be {@literal null}. + */ + public Artifact(ModuleIteration module) { + + Assert.notNull(module, "Module iteration must not be null!"); + + this.module = module; + this.repository = new Repository(module.getIteration()); + this.version = ArtifactVersion.from(module); + } + + /** + * Returns the Maven artifact identifier. + * + * @return + */ + public String getArtifactId() { + + String artifactId = String.format("spring-data-%s", module.getProject().getName().toLowerCase()); + return ReleaseTrains.REST.equals(module.getProject()) ? artifactId.concat("-webmvc") : artifactId; + } + + /** + * Returns the URL pointing to the artifacts. + * + * @return + */ + public String getRootUrl() { + return String.format("%s/%s/%s/%s", repository.getUrl(), GROUP_ID.asPath(), getArtifactId(), version); + } +} diff --git a/src/main/java/org/springframework/data/release/maven/MavenVersion.java b/src/main/java/org/springframework/data/release/maven/GroupId.java similarity index 68% rename from src/main/java/org/springframework/data/release/maven/MavenVersion.java rename to src/main/java/org/springframework/data/release/maven/GroupId.java index afa0e48..1f47335 100644 --- a/src/main/java/org/springframework/data/release/maven/MavenVersion.java +++ b/src/main/java/org/springframework/data/release/maven/GroupId.java @@ -17,21 +17,17 @@ package org.springframework.data.release.maven; import lombok.Value; -import org.springframework.data.release.model.Version; - /** + * Value object to represent an artifacts group identifier. + * * @author Oliver Gierke */ @Value -public class MavenVersion { +class GroupId { - private final Version version; + private final String value; - public String getReleaseVersion() { - return String.format("%s.RELEASE", version.toMajorMinorBugfix()); - } - - public String getSnapshotVersion() { - return String.format("%s.BUILD-SNAPSHOT", version.toMajorMinorBugfix()); + public String asPath() { + return value.replace('.', '/'); } } diff --git a/src/main/java/org/springframework/data/release/maven/MavenConfig.java b/src/main/java/org/springframework/data/release/maven/MavenConfig.java index 93a8fef..ac38b82 100644 --- a/src/main/java/org/springframework/data/release/maven/MavenConfig.java +++ b/src/main/java/org/springframework/data/release/maven/MavenConfig.java @@ -17,23 +17,56 @@ package org.springframework.data.release.maven; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.data.release.model.ArtifactVersion; import org.xmlbeam.ProjectionFactory; import org.xmlbeam.XBProjector; import org.xmlbeam.XBProjector.Flags; import org.xmlbeam.config.DefaultXMLFactoriesConfig; import org.xmlbeam.config.DefaultXMLFactoriesConfig.NamespacePhilosophy; +import org.xmlbeam.types.DefaultTypeConverter; +import org.xmlbeam.types.TypeConverter; /** * @author Oliver Gierke */ @Configuration -public class MavenConfig { +class MavenConfig { @Bean public ProjectionFactory projectionFactory() { + TypeConverter converter = new DefaultTypeConverter().setConversionForType(ArtifactVersion.class, + new ArtifactVersionConverter()); + DefaultXMLFactoriesConfig config = new DefaultXMLFactoriesConfig(); config.setNamespacePhilosophy(NamespacePhilosophy.AGNOSTIC); - return new XBProjector(config, Flags.TO_STRING_RENDERS_XML); + + XBProjector projector = new XBProjector(config, Flags.TO_STRING_RENDERS_XML); + projector.config().setTypeConverter(converter); + + return projector; + } + + /** + * Custom converter to be able to use {@link ArtifactVersion} directly from within an XmlBeam projection. + * + * @author Oliver Gierke + */ + private static class ArtifactVersionConverter extends DefaultTypeConverter.Conversion { + + private static final long serialVersionUID = 1L; + + public ArtifactVersionConverter() { + super(null); + } + + /* + * (non-Javadoc) + * @see org.xmlbeam.types.DefaultTypeConverter.Conversion#convert(java.lang.String) + */ + @Override + public ArtifactVersion convert(String data) { + return ArtifactVersion.parse(data); + } } } diff --git a/src/main/java/org/springframework/data/release/maven/MavenOperations.java b/src/main/java/org/springframework/data/release/maven/MavenOperations.java index b0c1ff3..ad61b18 100644 --- a/src/main/java/org/springframework/data/release/maven/MavenOperations.java +++ b/src/main/java/org/springframework/data/release/maven/MavenOperations.java @@ -17,14 +17,24 @@ package org.springframework.data.release.maven; import java.io.File; import java.io.IOException; +import java.util.logging.Logger; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.release.io.CommandResult; +import org.springframework.data.release.io.OsCommandOperations; import org.springframework.data.release.io.Workspace; +import org.springframework.data.release.model.ArtifactVersion; +import org.springframework.data.release.model.Iteration; +import org.springframework.data.release.model.ModuleIteration; import org.springframework.data.release.model.Project; +import org.springframework.data.release.model.ReleaseTrains; +import org.springframework.data.release.model.Train; +import org.springframework.shell.support.logging.HandlerUtils; import org.springframework.stereotype.Component; import org.xmlbeam.ProjectionFactory; +import org.xmlbeam.io.XBFileIO; /** * @author Oliver Gierke @@ -33,14 +43,91 @@ import org.xmlbeam.ProjectionFactory; @RequiredArgsConstructor(onConstructor = @__(@Autowired)) public class MavenOperations { + private static final Logger LOGGER = HandlerUtils.getLogger(MavenOperations.class); private static final String POM_XML = "pom.xml"; private final Workspace workspace; private final ProjectionFactory projectionFactory; + private final OsCommandOperations os; public Pom getMavenProject(Project project) throws IOException { File file = workspace.getFile(POM_XML, project); return projectionFactory.io().file(file).read(Pom.class); } + + public void prepareProject(Train train, Iteration iteration, Project project) throws IOException { + + if (ReleaseTrains.BUILD.equals(project)) { + return; + } + + ArtifactVersion commonsVersion = train.getModuleVersion(ReleaseTrains.COMMONS, iteration); + ArtifactVersion buildVersion = train.getModuleVersion(ReleaseTrains.BUILD, iteration); + Repository repository = new Repository(iteration); + + File file = workspace.getFile(POM_XML, project); + XBFileIO io = projectionFactory.io().file(file); + Pom pom = io.read(Pom.class); + + if (!project.equals(ReleaseTrains.COMMONS)) { + pom.setProperty("spring.data.commons", commonsVersion); + } + + pom.setParentVersion(buildVersion); + pom.setRepositoryId(repository.getSnapshotId(), repository.getId()); + pom.setRepositoryUrl(repository.getId(), repository.getUrl()); + + io.write(pom); + } + + /** + * Triggers building the distribution artifacts for all Maven projects of the given {@link Train}. + * + * @param train + * @param iteration + * @throws IOException + * @throws InterruptedException + */ + public void triggerDistributionBuild(Train train, Iteration iteration) throws Exception { + + for (ModuleIteration moduleIteration : train.getModuleIterations(iteration)) { + + Project project = moduleIteration.getProject(); + + if (ReleaseTrains.BUILD.equals(project)) { + continue; + } + + if (!isMavenProject(project)) { + LOGGER.info(String.format("Skipping project %s as no pom.xml could be found in the working directory!", + project.getFullName())); + continue; + } + + LOGGER.info(String.format("Triggering distribution build for %s…", project.getFullName())); + + ArtifactVersion version = ArtifactVersion.from(moduleIteration); + + String command = "mvn clean deploy -DskipTests -Pdistribute"; + + if (version.isMilestoneVersion()) { + command = command.concat(",milestone"); + } else if (version.isReleaseVersion()) { + command = command.concat(",release"); + } + + CommandResult result = os.executeWithOutput(command, moduleIteration.getProject()).get(); + + if (result.hasError()) { + throw result.getException(); + } + + LOGGER.info(String.format("Successfully finished distribution build for %s!", project)); + } + } + + private boolean isMavenProject(Project project) { + return workspace.getFile(POM_XML, project).exists(); + } } diff --git a/src/main/java/org/springframework/data/release/maven/MavenProject.java b/src/main/java/org/springframework/data/release/maven/MavenProject.java index 3594aaa..3682d4f 100644 --- a/src/main/java/org/springframework/data/release/maven/MavenProject.java +++ b/src/main/java/org/springframework/data/release/maven/MavenProject.java @@ -17,6 +17,7 @@ package org.springframework.data.release.maven; import lombok.Value; +import org.springframework.data.release.model.ArtifactVersion; import org.springframework.data.release.model.Module; /** @@ -35,7 +36,7 @@ public class MavenProject { return String.format("spring-data-%s", module.getProject().getName().toLowerCase()); } - public MavenVersion getVersion() { - return new MavenVersion(module.getVersion()); + public ArtifactVersion getReleaseVersion() { + return new ArtifactVersion(module.getVersion()); } } diff --git a/src/main/java/org/springframework/data/release/maven/Pom.java b/src/main/java/org/springframework/data/release/maven/Pom.java index d52894e..7fe5471 100644 --- a/src/main/java/org/springframework/data/release/maven/Pom.java +++ b/src/main/java/org/springframework/data/release/maven/Pom.java @@ -15,7 +15,9 @@ */ package org.springframework.data.release.maven; +import org.springframework.data.release.model.ArtifactVersion; import org.xmlbeam.annotation.XBRead; +import org.xmlbeam.annotation.XBValue; import org.xmlbeam.annotation.XBWrite; /** @@ -27,7 +29,7 @@ public interface Pom { Artifact getArtifactId(); @XBRead("/project/version") - String getVersion(); + ArtifactVersion getVersion(); @XBWrite("/project/version") void setVersion(String version); @@ -35,6 +37,18 @@ public interface Pom { @XBRead("/project/repositories/repository[id=\"spring-libs-snapshot\"]") Repository getSpringRepository(); + @XBWrite("/project/parent/version") + void setParentVersion(ArtifactVersion version); + + @XBWrite("/project/properties/{0}") + void setProperty(String property, @XBValue ArtifactVersion value); + + @XBWrite("/project/repositories/repository[id={0}]/id") + void setRepositoryId(String oldId, @XBValue String newId); + + @XBWrite("/project/repositories/repository[id={0}]/url") + void setRepositoryUrl(String id, @XBValue String url); + public interface Repository { @XBRead("child::id") diff --git a/src/main/java/org/springframework/data/release/maven/Repository.java b/src/main/java/org/springframework/data/release/maven/Repository.java new file mode 100644 index 0000000..5b1dd3d --- /dev/null +++ b/src/main/java/org/springframework/data/release/maven/Repository.java @@ -0,0 +1,50 @@ +/* + * Copyright 2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.release.maven; + +import org.springframework.data.release.model.Iteration; + +/** + * @author Oliver Gierke + */ +public class Repository { + + private static final String BASE = "http://repo.spring.io/libs-"; + + private final String id; + private final String url; + + public Repository(Iteration iteration) { + this.id = iteration.isPublicVersion() ? "spring-libs-release" : "spring-libs-milestone"; + this.url = iteration.isPublicVersion() ? BASE.concat("release") : BASE.concat("milestone"); + } + + public String getId() { + return id; + } + + public String getSnapshotId() { + return "spring-libs-snapshot"; + } + + public String getUrl() { + return url; + } + + public String getSnapshotUrl() { + return BASE.concat("snapshot"); + } +} diff --git a/src/main/java/org/springframework/data/release/model/ArtifactVersion.java b/src/main/java/org/springframework/data/release/model/ArtifactVersion.java new file mode 100644 index 0000000..85343c7 --- /dev/null +++ b/src/main/java/org/springframework/data/release/model/ArtifactVersion.java @@ -0,0 +1,151 @@ +/* + * Copyright 2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.release.model; + +import lombok.EqualsAndHashCode; +import lombok.RequiredArgsConstructor; + +import org.springframework.util.Assert; + +/** + * Value object to represent version of a particular artifact. + * + * @author Oliver Gierke + */ +@RequiredArgsConstructor +@EqualsAndHashCode +public class ArtifactVersion implements Comparable { + + private static final String RELEASE_SUFFIX = "RELEASE"; + private static final String MILESTONE_SUFFIX = "M\\d|RC\\d"; + private static final String SNAPSHOT_SUFFIX = "BUILD-SNAPSHOT"; + + private static final String VALID_SUFFIX = String.format("%s|%s|%s", RELEASE_SUFFIX, MILESTONE_SUFFIX, + SNAPSHOT_SUFFIX); + + private final Version version; + private final String suffix; + + /** + * Creates a new {@link ArtifactVersion} from the given logical {@link Version}. + * + * @param version must not be {@literal null}. + */ + public ArtifactVersion(Version version) { + + Assert.notNull(version, "Version must not be null!"); + + this.version = version; + this.suffix = RELEASE_SUFFIX; + } + + /** + * Parses the given {@link String} into an {@link ArtifactVersion}. + * + * @param source must not be {@literal null} or empty. + * @return + */ + public static ArtifactVersion parse(String source) { + + Assert.hasText(source, "Version source must not be null or empty!"); + + int suffixStart = source.lastIndexOf('.'); + + Version version = Version.parse(source.substring(0, suffixStart)); + String suffix = source.substring(suffixStart + 1); + + Assert.isTrue(suffix.matches(VALID_SUFFIX), "Invalid version suffix!"); + + return new ArtifactVersion(version, suffix); + } + + /** + * Creates a new {@link ArtifactVersion} from the given {@link IterationVersion}. + * + * @param iterationVersion must not be {@literal null}. + * @return + */ + public static ArtifactVersion from(IterationVersion iterationVersion) { + + Assert.notNull(iterationVersion, "IterationVersion must not be null!"); + + Version version = iterationVersion.getVersion(); + String iterationName = iterationVersion.getIteration().getName(); + + if (iterationName.equals("GA")) { + return new ArtifactVersion(version, RELEASE_SUFFIX); + } + + if (iterationName.startsWith("SR")) { + int bugfixDigits = Integer.parseInt(iterationName.substring(2, iterationName.length())); + return new ArtifactVersion(version.withBugfix(bugfixDigits), RELEASE_SUFFIX); + } + + return new ArtifactVersion(version, iterationName); + } + + /** + * Returns the release version for the current a + * + * @return + */ + public ArtifactVersion getReleaseVersion() { + return new ArtifactVersion(version, RELEASE_SUFFIX); + } + + public ArtifactVersion getSnapshotVersion() { + return new ArtifactVersion(version, SNAPSHOT_SUFFIX); + } + + public boolean isReleaseVersion() { + return suffix.equals(RELEASE_SUFFIX); + } + + public boolean isMilestoneVersion() { + return suffix.matches(MILESTONE_SUFFIX); + } + + public ArtifactVersion getNextDevelopmentVersion() { + + if (suffix.equals(SNAPSHOT_SUFFIX)) { + return this; + } + + if (suffix.equals(RELEASE_SUFFIX)) { + return new ArtifactVersion(version.nextBugfix(), SNAPSHOT_SUFFIX); + } + + return new ArtifactVersion(version, SNAPSHOT_SUFFIX); + } + + /* + * (non-Javadoc) + * @see java.lang.Comparable#compareTo(java.lang.Object) + */ + @Override + public int compareTo(ArtifactVersion that) { + return this.toString().compareTo(that.toString()); + } + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return String.format("%s.%s", version.toMajorMinorBugfix(), suffix); + } +} diff --git a/src/main/java/org/springframework/data/release/model/IterationVersion.java b/src/main/java/org/springframework/data/release/model/IterationVersion.java new file mode 100644 index 0000000..a6159d3 --- /dev/null +++ b/src/main/java/org/springframework/data/release/model/IterationVersion.java @@ -0,0 +1,26 @@ +/* + * Copyright 2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.release.model; + +/** + * @author Oliver Gierke + */ +public interface IterationVersion { + + Version getVersion(); + + Iteration getIteration(); +} diff --git a/src/main/java/org/springframework/data/release/model/ModuleIteration.java b/src/main/java/org/springframework/data/release/model/ModuleIteration.java index d880f87..190cbe5 100644 --- a/src/main/java/org/springframework/data/release/model/ModuleIteration.java +++ b/src/main/java/org/springframework/data/release/model/ModuleIteration.java @@ -15,13 +15,15 @@ */ package org.springframework.data.release.model; -import lombok.Value; +import lombok.EqualsAndHashCode; +import lombok.RequiredArgsConstructor; /** * @author Oliver Gierke */ -@Value -public class ModuleIteration { +@RequiredArgsConstructor +@EqualsAndHashCode +public class ModuleIteration implements IterationVersion { private final Module module; private final Iteration iteration; @@ -31,6 +33,23 @@ public class ModuleIteration { return module.getProject().getKey(); } + public Project getProject() { + return module.getProject(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.release.model.IterationVersion#getVersion() + */ + @Override + public Version getVersion() { + return module.getVersion(); + } + + public Iteration getIteration() { + return module.hasCustomFirstIteration() ? module.getCustomFirstIteration() : this.iteration; + } + public String getJiraVersionName() { Iteration iteration = module.hasCustomFirstIteration() ? module.getCustomFirstIteration() : this.iteration; diff --git a/src/main/java/org/springframework/data/release/model/Project.java b/src/main/java/org/springframework/data/release/model/Project.java index aea9a4c..dd4b93c 100644 --- a/src/main/java/org/springframework/data/release/model/Project.java +++ b/src/main/java/org/springframework/data/release/model/Project.java @@ -15,6 +15,9 @@ */ package org.springframework.data.release.model; +import java.util.Arrays; +import java.util.List; + import lombok.Value; /** @@ -25,10 +28,16 @@ public class Project { private final ProjectKey key; private final String name; + private final List dependencies; - public Project(String key, String name) { + public Project(String key, String name, Project... dependencies) { this.key = new ProjectKey(key); this.name = name; + this.dependencies = Arrays.asList(dependencies); + } + + public String getFullName() { + return "Spring Data ".concat(name); } } diff --git a/src/main/java/org/springframework/data/release/model/ReleaseTrains.java b/src/main/java/org/springframework/data/release/model/ReleaseTrains.java index 0398781..f0bb297 100644 --- a/src/main/java/org/springframework/data/release/model/ReleaseTrains.java +++ b/src/main/java/org/springframework/data/release/model/ReleaseTrains.java @@ -26,28 +26,29 @@ public class ReleaseTrains { public static final List TRAINS; public static final Train CODD, DIJKSTRA, EVANS, FOWLER; - public static final Project COMMONS; + public static final Project COMMONS, BUILD, REST; - private static final Project JPA, MONGO_DB, NEO4J, SOLR, COUCHBASE, CASSANDRA, ELASTICSEARCH, REDIS, GEMFIRE, REST; + private static final Project JPA, MONGO_DB, NEO4J, SOLR, COUCHBASE, CASSANDRA, ELASTICSEARCH, REDIS, GEMFIRE; private static final List PROJECTS; static { - COMMONS = new Project("DATACMNS", "Commons"); - JPA = new Project("DATAJPA", "JPA"); - MONGO_DB = new Project("DATAMONGO", "MongoDB"); - NEO4J = new Project("DATAGRAPH", "Neo4j"); - SOLR = new Project("DATASOLR", "Solr"); - COUCHBASE = new Project("DATACOUCH", "Couchbase"); - CASSANDRA = new Project("DATACASS", "Cassandra"); - ELASTICSEARCH = new Project("DATAES", "Elasticsearch"); + BUILD = new Project("DATABUILD", "Build"); + COMMONS = new Project("DATACMNS", "Commons", BUILD); + JPA = new Project("DATAJPA", "JPA", COMMONS); + MONGO_DB = new Project("DATAMONGO", "MongoDB", COMMONS); + NEO4J = new Project("DATAGRAPH", "Neo4j", COMMONS); + SOLR = new Project("DATASOLR", "Solr", COMMONS); + COUCHBASE = new Project("DATACOUCH", "Couchbase", COMMONS); + CASSANDRA = new Project("DATACASS", "Cassandra", COMMONS); + ELASTICSEARCH = new Project("DATAES", "Elasticsearch", COMMONS); REDIS = new Project("DATAREDIS", "Redis"); - GEMFIRE = new Project("SGF", "Gemfire"); + GEMFIRE = new Project("SGF", "Gemfire", COMMONS); - REST = new Project("DATAREST", "REST"); + REST = new Project("DATAREST", "REST", COMMONS, JPA, MONGO_DB, NEO4J, GEMFIRE); - PROJECTS = Arrays.asList(COMMONS, JPA, MONGO_DB, NEO4J, SOLR, COUCHBASE, CASSANDRA, ELASTICSEARCH, REDIS, GEMFIRE, - REST); + PROJECTS = Arrays.asList(BUILD, COMMONS, JPA, MONGO_DB, NEO4J, SOLR, COUCHBASE, CASSANDRA, ELASTICSEARCH, REDIS, + GEMFIRE, REST); CODD = codd(); DIJKSTRA = dijkstra(); @@ -69,19 +70,21 @@ public class ReleaseTrains { private static Train codd() { + Module build = new Module(BUILD, "1.3"); Module commons = new Module(COMMONS, "1.7"); Module jpa = new Module(JPA, "1.5"); Module mongoDb = new Module(MONGO_DB, "1.4"); Module neo4j = new Module(NEO4J, "3.0"); Module solr = new Module(SOLR, "1.1"); - Module rest = new Module(REST, "2.1"); + Module rest = new Module(REST, "2.0"); - return new Train("Codd", commons, jpa, mongoDb, neo4j, solr, rest); + return new Train("Codd", build, commons, jpa, mongoDb, neo4j, solr, rest); } private static Train dijkstra() { + Module build = new Module(BUILD, "1.4"); Module commons = new Module(COMMONS, "1.8"); Module jpa = new Module(JPA, "1.6"); Module mongoDb = new Module(MONGO_DB, "1.5"); @@ -95,8 +98,8 @@ public class ReleaseTrains { Module rest = new Module(REST, "2.1"); - return new Train("Dijkstra", commons, jpa, mongoDb, neo4j, solr, couchbase, cassandra, elasticsearch, gemfire, - redis, rest); + return new Train("Dijkstra", build, commons, jpa, mongoDb, neo4j, solr, couchbase, cassandra, elasticsearch, + gemfire, redis, rest); } public static Train getTrainByName(String name) { diff --git a/src/main/java/org/springframework/data/release/model/Train.java b/src/main/java/org/springframework/data/release/model/Train.java index 53609df..bef5df1 100644 --- a/src/main/java/org/springframework/data/release/model/Train.java +++ b/src/main/java/org/springframework/data/release/model/Train.java @@ -92,6 +92,37 @@ public class Train implements Iterable { return new Train(name, nextModules); } + public Iterable getModuleIterations(Iteration iteration) { + return getModuleIterations(iteration, new Project[0]); + } + + public Iterable getModuleIterations(Iteration iteration, Project... exclusions) { + + List iterations = new ArrayList<>(modules.size()); + List exclusionList = Arrays.asList(exclusions); + + for (Module module : this) { + + if (exclusionList.contains(module.getProject())) { + continue; + } + + iterations.add(new ModuleIteration(module, iteration, this)); + } + + return iterations; + } + + public Iteration getIteration(String name) { + return iterations.getIterationByName(name); + } + + public ArtifactVersion getModuleVersion(Project project, Iteration iteration) { + + Module module = getModule(project); + return ArtifactVersion.from(new ModuleIteration(module, iteration, this)); + } + @Override public String toString() { diff --git a/src/main/java/org/springframework/data/release/model/Version.java b/src/main/java/org/springframework/data/release/model/Version.java index 689f773..f62d073 100644 --- a/src/main/java/org/springframework/data/release/model/Version.java +++ b/src/main/java/org/springframework/data/release/model/Version.java @@ -117,6 +117,14 @@ public class Version implements Comparable { return new Version(this.major, this.minor + 1); } + public Version nextBugfix() { + return new Version(this.major, this.minor, this.bugfix + 1); + } + + public Version withBugfix(int bugfix) { + return new Version(this.major, this.minor, bugfix); + } + public String toMajorMinorBugfix() { return String.format("%s.%s.%s", major, minor, bugfix); } diff --git a/src/test/java/org/springframework/data/release/cli/ReleaseCommandsIntegrationTests.java b/src/test/java/org/springframework/data/release/cli/ReleaseCommandsIntegrationTests.java index a3987c2..8097e67 100644 --- a/src/test/java/org/springframework/data/release/cli/ReleaseCommandsIntegrationTests.java +++ b/src/test/java/org/springframework/data/release/cli/ReleaseCommandsIntegrationTests.java @@ -18,11 +18,11 @@ package org.springframework.data.release.cli; import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; -import java.io.IOException; - import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.release.AbstractIntegrationTests; +import org.springframework.data.release.git.GitOperations; +import org.springframework.data.release.model.ReleaseTrains; /** * @author Oliver Gierke @@ -30,9 +30,13 @@ import org.springframework.data.release.AbstractIntegrationTests; public class ReleaseCommandsIntegrationTests extends AbstractIntegrationTests { @Autowired ReleaseCommands releaseCommands; + @Autowired GitOperations git; @Test - public void foo() throws IOException { + public void predictsReleasTrainCorrectly() throws Exception { + + git.update(ReleaseTrains.DIJKSTRA); + assertThat(releaseCommands.predictTrainAndIteration(), is("Dijkstra")); } } diff --git a/src/test/java/org/springframework/data/release/git/BranchUnitTests.java b/src/test/java/org/springframework/data/release/git/BranchUnitTests.java new file mode 100644 index 0000000..9b6e9dd --- /dev/null +++ b/src/test/java/org/springframework/data/release/git/BranchUnitTests.java @@ -0,0 +1,45 @@ +/* + * Copyright 2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.release.git; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +import org.junit.Test; +import org.springframework.data.release.model.Iteration; +import org.springframework.data.release.model.IterationVersion; +import org.springframework.data.release.model.SimpleIterationVersion; +import org.springframework.data.release.model.Version; + +/** + * @author Oliver Gierke + */ +public class BranchUnitTests { + + @Test + public void testname() { + + IterationVersion iterationVersion = new SimpleIterationVersion(new Version(1, 4), Iteration.RC1); + assertThat(Branch.from(iterationVersion).toString(), is("master")); + } + + @Test + public void createsBugfixBranchForServiceRelease() { + + IterationVersion iterationVersion = new SimpleIterationVersion(new Version(1, 4), Iteration.SR1); + assertThat(Branch.from(iterationVersion).toString(), is("1.4.x")); + } +} diff --git a/src/test/java/org/springframework/data/release/git/GitOperationsIntegrationTests.java b/src/test/java/org/springframework/data/release/git/GitOperationsIntegrationTests.java index 6cd6b30..5754b2a 100644 --- a/src/test/java/org/springframework/data/release/git/GitOperationsIntegrationTests.java +++ b/src/test/java/org/springframework/data/release/git/GitOperationsIntegrationTests.java @@ -15,9 +15,6 @@ */ package org.springframework.data.release.git; -import java.io.IOException; -import java.util.List; - import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.release.AbstractIntegrationTests; @@ -32,14 +29,14 @@ public class GitOperationsIntegrationTests extends AbstractIntegrationTests { @Autowired GitOperations gitOperations; @Test - public void testname() throws IOException, InterruptedException { + public void testname() throws Exception { gitOperations.update(ReleaseTrains.CODD); } @Test - public void showTags() throws IOException { + public void showTags() throws Exception { - List tags = gitOperations.getTags(ReleaseTrains.COMMONS); - System.out.println(StringUtils.collectionToDelimitedString(tags, "\n")); + Tags tags = gitOperations.getTags(ReleaseTrains.COMMONS); + System.out.println(StringUtils.collectionToDelimitedString(tags.asList(), "\n")); } } diff --git a/src/test/java/org/springframework/data/release/maven/MavenVersionUnitTests.java b/src/test/java/org/springframework/data/release/maven/MavenVersionUnitTests.java new file mode 100644 index 0000000..f13edfa --- /dev/null +++ b/src/test/java/org/springframework/data/release/maven/MavenVersionUnitTests.java @@ -0,0 +1,94 @@ +/* + * Copyright 2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.release.maven; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +import org.junit.Test; +import org.springframework.data.release.model.ArtifactVersion; +import org.springframework.data.release.model.Iteration; +import org.springframework.data.release.model.IterationVersion; +import org.springframework.data.release.model.SimpleIterationVersion; +import org.springframework.data.release.model.Version; + +/** + * @author Oliver Gierke + */ +public class MavenVersionUnitTests { + + @Test(expected = IllegalArgumentException.class) + public void rejectsInvalidVersionSuffix() { + ArtifactVersion.parse("1.4.5.GA"); + } + + @Test + public void parsesReleaseVersionCorrectly() { + + ArtifactVersion version = ArtifactVersion.parse("1.4.5.RELEASE"); + + assertThat(version.isReleaseVersion(), is(true)); + assertThat(version.getNextDevelopmentVersion(), is(ArtifactVersion.parse("1.4.6.BUILD-SNAPSHOT"))); + } + + @Test + public void createsMilestoneVersionCorrectly() { + + ArtifactVersion version = ArtifactVersion.parse("1.4.5.M1"); + + assertThat(version.isReleaseVersion(), is(false)); + assertThat(version.isMilestoneVersion(), is(true)); + } + + @Test + public void createsReleaseVersionByDefault() { + + ArtifactVersion version = new ArtifactVersion(new Version(1, 4, 5)); + + assertThat(version.isReleaseVersion(), is(true)); + assertThat(version.toString(), is("1.4.5.RELEASE")); + } + + @Test + public void createsMilestoneVersionFromIteration() { + + IterationVersion oneFourMilestoneOne = new SimpleIterationVersion(new Version(1, 4), Iteration.M1); + ArtifactVersion version = ArtifactVersion.from(oneFourMilestoneOne); + + assertThat(version.isMilestoneVersion(), is(true)); + assertThat(version.toString(), is("1.4.0.M1")); + } + + @Test + public void createsReleaseVersionFromIteration() { + + IterationVersion oneFourGA = new SimpleIterationVersion(new Version(1, 4), Iteration.GA); + ArtifactVersion version = ArtifactVersion.from(oneFourGA); + + assertThat(version.isReleaseVersion(), is(true)); + assertThat(version.toString(), is("1.4.0.RELEASE")); + } + + @Test + public void createsServiceReleaseVersionFromIteration() { + + IterationVersion oneFourServiceReleaseTwo = new SimpleIterationVersion(new Version(1, 4), Iteration.SR2); + ArtifactVersion version = ArtifactVersion.from(oneFourServiceReleaseTwo); + + assertThat(version.isReleaseVersion(), is(true)); + assertThat(version.toString(), is("1.4.2.RELEASE")); + } +} diff --git a/src/test/java/org/springframework/data/release/model/SimpleIterationVersion.java b/src/test/java/org/springframework/data/release/model/SimpleIterationVersion.java new file mode 100644 index 0000000..203c090 --- /dev/null +++ b/src/test/java/org/springframework/data/release/model/SimpleIterationVersion.java @@ -0,0 +1,25 @@ +/* + * Copyright 2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.release.model; + +import lombok.Value; + +@Value +public class SimpleIterationVersion implements IterationVersion { + + private final Version version; + private final Iteration iteration; +}