From d2beb0bd7a9df11d699436a7118ecf39a7ff4e33 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Tue, 7 Jul 2015 08:30:23 +0200 Subject: [PATCH] Moved release tool into dedicated sub-folder. --- infrastructure.properties | 5 + pom.xml | 113 +++ readme.md | 1 + .../announcement/AnnouncementOperations.java | 84 ++ .../data/release/cli/IssueTracerCommands.java | 95 +++ .../data/release/cli/ModelCommands.java | 50 ++ .../data/release/cli/ReleaseCommands.java | 123 +++ .../SpringDataReleaseCliBannerProvider.java | 72 ++ .../SpringDataReleaseCliPromptProvider.java | 47 ++ .../data/release/cli/StaticResources.java | 53 ++ .../data/release/cli/TrainConverter.java | 62 ++ .../release/cli/TrainIterationConverter.java | 75 ++ .../release/docs/DocumentationOperations.java | 77 ++ .../data/release/git/Branch.java | 65 ++ .../data/release/git/Commit.java | 52 ++ .../data/release/git/GitCommands.java | 97 +++ .../data/release/git/GitOperations.java | 429 ++++++++++ .../data/release/git/GitProject.java | 52 ++ .../data/release/git/GitServer.java | 28 + .../springframework/data/release/git/Tag.java | 75 ++ .../data/release/git/Tags.java | 84 ++ .../data/release/gradle/GradleOperations.java | 139 ++++ .../data/release/io/CommandResult.java | 33 + .../io/CommonsExecOsCommandOperations.java | 154 ++++ .../data/release/io/OsCommandOperations.java | 44 ++ .../data/release/io/OsConfiguration.java | 28 + .../data/release/io/Workspace.java | 153 ++++ .../data/release/jira/Changelog.java | 75 ++ .../data/release/jira/Credentials.java | 33 + .../data/release/jira/GitHubIssue.java | 38 + .../data/release/jira/GitHubIssueTracker.java | 170 ++++ .../data/release/jira/GitHubMilestone.java | 28 + .../data/release/jira/IssueTracker.java | 36 + .../data/release/jira/Jira.java | 226 ++++++ .../data/release/jira/JiraConfiguration.java | 72 ++ .../data/release/jira/JiraConnector.java | 41 + .../data/release/jira/JiraIssue.java | 64 ++ .../data/release/jira/JiraIssues.java | 51 ++ .../data/release/jira/JiraVersion.java | 59 ++ .../data/release/jira/JqlQuery.java | 71 ++ .../data/release/jira/Ticket.java | 39 + .../data/release/jira/Tickets.java | 61 ++ .../data/release/maven/Artifact.java | 77 ++ .../data/release/maven/GroupId.java | 33 + .../data/release/maven/MavenConfig.java | 43 + .../data/release/maven/MavenOperations.java | 258 ++++++ .../data/release/maven/MavenProject.java | 42 + .../data/release/maven/ParentPom.java | 29 + .../data/release/maven/Pom.java | 84 ++ .../data/release/maven/Repository.java | 50 ++ .../data/release/misc/ReleaseOperations.java | 131 +++ .../data/release/model/ArtifactVersion.java | 175 ++++ .../data/release/model/Iteration.java | 78 ++ .../data/release/model/IterationVersion.java | 28 + .../data/release/model/Module.java | 64 ++ .../data/release/model/ModuleIteration.java | 84 ++ .../data/release/model/Phase.java | 24 + .../data/release/model/Project.java | 80 ++ .../data/release/model/ProjectKey.java | 33 + .../data/release/model/Projects.java | 52 ++ .../data/release/model/Release.java | 33 + .../data/release/model/ReleaseTrains.java | 107 +++ .../data/release/model/Tracker.java | 24 + .../data/release/model/Train.java | 218 +++++ .../data/release/model/TrainIteration.java | 70 ++ .../data/release/model/Transition.java | 24 + .../data/release/model/Version.java | 218 +++++ .../data/release/utils/CommandUtils.java | 57 ++ .../data/release/utils/Logger.java | 49 ++ .../META-INF/spring/spring-shell-plugin.xml | 10 + src/main/resources/logback.xml | 16 + .../data/release/cli/banner.txt | 8 + .../release/AbstractIntegrationTests.java | 29 + .../data/release/ArtifactUnitTests.java | 41 + .../data/release/TestConfig.java | 28 + .../cli/ReleaseCommandsIntegrationTests.java | 42 + .../data/release/git/BranchUnitTests.java | 45 ++ .../git/GitOperationsIntegrationTests.java | 53 ++ .../data/release/git/GitProjectUnitTests.java | 38 + ...OsCommandOperationsIntegegrationTests.java | 58 ++ .../release/jira/JiraVersionUnitTests.java | 71 ++ .../release/maven/MavenIntegrationTests.java | 64 ++ .../model/ArtifactVersionUnitTests.java | 93 +++ .../release/model/IterationUnitTests.java | 24 + .../model/ModuleIterationUnitTests.java | 45 ++ .../release/model/SimpleIterationVersion.java | 25 + src/test/resources/parent-pom.xml | 745 ++++++++++++++++++ src/test/resources/sample-pom.xml | 112 +++ 88 files changed, 7066 insertions(+) create mode 100644 infrastructure.properties create mode 100644 pom.xml create mode 100644 readme.md create mode 100644 src/main/java/org/springframework/data/release/announcement/AnnouncementOperations.java create mode 100644 src/main/java/org/springframework/data/release/cli/IssueTracerCommands.java create mode 100644 src/main/java/org/springframework/data/release/cli/ModelCommands.java create mode 100644 src/main/java/org/springframework/data/release/cli/ReleaseCommands.java create mode 100644 src/main/java/org/springframework/data/release/cli/SpringDataReleaseCliBannerProvider.java create mode 100644 src/main/java/org/springframework/data/release/cli/SpringDataReleaseCliPromptProvider.java create mode 100644 src/main/java/org/springframework/data/release/cli/StaticResources.java create mode 100644 src/main/java/org/springframework/data/release/cli/TrainConverter.java create mode 100644 src/main/java/org/springframework/data/release/cli/TrainIterationConverter.java create mode 100644 src/main/java/org/springframework/data/release/docs/DocumentationOperations.java create mode 100644 src/main/java/org/springframework/data/release/git/Branch.java create mode 100644 src/main/java/org/springframework/data/release/git/Commit.java create mode 100644 src/main/java/org/springframework/data/release/git/GitCommands.java create mode 100644 src/main/java/org/springframework/data/release/git/GitOperations.java create mode 100644 src/main/java/org/springframework/data/release/git/GitProject.java create mode 100644 src/main/java/org/springframework/data/release/git/GitServer.java create mode 100644 src/main/java/org/springframework/data/release/git/Tag.java create mode 100644 src/main/java/org/springframework/data/release/git/Tags.java create mode 100644 src/main/java/org/springframework/data/release/gradle/GradleOperations.java create mode 100644 src/main/java/org/springframework/data/release/io/CommandResult.java create mode 100644 src/main/java/org/springframework/data/release/io/CommonsExecOsCommandOperations.java create mode 100644 src/main/java/org/springframework/data/release/io/OsCommandOperations.java create mode 100644 src/main/java/org/springframework/data/release/io/OsConfiguration.java create mode 100644 src/main/java/org/springframework/data/release/io/Workspace.java create mode 100644 src/main/java/org/springframework/data/release/jira/Changelog.java create mode 100644 src/main/java/org/springframework/data/release/jira/Credentials.java create mode 100644 src/main/java/org/springframework/data/release/jira/GitHubIssue.java create mode 100644 src/main/java/org/springframework/data/release/jira/GitHubIssueTracker.java create mode 100644 src/main/java/org/springframework/data/release/jira/GitHubMilestone.java create mode 100644 src/main/java/org/springframework/data/release/jira/IssueTracker.java create mode 100644 src/main/java/org/springframework/data/release/jira/Jira.java create mode 100644 src/main/java/org/springframework/data/release/jira/JiraConfiguration.java create mode 100644 src/main/java/org/springframework/data/release/jira/JiraConnector.java create mode 100644 src/main/java/org/springframework/data/release/jira/JiraIssue.java create mode 100644 src/main/java/org/springframework/data/release/jira/JiraIssues.java create mode 100644 src/main/java/org/springframework/data/release/jira/JiraVersion.java create mode 100644 src/main/java/org/springframework/data/release/jira/JqlQuery.java create mode 100644 src/main/java/org/springframework/data/release/jira/Ticket.java create mode 100644 src/main/java/org/springframework/data/release/jira/Tickets.java create mode 100644 src/main/java/org/springframework/data/release/maven/Artifact.java create mode 100644 src/main/java/org/springframework/data/release/maven/GroupId.java create mode 100644 src/main/java/org/springframework/data/release/maven/MavenConfig.java create mode 100644 src/main/java/org/springframework/data/release/maven/MavenOperations.java create mode 100644 src/main/java/org/springframework/data/release/maven/MavenProject.java create mode 100644 src/main/java/org/springframework/data/release/maven/ParentPom.java create mode 100644 src/main/java/org/springframework/data/release/maven/Pom.java create mode 100644 src/main/java/org/springframework/data/release/maven/Repository.java create mode 100644 src/main/java/org/springframework/data/release/misc/ReleaseOperations.java create mode 100644 src/main/java/org/springframework/data/release/model/ArtifactVersion.java create mode 100644 src/main/java/org/springframework/data/release/model/Iteration.java create mode 100644 src/main/java/org/springframework/data/release/model/IterationVersion.java create mode 100644 src/main/java/org/springframework/data/release/model/Module.java create mode 100644 src/main/java/org/springframework/data/release/model/ModuleIteration.java create mode 100644 src/main/java/org/springframework/data/release/model/Phase.java create mode 100644 src/main/java/org/springframework/data/release/model/Project.java create mode 100644 src/main/java/org/springframework/data/release/model/ProjectKey.java create mode 100644 src/main/java/org/springframework/data/release/model/Projects.java create mode 100644 src/main/java/org/springframework/data/release/model/Release.java create mode 100644 src/main/java/org/springframework/data/release/model/ReleaseTrains.java create mode 100644 src/main/java/org/springframework/data/release/model/Tracker.java create mode 100644 src/main/java/org/springframework/data/release/model/Train.java create mode 100644 src/main/java/org/springframework/data/release/model/TrainIteration.java create mode 100644 src/main/java/org/springframework/data/release/model/Transition.java create mode 100644 src/main/java/org/springframework/data/release/model/Version.java create mode 100644 src/main/java/org/springframework/data/release/utils/CommandUtils.java create mode 100644 src/main/java/org/springframework/data/release/utils/Logger.java create mode 100644 src/main/resources/META-INF/spring/spring-shell-plugin.xml create mode 100644 src/main/resources/logback.xml create mode 100644 src/main/resources/org/springframework/data/release/cli/banner.txt create mode 100644 src/test/java/org/springframework/data/release/AbstractIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/release/ArtifactUnitTests.java create mode 100644 src/test/java/org/springframework/data/release/TestConfig.java create mode 100644 src/test/java/org/springframework/data/release/cli/ReleaseCommandsIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/release/git/BranchUnitTests.java create mode 100644 src/test/java/org/springframework/data/release/git/GitOperationsIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/release/git/GitProjectUnitTests.java create mode 100644 src/test/java/org/springframework/data/release/io/CommonsExecOsCommandOperationsIntegegrationTests.java create mode 100644 src/test/java/org/springframework/data/release/jira/JiraVersionUnitTests.java create mode 100644 src/test/java/org/springframework/data/release/maven/MavenIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/release/model/ArtifactVersionUnitTests.java create mode 100644 src/test/java/org/springframework/data/release/model/IterationUnitTests.java create mode 100644 src/test/java/org/springframework/data/release/model/ModuleIterationUnitTests.java create mode 100644 src/test/java/org/springframework/data/release/model/SimpleIterationVersion.java create mode 100644 src/test/resources/parent-pom.xml create mode 100644 src/test/resources/sample-pom.xml diff --git a/infrastructure.properties b/infrastructure.properties new file mode 100644 index 0000000..9f5bb8a --- /dev/null +++ b/infrastructure.properties @@ -0,0 +1,5 @@ +io.workDir=~/temp/spring-data-shell + +git.username=olivergierke +git.author=Oliver Gierke +git.email=ogierke@pivotal.io \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..a36135b --- /dev/null +++ b/pom.xml @@ -0,0 +1,113 @@ + + 4.0.0 + org.springframework.data.build + spring-data-release-cli + 1.0.0.BUILD-SNAPSHOT + + + org.springframework.boot + spring-boot-starter-parent + 1.2.4.RELEASE + + + + 1.8 + org.springframework.shell.Bootstrap + + + + + + org.springframework.boot + spring-boot-starter-logging + + + + org.springframework + spring-web + + + + com.fasterxml.jackson.core + jackson-databind + + + + joda-time + joda-time + + + + org.springframework.shell + spring-shell + 1.1.0.BUILD-SNAPSHOT + + + + org.xmlbeam + xmlprojector + 1.4.7 + + + + org.apache.commons + commons-exec + 1.3 + + + + org.projectlombok + lombok + 1.16.4 + provided + + + + org.springframework.plugin + spring-plugin-core + + + + org.springframework.boot + spring-boot-starter-test + + + + org.eclipse.jgit + org.eclipse.jgit + 4.0.1.201506240215-r + + + + + + + + + + + org.codehaus.mojo + appassembler-maven-plugin + 1.7 + + + + org.springframework.shell.Bootstrap + spring-data-release-shell + + + + + + + + + + + spring-libs-snapshot + http://repo.spring.io/libs-snapshot + + + + \ No newline at end of file diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..3e12c42 --- /dev/null +++ b/readme.md @@ -0,0 +1 @@ +`mvn package appassembler:assemble && sh target/appassembler/bin/spring-data-release-shell` \ No newline at end of file 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..e504ae5 --- /dev/null +++ b/src/main/java/org/springframework/data/release/announcement/AnnouncementOperations.java @@ -0,0 +1,84 @@ +/* + * 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 static org.springframework.data.release.model.Projects.*; + +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.data.release.model.TrainIteration; +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(TrainIteration iteration) { + + Assert.notNull(iteration, "Iteration must not be null!"); + + StringBuilder builder = new StringBuilder(); + + for (ModuleIteration module : iteration.getModulesExcept(BUILD)) { + + Project project = module.getProject(); + + builder.append("* "); + builder.append(project.getFullName()).append(" "); + builder.append(module.getVersionString()); + 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(new TrainIteration(ReleaseTrains.FOWLER, Iteration.SR1))); + } +} diff --git a/src/main/java/org/springframework/data/release/cli/IssueTracerCommands.java b/src/main/java/org/springframework/data/release/cli/IssueTracerCommands.java new file mode 100644 index 0000000..15268dc --- /dev/null +++ b/src/main/java/org/springframework/data/release/cli/IssueTracerCommands.java @@ -0,0 +1,95 @@ +/* + * 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 org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.env.Environment; +import org.springframework.data.release.jira.Credentials; +import org.springframework.data.release.jira.IssueTracker; +import org.springframework.data.release.jira.JiraConnector; +import org.springframework.data.release.model.ModuleIteration; +import org.springframework.data.release.model.Project; +import org.springframework.data.release.model.TrainIteration; +import org.springframework.plugin.core.PluginRegistry; +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; +import org.springframework.util.StringUtils; + +/** + * @author Oliver Gierke + */ +@Component +public class IssueTracerCommands implements CommandMarker { + + private final PluginRegistry tracker; + private final JiraConnector jira; + private final Credentials credentials; + + /** + * @param tracker + * @param environment + */ + @Autowired + public IssueTracerCommands(PluginRegistry tracker, JiraConnector jira, Environment environment) { + + String username = environment.getProperty("jira.username", (String) null); + String password = environment.getProperty("jira.password", (String) null); + + this.tracker = tracker; + this.jira = jira; + this.credentials = StringUtils.hasText(username) ? new Credentials(username, password) : null; + } + + @CliCommand("jira evict") + public void jiraEvict() { + jira.reset(); + } + + @CliCommand(value = "jira tickets") + public String jira( + @CliOption(key = "", mandatory = true) TrainIteration iteration, // + @CliOption(key = "for-current-user", specifiedDefaultValue = "true", unspecifiedDefaultValue = "false") boolean forCurrentUser) { + + if (forCurrentUser && credentials == null) { + return "No authentication specified! Use 'jira authenticate' first!"; + } + + return jira.getTicketsFor(iteration, forCurrentUser ? credentials : null).toString(); + } + + @CliCommand("tracker changelog") + public String changelog(@CliOption(key = "", mandatory = true) TrainIteration iteration, // + @CliOption(key = "module") String moduleName) { + + if (StringUtils.hasText(moduleName)) { + + ModuleIteration module = iteration.getModule(moduleName); + return tracker.getPluginFor(module.getProject()).getChangelogFor(module).toString(); + } + + StringBuilder builder = new StringBuilder(); + + for (ModuleIteration module : iteration) { + + IssueTracker issues = tracker.getPluginFor(module.getProject()); + builder.append(issues.getChangelogFor(module)).append("\n"); + } + + return builder.toString(); + } +} diff --git a/src/main/java/org/springframework/data/release/cli/ModelCommands.java b/src/main/java/org/springframework/data/release/cli/ModelCommands.java new file mode 100644 index 0000000..522e3b1 --- /dev/null +++ b/src/main/java/org/springframework/data/release/cli/ModelCommands.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.cli; + +import java.util.ArrayList; +import java.util.List; + +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; +import org.springframework.util.StringUtils; + +/** + * @author Oliver Gierke + */ +@Component +public class ModelCommands implements CommandMarker { + + @CliCommand("trains") + public String train(@CliOption(key = { "", "train" }) Train train) { + + if (train != null) { + return train.toString(); + } + + List names = new ArrayList<>(); + + for (Train releaseTrain : ReleaseTrains.TRAINS) { + names.add(releaseTrain.getName()); + } + + return StringUtils.collectionToDelimitedString(names, ", "); + } +} diff --git a/src/main/java/org/springframework/data/release/cli/ReleaseCommands.java b/src/main/java/org/springframework/data/release/cli/ReleaseCommands.java new file mode 100644 index 0000000..ac4b94b --- /dev/null +++ b/src/main/java/org/springframework/data/release/cli/ReleaseCommands.java @@ -0,0 +1,123 @@ +/* + * 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 static org.springframework.data.release.model.Projects.*; +import lombok.RequiredArgsConstructor; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.release.docs.DocumentationOperations; +import org.springframework.data.release.git.GitOperations; +import org.springframework.data.release.git.Tags; +import org.springframework.data.release.gradle.GradleOperations; +import org.springframework.data.release.maven.MavenOperations; +import org.springframework.data.release.maven.Pom; +import org.springframework.data.release.misc.ReleaseOperations; +import org.springframework.data.release.model.ArtifactVersion; +import org.springframework.data.release.model.Module; +import org.springframework.data.release.model.Phase; +import org.springframework.data.release.model.ReleaseTrains; +import org.springframework.data.release.model.Train; +import org.springframework.data.release.model.TrainIteration; +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; + +/** + * @author Oliver Gierke + */ +@Component +@RequiredArgsConstructor(onConstructor = @__(@Autowired)) +public class ReleaseCommands implements CommandMarker { + + private final MavenOperations maven; + private final GradleOperations gradle; + private final GitOperations git; + private final ReleaseOperations misc; + private final DocumentationOperations docs; + + @CliCommand("release predict") + public String predictTrainAndIteration() throws Exception { + + 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().toString().startsWith(module.getVersion().toMajorMinorBugfix())) { + continue; + } + + return train.getName(); + } + + 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 = "", mandatory = true) TrainIteration iteration) throws Exception { + + git.checkout(iteration); + maven.triggerDistributionBuild(iteration); + } + + /** + * Prepares the release of the given iteration of the given train. + * + * @param trainName the name of the release train (ignoring case). + * @param iterationName the name of the iteration. + * @throws Exception + */ + @CliCommand(value = "release prepare", help = "Prepares the release of the iteration of the given train.") + public void prepare(@CliOption(key = "", mandatory = true) TrainIteration iteration) throws Exception { + + git.prepare(iteration); + + misc.prepareChangelogs(iteration); + misc.updateResources(iteration); + docs.updateDockbookIncludes(iteration); + + maven.updatePom(iteration, Phase.PREPARE); + gradle.updateProject(iteration, Phase.PREPARE); + + git.commit(iteration, "Prepare %s.", null); + } + + @CliCommand(value = "release conclude") + public void conclude(@CliOption(key = "", mandatory = true) TrainIteration iteration) throws Exception { + + git.tagRelease(iteration); + + maven.updatePom(iteration, Phase.CLEANUP); + gradle.updateProject(iteration, Phase.CLEANUP); + + git.commit(iteration, "After release cleanups.", null); + } +} diff --git a/src/main/java/org/springframework/data/release/cli/SpringDataReleaseCliBannerProvider.java b/src/main/java/org/springframework/data/release/cli/SpringDataReleaseCliBannerProvider.java new file mode 100644 index 0000000..106db4e --- /dev/null +++ b/src/main/java/org/springframework/data/release/cli/SpringDataReleaseCliBannerProvider.java @@ -0,0 +1,72 @@ +/* + * 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 org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.shell.plugin.BannerProvider; +import org.springframework.shell.support.util.FileUtils; +import org.springframework.shell.support.util.OsUtils; +import org.springframework.stereotype.Component; + +@Order(Ordered.HIGHEST_PRECEDENCE) +@Component +class SpringDataReleaseCliBannerProvider implements BannerProvider { + + /* + * (non-Javadoc) + * @see org.springframework.shell.plugin.NamedProvider#getProviderName() + */ + @Override + public String getProviderName() { + return "Spring Data Release Shell"; + } + + /* + * (non-Javadoc) + * @see org.springframework.shell.plugin.BannerProvider#getBanner() + */ + @Override + public String getBanner() { + + StringBuilder builder = new StringBuilder(); + + builder.append(FileUtils.readBanner(SpringDataReleaseCliBannerProvider.class, "banner.txt")); + builder.append(getVersion()).append(OsUtils.LINE_SEPARATOR); + builder.append(OsUtils.LINE_SEPARATOR); + + return builder.toString(); + } + + /* + * (non-Javadoc) + * @see org.springframework.shell.plugin.BannerProvider#getVersion() + */ + @Override + public String getVersion() { + return "1.0"; + } + + /* + * (non-Javadoc) + * @see org.springframework.shell.plugin.BannerProvider#getWelcomeMessage() + */ + @Override + public String getWelcomeMessage() { + return "Welcome to the Spring Data Release Shell!"; + } + +} diff --git a/src/main/java/org/springframework/data/release/cli/SpringDataReleaseCliPromptProvider.java b/src/main/java/org/springframework/data/release/cli/SpringDataReleaseCliPromptProvider.java new file mode 100644 index 0000000..1f89041 --- /dev/null +++ b/src/main/java/org/springframework/data/release/cli/SpringDataReleaseCliPromptProvider.java @@ -0,0 +1,47 @@ +/* + * 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 org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.shell.plugin.PromptProvider; +import org.springframework.stereotype.Component; + +/** + * @author Oliver Gierke + */ +@Component +@Order(Ordered.HIGHEST_PRECEDENCE) +class SpringDataReleaseCliPromptProvider implements PromptProvider { + + /* + * (non-Javadoc) + * @see org.springframework.shell.plugin.PromptProvider#getPrompt() + */ + @Override + public String getPrompt() { + return "$ "; + } + + /* + * (non-Javadoc) + * @see org.springframework.shell.plugin.NamedProvider#getProviderName() + */ + @Override + public String getProviderName() { + return "spring-data-release-cli"; + } +} 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..a2bb509 --- /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/html"); + } + + 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/cli/TrainConverter.java b/src/main/java/org/springframework/data/release/cli/TrainConverter.java new file mode 100644 index 0000000..5ab2deb --- /dev/null +++ b/src/main/java/org/springframework/data/release/cli/TrainConverter.java @@ -0,0 +1,62 @@ +/* + * 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 java.util.List; + +import org.springframework.data.release.model.ReleaseTrains; +import org.springframework.data.release.model.Train; +import org.springframework.shell.core.Completion; +import org.springframework.shell.core.Converter; +import org.springframework.shell.core.MethodTarget; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +/** + * @author Oliver Gierke + */ +@Component +public class TrainConverter implements Converter { + + /* + * (non-Javadoc) + * @see org.springframework.shell.core.Converter#supports(java.lang.Class, java.lang.String) + */ + @Override + public boolean supports(Class type, String optionContext) { + return Train.class.isAssignableFrom(type); + } + + /* + * (non-Javadoc) + * @see org.springframework.shell.core.Converter#convertFromText(java.lang.String, java.lang.Class, java.lang.String) + */ + @Override + public Train convertFromText(String value, Class targetType, String optionContext) { + return StringUtils.hasText(value) ? ReleaseTrains.getTrainByName(value) : null; + } + + /* + * (non-Javadoc) + * @see org.springframework.shell.core.Converter#getAllPossibleValues(java.util.List, java.lang.Class, java.lang.String, java.lang.String, org.springframework.shell.core.MethodTarget) + */ + @Override + public boolean getAllPossibleValues(List completions, Class targetType, String existingData, + String optionContext, MethodTarget target) { + + return false; + } +} diff --git a/src/main/java/org/springframework/data/release/cli/TrainIterationConverter.java b/src/main/java/org/springframework/data/release/cli/TrainIterationConverter.java new file mode 100644 index 0000000..6850c2a --- /dev/null +++ b/src/main/java/org/springframework/data/release/cli/TrainIterationConverter.java @@ -0,0 +1,75 @@ +/* + * 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 java.util.List; + +import org.springframework.data.release.model.Iteration; +import org.springframework.data.release.model.ReleaseTrains; +import org.springframework.data.release.model.Train; +import org.springframework.data.release.model.TrainIteration; +import org.springframework.shell.core.Completion; +import org.springframework.shell.core.Converter; +import org.springframework.shell.core.MethodTarget; +import org.springframework.stereotype.Component; + +/** + * @author Oliver Gierke + */ +@Component +public class TrainIterationConverter implements Converter { + + /* + * (non-Javadoc) + * @see org.springframework.shell.core.Converter#supports(java.lang.Class, java.lang.String) + */ + @Override + public boolean supports(Class type, String optionContext) { + return TrainIteration.class.equals(type); + } + + /* + * (non-Javadoc) + * @see org.springframework.shell.core.Converter#convertFromText(java.lang.String, java.lang.Class, java.lang.String) + */ + @Override + public TrainIteration convertFromText(String value, Class targetType, String optionContext) { + + String[] parts = value.split(" "); + Train train = ReleaseTrains.getTrainByName(parts[0].trim()); + Iteration iteration = train.getIteration(parts[1].trim()); + + return new TrainIteration(train, iteration); + } + + /* + * (non-Javadoc) + * @see org.springframework.shell.core.Converter#getAllPossibleValues(java.util.List, java.lang.Class, java.lang.String, java.lang.String, org.springframework.shell.core.MethodTarget) + */ + @Override + public boolean getAllPossibleValues(List completions, Class targetType, String existingData, + String optionContext, MethodTarget target) { + + for (Train train : ReleaseTrains.TRAINS) { + + for (Iteration iteration : train.getIterations()) { + completions.add(new Completion(new TrainIteration(train, iteration).toString())); + } + } + + return false; + } +} diff --git a/src/main/java/org/springframework/data/release/docs/DocumentationOperations.java b/src/main/java/org/springframework/data/release/docs/DocumentationOperations.java new file mode 100644 index 0000000..584cf89 --- /dev/null +++ b/src/main/java/org/springframework/data/release/docs/DocumentationOperations.java @@ -0,0 +1,77 @@ +/* + * 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.docs; + +import static org.springframework.data.release.model.Projects.*; +import lombok.RequiredArgsConstructor; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.release.git.GitOperations; +import org.springframework.data.release.git.GitProject; +import org.springframework.data.release.git.Tag; +import org.springframework.data.release.git.Tags; +import org.springframework.data.release.io.Workspace; +import org.springframework.data.release.io.Workspace.LineCallback; +import org.springframework.data.release.model.ModuleIteration; +import org.springframework.data.release.model.Project; +import org.springframework.data.release.model.TrainIteration; +import org.springframework.stereotype.Component; + +/** + * @author Oliver Gierke + */ +@Component +@RequiredArgsConstructor(onConstructor = @__(@Autowired)) +public class DocumentationOperations { + + private static final String INDEX_LOCATION = "/src/docbkx/index.xml"; + + private final Workspace workspace; + private final GitOperations git; + + public void updateDockbookIncludes(TrainIteration iteration) throws Exception { + + Tags tags = git.getTags(COMMONS); + + ModuleIteration commons = iteration.getModule(COMMONS); + ModuleIteration previousIteration = iteration.getPreviousIteration(commons); + + final GitProject gitProject = git.getGitProject(COMMONS); + final Tag previousTag = tags.createTag(previousIteration); + final Tag newTag = tags.createTag(commons); + + for (ModuleIteration module : iteration) { + + Project project = module.getProject(); + + if (!project.dependsOn(COMMONS)) { + continue; + } + + workspace.processFile(INDEX_LOCATION, project, new LineCallback() { + + @Override + public String doWith(String line, long number) { + + boolean isInclude = line.contains("xi:include"); + boolean containsGitRepo = line.contains(gitProject.getRepositoryName()); + + return isInclude && containsGitRepo ? line.replace(previousTag.toString(), newTag.toString()) : line; + } + }); + } + } +} 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..b71988a --- /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 +class Branch { + + public 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/Commit.java b/src/main/java/org/springframework/data/release/git/Commit.java new file mode 100644 index 0000000..d7bfd98 --- /dev/null +++ b/src/main/java/org/springframework/data/release/git/Commit.java @@ -0,0 +1,52 @@ +/* + * 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.jira.Ticket; + +/** + * @author Oliver Gierke + */ +@EqualsAndHashCode +@RequiredArgsConstructor +public class Commit { + + private final Ticket ticket; + private final String summary; + private final String details; + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + + StringBuilder builder = new StringBuilder(); + + builder.append(ticket.getId()).append(" - ").append(summary).append("\n"); + + if (details != null) { + builder.append("\n"); + builder.append(details).append("\n"); + } + + return builder.toString(); + } +} diff --git a/src/main/java/org/springframework/data/release/git/GitCommands.java b/src/main/java/org/springframework/data/release/git/GitCommands.java new file mode 100644 index 0000000..db07d85 --- /dev/null +++ b/src/main/java/org/springframework/data/release/git/GitCommands.java @@ -0,0 +1,97 @@ +/* + * 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.RequiredArgsConstructor; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.release.model.Project; +import org.springframework.data.release.model.ReleaseTrains; +import org.springframework.data.release.model.Train; +import org.springframework.data.release.model.TrainIteration; +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; +import org.springframework.util.StringUtils; + +/** + * @author Oliver Gierke + */ +@Component +@RequiredArgsConstructor(onConstructor = @__(@Autowired)) +public class GitCommands implements CommandMarker { + + private final GitOperations git; + + @CliCommand("git checkout") + public void checkout(@CliOption(key = "", mandatory = true) TrainIteration iteration) throws Exception { + git.checkout(iteration); + } + + @CliCommand("git update") + public void checkout(@CliOption(key = { "", "train" }, mandatory = true) String trainName) throws Exception, + InterruptedException { + git.update(ReleaseTrains.getTrainByName(trainName)); + } + + @CliCommand("git tags") + public String tags(@CliOption(key = { "project" }, mandatory = true) String projectName) throws Exception { + + Project project = ReleaseTrains.getProjectByName(projectName); + + return StringUtils.collectionToDelimitedString(git.getTags(project).asList(), "\n"); + } + + /** + * Resets all projects contained in the given {@link Train}. + * + * @param trainName + * @throws Exception + */ + @CliCommand("git reset") + public void reset(@CliOption(key = "", mandatory = true) TrainIteration iteration) throws Exception { + git.reset(iteration); + } + + @CliCommand("git prepare") + public void prepare(@CliOption(key = "", mandatory = true) TrainIteration iteration) throws Exception { + git.prepare(iteration); + } + + /** + * Pushes all changes of all modules of the given {@link TrainIteration} to the remote server. If {@code tags} is + * given, only the tags are pushed. + * + * @param iteration + * @param tags + * @throws Exception + */ + @CliCommand("git push") + public void push(// + @CliOption(key = "", mandatory = true) TrainIteration iteration, // + @CliOption(key = "tags", specifiedDefaultValue = "true", unspecifiedDefaultValue = "false") String tags) + throws Exception { + + boolean pushTags = Boolean.parseBoolean(tags); + + if (pushTags) { + git.pushTags(iteration.getTrain()); + } else { + git.push(iteration); + } + } +} diff --git a/src/main/java/org/springframework/data/release/git/GitOperations.java b/src/main/java/org/springframework/data/release/git/GitOperations.java new file mode 100644 index 0000000..b49ad9f --- /dev/null +++ b/src/main/java/org/springframework/data/release/git/GitOperations.java @@ -0,0 +1,429 @@ +/* + * 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.RequiredArgsConstructor; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Future; +import java.util.stream.Collectors; + +import javax.annotation.PostConstruct; + +import org.eclipse.jgit.api.CheckoutCommand; +import org.eclipse.jgit.api.CreateBranchCommand.SetupUpstreamMode; +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.ResetCommand.ResetType; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.storage.file.FileRepositoryBuilder; +import org.eclipse.jgit.transport.CredentialsProvider; +import org.eclipse.jgit.transport.RefSpec; +import org.eclipse.jgit.transport.TagOpt; +import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.env.Environment; +import org.springframework.data.release.io.CommandResult; +import org.springframework.data.release.io.Workspace; +import org.springframework.data.release.jira.IssueTracker; +import org.springframework.data.release.jira.Ticket; +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.data.release.model.TrainIteration; +import org.springframework.data.release.utils.CommandUtils; +import org.springframework.data.release.utils.Logger; +import org.springframework.plugin.core.PluginRegistry; +import org.springframework.stereotype.Component; +import org.springframework.util.Assert; + +/** + * Component to execut Git related operations. + * + * @author Oliver Gierke + */ +@Component +@RequiredArgsConstructor(onConstructor = @__(@Autowired) ) +public class GitOperations { + + private final GitServer server = new GitServer(); + private final Workspace workspace; + private final Logger logger; + private final PluginRegistry issueTracker; + private final Environment environment; + + private CredentialsProvider credentials; + + @PostConstruct + public void init() { + this.credentials = new UsernamePasswordCredentialsProvider(environment.getProperty("git.username"), + environment.getProperty("git.password")); + } + + public GitProject getGitProject(Project project) { + return new GitProject(project, server); + } + + /** + * Resets the repositories for all modules of the given {@link Train}. + * + * @param train must not be {@literal null}. + * @throws Exception + */ + public void reset(TrainIteration train) throws Exception { + + Assert.notNull(train, "Train must not be null!"); + + for (ModuleIteration module : train) { + + Branch branch = Branch.from(module); + + try (Git git = new Git(getRepository(module.getProject()))) { + + logger.log(module, "git reset --hard origin/%s", branch); + + git.reset().// + setMode(ResetType.HARD).// + setRef("origin/".concat(branch.toString())).// + call(); + } + } + } + + /** + * 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(TrainIteration iteration) throws Exception { + + update(iteration.getTrain()); + + for (ModuleIteration module : 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)); + } + + try (Git git = new Git(getRepository(module.getProject()))) { + + logger.log(module, "git checkout %s", tag); + git.checkout().setStartPoint(tag.toString()); + } + } + + logger.log(iteration, "Successfully checked out projects."); + } + + public void prepare(TrainIteration iteration) throws Exception { + + for (ModuleIteration module : iteration) { + + Branch branch = Branch.from(module); + + update(module.getProject()); + + logger.log(module.getProject(), "git checkout %s && git pull origin %s", branch, branch); + checkout(module.getProject(), branch); + + try (Git git = new Git(getRepository(module.getProject()))) { + git.pull().// + setRebase(true).// + call(); + } + } + } + + public void update(Train train) throws Exception { + + List> executions = new ArrayList<>(); + + for (Module module : train) { + update(module.getProject()); + } + + for (Future execution : executions) { + CommandUtils.getCommandResult(execution); + } + } + + public void push(TrainIteration iteration) throws Exception { + + for (ModuleIteration module : iteration) { + + Branch branch = Branch.from(module); + + logger.log(module, "git push origin %s", branch); + + try (Git git = new Git(getRepository(module.getProject()))) { + + Ref ref = git.getRepository().getRef(branch.toString()); + + git.push().// + setRemote("origin").// + setRefSpecs(new RefSpec(ref.getName())).// + setCredentialsProvider(credentials).// + call(); + } + } + } + + public void pushTags(Train train) throws Exception { + + for (Module module : train) { + + logger.log(module.getProject(), "git push --tags"); + + try (Git git = new Git(getRepository(module.getProject()))) { + + git.push().// + setRemote("origin").// + setPushTags().// + setCredentialsProvider(this.credentials).// + call(); + } + } + } + + public void update(Project project) throws Exception { + + GitProject gitProject = new GitProject(project, server); + String repositoryName = gitProject.getRepositoryName(); + + Repository repository = getRepository(project); + + try (Git git = new Git(repository)) { + + if (workspace.hasProjectDirectory(project)) { + + logger.log(project, "Found existing repository %s. Obtaining latest changes…", repositoryName); + logger.log(project, "git checkout master && git reset --hard && git fetch --tags && git pull origin master"); + + checkout(project, Branch.MASTER); + + git.reset().setMode(ResetType.HARD).call(); + + git.fetch().setTagOpt(TagOpt.FETCH_TAGS); + + git.pull().call(); + + // return os.executeCommand("git checkout master && git reset --hard && git fetch --tags && git pull origin + // master", + // project); + + } else { + + logger.log(project, "No repository found! Cloning from %s…", gitProject.getProjectUri()); + clone(project); + + // return os.executeCommand(command); + } + } + } + + public Tags getTags(Project project) throws Exception { + + try (Git git = new Git(getRepository(project))) { + return new Tags(git.tagList().call().stream().map(ref -> new Tag(ref.getName())).collect(Collectors.toList())); + } + } + + public void tagRelease(TrainIteration iteration) throws Exception { + + for (ModuleIteration module : iteration) { + + Branch branch = Branch.from(module); + Project project = module.getProject(); + + try (Git git = new Git(getRepository(module.getProject()))) { + + logger.log(module, "git checkout %s", branch); + checkout(project, branch); + + logger.log(module, "git pull origin %s", branch); + git.pull().call(); + + ObjectId hash = getReleaseHash(module); + Tag tag = getTags(project).createTag(module); + + try (RevWalk walk = new RevWalk(git.getRepository())) { + + RevCommit commit = walk.parseCommit(hash); + + logger.log(module, "git tag %s %s", tag, hash.getName()); + git.tag().setName(tag.toString()).setObjectId(commit).call(); + } + } + } + } + + /** + * Commits all changes currently made to all modules of the given {@link TrainIteration}. The summary can contain a + * single {@code %s} placeholder which the version of the current module will get replace into. + * + * @param iteration must not be {@literal null}. + * @param summary must not be {@literal null} or empty. + * @param details can be {@literal null} or empty. + * @throws Exception + */ + public void commit(TrainIteration iteration, String summary, String details) throws Exception { + + Assert.notNull(iteration, "Train iteration must not be null!"); + Assert.hasText(summary, "Summary must not be null or empty!"); + + for (ModuleIteration module : iteration) { + commit(module, expandSummary(summary, module, iteration), details); + } + } + + private String expandSummary(String summary, ModuleIteration module, TrainIteration iteration) { + + if (!summary.contains("%s")) { + return summary; + } + + return String.format(summary, + ArtifactVersion.from(module).toString().concat(String.format(" (%s)", iteration.toString()))); + } + + /** + * Commits the given files for the given {@link ModuleIteration} using the given summary and details for the commit + * message. If no files are given, all pending changes are commited. + * + * @param module must not be {@literal null}. + * @param summary must not be {@literal null} or empty. + * @param details can be {@literal null} or empty. + * @param files can be empty. + * @throws Exception + */ + public void commit(ModuleIteration module, String summary, String details, File... files) throws Exception { + + Assert.notNull(module, "Module iteration must not be null!"); + Assert.hasText(summary, "Summary must not be null or empty!"); + + Project project = module.getProject(); + IssueTracker tracker = issueTracker.getPluginFor(project); + Ticket ticket = tracker.getReleaseTicketFor(module); + + Commit commit = new Commit(ticket, summary, details); + String author = environment.getProperty("git.author"); + String email = environment.getProperty("git.email"); + + logger.log(module, "git commit -m \"%s\" --author=\"%s <%s>\"", commit, author, email); + + try (Git git = new Git(getRepository(module.getProject()))) { + + git.commit().// + setMessage(commit.toString()).// + setAuthor(author, email).// + setAll(true).// + call(); + } + } + + private ObjectId getReleaseHash(ModuleIteration module) throws Exception { + + Project project = module.getProject(); + + Ticket releaseTicket = issueTracker.getPluginFor(project).getReleaseTicketFor(module); + String trigger = String.format("%s - Release", releaseTicket.getId()); + + try (Git git = new Git(getRepository(module.getProject()))) { + + for (RevCommit commit : git.log().setMaxCount(50).call()) { + + String summary = commit.getShortMessage(); + + if (summary.startsWith(trigger)) { + return commit.getId(); + } + } + } + + throw new IllegalStateException( + String.format("Did not find a release commit for project %s (ticket id %s)", project, releaseTicket.getId())); + } + + /** + * 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; + } + + public void checkout(Project project, Branch branch) throws Exception { + + try (Git git = new Git(getRepository(project))) { + + Ref ref = git.getRepository().getRef(branch.toString()); + CheckoutCommand checkout = git.checkout().setName(branch.toString()); + + if (ref == null) { + + checkout.setCreateBranch(true).// + setUpstreamMode(SetupUpstreamMode.TRACK).// + setStartPoint("origin/".concat(branch.toString())); + } + + checkout.call(); + } + } + + private Repository getRepository(Project project) throws Exception { + return FileRepositoryBuilder.create(workspace.getFile(".git", project)); + } + + public void clone(Project project) throws Exception { + + Git git = Git.cloneRepository().// + setURI(getGitProject(project).getProjectUri()).// + setDirectory(workspace.getProjectDirectory(project)).// + call(); + + git.checkout().setName(Branch.MASTER.toString()).// + call(); + } +} diff --git a/src/main/java/org/springframework/data/release/git/GitProject.java b/src/main/java/org/springframework/data/release/git/GitProject.java new file mode 100644 index 0000000..543b8fb --- /dev/null +++ b/src/main/java/org/springframework/data/release/git/GitProject.java @@ -0,0 +1,52 @@ +/* + * 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.Project; + +/** + * @author Oliver Gierke + */ +@EqualsAndHashCode +@RequiredArgsConstructor +public class GitProject { + + private static final String PROJECT_PREFIX = "spring-data"; + + 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/GitServer.java b/src/main/java/org/springframework/data/release/git/GitServer.java new file mode 100644 index 0000000..e6ec68c --- /dev/null +++ b/src/main/java/org/springframework/data/release/git/GitServer.java @@ -0,0 +1,28 @@ +/* + * 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; + +/** + * @author Oliver Gierke + */ +public class GitServer { + + private static final String SERVER_URI = "https://github.com/spring-projects/"; + + public String getUri() { + return SERVER_URI; + } +} diff --git a/src/main/java/org/springframework/data/release/git/Tag.java b/src/main/java/org/springframework/data/release/git/Tag.java new file mode 100644 index 0000000..c09210f --- /dev/null +++ b/src/main/java/org/springframework/data/release/git/Tag.java @@ -0,0 +1,75 @@ +/* + * 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.ArtifactVersion; + +/** + * Value object to represent an SCM tag. + * + * @author Oliver Gierke + */ +@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()); + } + + /** + * Creates a new {@link Tag} for the given {@link ArtifactVersion} based on the format of the current one. + * + * @param version + * @return + */ + public Tag createNew(ArtifactVersion version) { + return new Tag(name.startsWith("v") ? "v".concat(version.toString()) : version.toString()); + } + + /* + * (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..ec2fde6 --- /dev/null +++ b/src/main/java/org/springframework/data/release/git/Tags.java @@ -0,0 +1,84 @@ +/* + * 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.data.release.model.ArtifactVersion; +import org.springframework.data.release.model.ModuleIteration; +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); + } + + public Tag createTag(ModuleIteration iteration) { + return getLatest().createNew(ArtifactVersion.from(iteration)); + } + + /** + * 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/gradle/GradleOperations.java b/src/main/java/org/springframework/data/release/gradle/GradleOperations.java new file mode 100644 index 0000000..4198b87 --- /dev/null +++ b/src/main/java/org/springframework/data/release/gradle/GradleOperations.java @@ -0,0 +1,139 @@ +/* + * 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.gradle; + +import static org.springframework.data.release.model.Projects.*; + +import lombok.RequiredArgsConstructor; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.release.io.Workspace; +import org.springframework.data.release.io.Workspace.LineCallback; +import org.springframework.data.release.maven.Repository; +import org.springframework.data.release.model.ArtifactVersion; +import org.springframework.data.release.model.ModuleIteration; +import org.springframework.data.release.model.Phase; +import org.springframework.data.release.model.Project; +import org.springframework.data.release.model.TrainIteration; +import org.springframework.data.release.utils.Logger; +import org.springframework.stereotype.Component; + +/** + * Gradle specific operations. + * + * @author Oliver Gierke + */ +@Component +@RequiredArgsConstructor(onConstructor = @__(@Autowired) ) +public class GradleOperations { + + private static final String BUILD_GRADLE = "build.gradle"; + private static final String GRADLE_PROPERTIES = "gradle.properties"; + private static final String COMMONS_PROPERTY = "springDataCommonsVersion"; + private static final String BUILD_PROPERTY = "springDataBuildVersion"; + + private final Workspace workspace; + private final Logger logger; + + /** + * Updates all Gradle projects contained in the release. + * + * @param iteration + * @param phase + * @throws Exception + */ + public void updateProject(TrainIteration iteration, final Phase phase) throws Exception { + + final Repository repository = new Repository(iteration.getIteration()); + final ArtifactVersion commonsVersion = iteration.getModuleVersion(COMMONS); + final ArtifactVersion buildVersion = iteration.getModuleVersion(BUILD); + + for (ModuleIteration module : iteration.getModulesExcept(BUILD)) { + + final Project project = module.getProject(); + + if (!isGradleProject(project)) { + continue; + } + + workspace.processFile(GRADLE_PROPERTIES, project, new LineCallback() { + + /* + * (non-Javadoc) + * @see org.springframework.data.release.io.Workspace.LineCallback#doWith(java.lang.String, long) + */ + @Override + public String doWith(String line, long number) { + + if (line.contains(COMMONS_PROPERTY)) { + + ArtifactVersion version = phase.equals(Phase.PREPARE) ? commonsVersion + : commonsVersion.getNextDevelopmentVersion(); + + logger.log(project, "Setting Spring Data Commons version in %s to %s.", GRADLE_PROPERTIES, version); + return String.format("%s=%s", COMMONS_PROPERTY, version); + } + + if (line.contains(BUILD_PROPERTY)) { + + ArtifactVersion version = phase.equals(Phase.PREPARE) ? buildVersion + : buildVersion.getNextDevelopmentVersion(); + + logger.log(project, "Setting Spring Data Build version in %s to %s.", GRADLE_PROPERTIES, version); + return String.format("%s=%s", BUILD_PROPERTY, version); + } + + return line; + } + }); + + workspace.processFile(BUILD_GRADLE, project, new LineCallback() { + + /* + * (non-Javadoc) + * @see org.springframework.data.release.io.Workspace.LineCallback#doWith(java.lang.String, long) + */ + @Override + public String doWith(String line, long number) { + + String snapshotUrl = repository.getSnapshotUrl(); + String releaseUrl = repository.getUrl(); + String message = "Switching to Spring repository %s"; + + switch (phase) { + case CLEANUP: + logger.log(project, message, snapshotUrl); + return line.contains(releaseUrl) ? line.replace(releaseUrl, snapshotUrl) : line; + case PREPARE: + default: + logger.log(project, message, releaseUrl); + return line.contains(snapshotUrl) ? line.replace(snapshotUrl, releaseUrl) : line; + } + } + }); + } + } + + /** + * Returns whether the given project is a Gradle project (checks for the presence of a build.gradle file). + * + * @param project + * @return + */ + private boolean isGradleProject(Project project) { + return workspace.getFile(BUILD_GRADLE, project).exists(); + } +} diff --git a/src/main/java/org/springframework/data/release/io/CommandResult.java b/src/main/java/org/springframework/data/release/io/CommandResult.java new file mode 100644 index 0000000..e8c2b48 --- /dev/null +++ b/src/main/java/org/springframework/data/release/io/CommandResult.java @@ -0,0 +1,33 @@ +/* + * 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 lombok.Value; + +/** + * @author Oliver Gierke + */ +@Value +public class CommandResult { + + private final int status; + private final String output; + private final Exception exception; + + 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..0f7cb2f --- /dev/null +++ b/src/main/java/org/springframework/data/release/io/CommonsExecOsCommandOperations.java @@ -0,0 +1,154 @@ +/* + * 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 lombok.RequiredArgsConstructor; + +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 javax.annotation.PostConstruct; + +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.data.release.utils.Logger; +import org.springframework.scheduling.annotation.Async; +import org.springframework.scheduling.annotation.AsyncResult; +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 Map ENVIRONMENT = new HashMap<>(); + + private final Workspace workspace; + private final Logger logger; + + /* + * (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.log(project, 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.log(project, 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 looking 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.8 -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/OsCommandOperations.java b/src/main/java/org/springframework/data/release/io/OsCommandOperations.java new file mode 100644 index 0000000..8d18e35 --- /dev/null +++ b/src/main/java/org/springframework/data/release/io/OsCommandOperations.java @@ -0,0 +1,44 @@ +/* + * 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.IOException; +import java.util.concurrent.Future; + +import org.springframework.data.release.model.Project; + +/** + * Operations type to allow execution of native OS commands from the Spring Roo shell. + * + * @author Stefan Schmidt + * @since 1.2.0 + */ +public interface OsCommandOperations { + + /** + * Attempts the execution of a commands and delegates the output to the standard logger. + * + * @param command the command to execute + * @throws IOException if an error occurs + */ + Future executeCommand(String command) throws IOException; + + Future executeCommand(String command, Project project) 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/OsConfiguration.java b/src/main/java/org/springframework/data/release/io/OsConfiguration.java new file mode 100644 index 0000000..77d5416 --- /dev/null +++ b/src/main/java/org/springframework/data/release/io/OsConfiguration.java @@ -0,0 +1,28 @@ +/* + * 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 org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; + +/** + * @author Oliver Gierke + */ +@Configuration +@PropertySource("file:infrastructure.properties") +class OsConfiguration { + +} diff --git a/src/main/java/org/springframework/data/release/io/Workspace.java b/src/main/java/org/springframework/data/release/io/Workspace.java new file mode 100644 index 0000000..34366bc --- /dev/null +++ b/src/main/java/org/springframework/data/release/io/Workspace.java @@ -0,0 +1,153 @@ +/* + * 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.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.Path; +import java.util.Scanner; + +import javax.annotation.PostConstruct; + +import lombok.RequiredArgsConstructor; + +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; + +import com.google.common.io.Files; + +/** + * Abstraction of the workspace that is used to work with the {@link Project}'s repositories, execute builds, etc. + * + * @author Oliver Gierke + */ +@Component +@RequiredArgsConstructor(onConstructor = @__(@Autowired)) +public class Workspace { + + private static final Charset UTF_8 = Charset.forName("UTF-8"); + + public static final String WORK_DIR_PROPERTY = "io.workDir"; + + 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) { + + Assert.hasText(name, "Filename must not be null or empty!"); + Assert.notNull(project, "Project must not be null!"); + + return new File(getProjectDirectory(project), name); + } + + public boolean processFile(String filename, Project project, LineCallback callback) throws Exception { + + File file = getFile(filename, project); + + if (!file.exists()) { + return false; + } + + StringBuilder builder = new StringBuilder(); + + try (Scanner scanner = new Scanner(file)) { + + long number = 0; + + while (scanner.hasNextLine()) { + + String result = callback.doWith(scanner.nextLine(), number++); + + if (result != null) { + builder.append(result).append("\n"); + } + } + } + + writeContentToFile(filename, project, builder.toString()); + return true; + } + + private void writeContentToFile(String name, Project project, String content) throws IOException { + + File file = getFile(name, project); + Files.write(content, file, UTF_8); + } + + /** + * Initializes the working directory and creates the folders if necessary. + * + * @throws IOException + */ + @PostConstruct + public void setUp() throws IOException { + + Path path = getWorkingDirectory().toPath(); + + if (!java.nio.file.Files.exists(path)) { + java.nio.file.Files.createDirectories(path); + } + } + + public interface LineCallback { + String doWith(String line, long number); + } +} 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..facb97b --- /dev/null +++ b/src/main/java/org/springframework/data/release/jira/Changelog.java @@ -0,0 +1,75 @@ +/* + * 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.ArtifactVersion; +import org.springframework.data.release.model.ModuleIteration; +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 ModuleIteration module; + private final Tickets tickets; + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + + ArtifactVersion version = ArtifactVersion.from(module); + + String headline = String.format("Changes in version %s (%s)", version, + 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.trim()); + + if (!summary.endsWith(".")) { + builder.append("."); + } + + builder.append(OsUtils.LINE_SEPARATOR); + } + + return builder.toString(); + } +} diff --git a/src/main/java/org/springframework/data/release/jira/Credentials.java b/src/main/java/org/springframework/data/release/jira/Credentials.java new file mode 100644 index 0000000..56dc81a --- /dev/null +++ b/src/main/java/org/springframework/data/release/jira/Credentials.java @@ -0,0 +1,33 @@ +/* + * 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 javax.xml.bind.DatatypeConverter; + +import lombok.Value; + +/** + * @author Oliver Gierke + */ +@Value +public class Credentials { + + private final String username, password; + + public String asBase64() { + return DatatypeConverter.printBase64Binary(String.format("%s:%s", username, password).getBytes()); + } +} diff --git a/src/main/java/org/springframework/data/release/jira/GitHubIssue.java b/src/main/java/org/springframework/data/release/jira/GitHubIssue.java new file mode 100644 index 0000000..10578d5 --- /dev/null +++ b/src/main/java/org/springframework/data/release/jira/GitHubIssue.java @@ -0,0 +1,38 @@ +/* + * 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 lombok.Data; + +import org.springframework.data.release.model.ModuleIteration; + +/** + * @author Oliver Gierke + */ +@Data +class GitHubIssue { + + private String number; + private String title; + + public String getId() { + return "#".concat(number); + } + + public boolean isReleaseTicket(ModuleIteration module) { + return title.contains("Release") && title.contains(module.getVersionString()); + } +} diff --git a/src/main/java/org/springframework/data/release/jira/GitHubIssueTracker.java b/src/main/java/org/springframework/data/release/jira/GitHubIssueTracker.java new file mode 100644 index 0000000..552ede0 --- /dev/null +++ b/src/main/java/org/springframework/data/release/jira/GitHubIssueTracker.java @@ -0,0 +1,170 @@ +/* + * 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.net.URI; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import lombok.RequiredArgsConstructor; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.data.release.git.GitProject; +import org.springframework.data.release.git.GitServer; +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.Projects; +import org.springframework.data.release.model.ReleaseTrains; +import org.springframework.data.release.model.Tracker; +import org.springframework.data.release.model.TrainIteration; +import org.springframework.data.release.utils.Logger; +import org.springframework.http.HttpMethod; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestOperations; +import org.springframework.web.util.UriTemplate; + +/** + * @author Oliver Gierke + */ +@Component +@RequiredArgsConstructor(onConstructor = @_(@Autowired)) +class GitHubIssueTracker implements IssueTracker { + + private static final String MILESTONE_URI = "https://api.github.com/repos/spring-projects/{repoName}/milestones?state={state}"; + private static final String URI_TEMPLATE = "https://api.github.com/repos/spring-projects/{repoName}/issues?milestone={id}&state=all"; + + private static final ParameterizedTypeReference> MILESTONES_TYPE = new ParameterizedTypeReference>() {}; + private static final ParameterizedTypeReference> ISSUES_TYPE = new ParameterizedTypeReference>() {}; + + private final RestOperations operations; + private final Logger logger; + + /* + * (non-Javadoc) + * @see org.springframework.data.release.jira.IssueTracker#getReleaseTicketFor(org.springframework.data.release.model.ModuleIteration) + */ + @Override + @Cacheable("release-tickets") + public Ticket getReleaseTicketFor(ModuleIteration module) { + + for (GitHubIssue issue : getIssuesFor(module)) { + if (issue.isReleaseTicket(module)) { + return new Ticket(issue.getId(), issue.getTitle()); + } + } + + throw new IllegalArgumentException(String.format("Could not find a release ticket for %s!", module)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.release.jira.IssueTracker#getChangelogFor(org.springframework.data.release.model.ModuleIteration) + */ + @Override + @Cacheable("changelogs") + public Changelog getChangelogFor(ModuleIteration module) { + + List issues = getIssuesFor(module); + List tickets = new ArrayList<>(issues.size()); + + for (GitHubIssue issue : issues) { + tickets.add(new Ticket(issue.getId(), issue.getTitle())); + } + + logger.log(module, "Created changelog with %s entries.", tickets.size()); + + return new Changelog(module, new Tickets(tickets)); + } + + /* + * (non-Javadoc) + * @see org.springframework.plugin.core.Plugin#supports(java.lang.Object) + */ + @Override + public boolean supports(Project project) { + return project.uses(Tracker.GITHUB); + } + + private List getIssuesFor(ModuleIteration module) { + + String repositoryName = new GitProject(module.getProject(), new GitServer()).getRepositoryName(); + + GitHubMilestone milestone = findMilestone(module, repositoryName); + + Map parameters = new HashMap<>(); + parameters.put("repoName", repositoryName); + parameters.put("id", milestone.getNumber()); + + return operations.exchange(URI_TEMPLATE, HttpMethod.GET, null, ISSUES_TYPE, parameters).getBody(); + } + + private GitHubMilestone findMilestone(ModuleIteration module, String repositoryName) { + + for (String state : Arrays.asList("close", "open")) { + + Map parameters = new HashMap<>(); + parameters.put("repoName", repositoryName); + parameters.put("state", state); + + URI milestoneUri = new UriTemplate(MILESTONE_URI).expand(parameters); + + logger.log(module, "Looking up milestone from %s…", milestoneUri); + + List exchange = operations.exchange(MILESTONE_URI, HttpMethod.GET, null, MILESTONES_TYPE, + parameters).getBody(); + + GitHubMilestone milestone = null; + + for (GitHubMilestone candidate : exchange) { + if (candidate.getTitle().contains(module.getVersionString())) { + milestone = candidate; + } + } + + if (milestone != null) { + logger.log(module, "Found milestone %s.", milestone); + return milestone; + } + } + + throw new IllegalStateException(String.format("No milestone found containing %s!", module.getVersionString())); + } + + public static void main(String[] args) { + + try (ConfigurableApplicationContext context = new ClassPathXmlApplicationContext( + "META-INF/spring/spring-shell-plugin.xml")) { + + IssueTracker tracker = context.getBean("gitHubIssueTracker", IssueTracker.class); + + TrainIteration iteration = new TrainIteration(ReleaseTrains.CODD, Iteration.SR2); + ModuleIteration module = iteration.getModule(Projects.BUILD); + + Changelog changelog = tracker.getChangelogFor(module); + System.out.println(changelog); + + System.out.println(tracker.getReleaseTicketFor(module)); + } + } +} diff --git a/src/main/java/org/springframework/data/release/jira/GitHubMilestone.java b/src/main/java/org/springframework/data/release/jira/GitHubMilestone.java new file mode 100644 index 0000000..7cee9a4 --- /dev/null +++ b/src/main/java/org/springframework/data/release/jira/GitHubMilestone.java @@ -0,0 +1,28 @@ +/* + * 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 lombok.Data; + +/** + * @author Oliver Gierke + */ +@Data +class GitHubMilestone { + + private String title; + private Long number; +} diff --git a/src/main/java/org/springframework/data/release/jira/IssueTracker.java b/src/main/java/org/springframework/data/release/jira/IssueTracker.java new file mode 100644 index 0000000..cce5634 --- /dev/null +++ b/src/main/java/org/springframework/data/release/jira/IssueTracker.java @@ -0,0 +1,36 @@ +/* + * 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 org.springframework.data.release.model.ModuleIteration; +import org.springframework.data.release.model.Project; +import org.springframework.plugin.core.Plugin; + +/** + * @author Oliver Gierke + */ +public interface IssueTracker extends Plugin { + + /** + * Returns the {@link Ticket} that tracks modifications in the context of a release. + * + * @param module the module to lookup the {@link Ticket} for. + * @return + */ + Ticket getReleaseTicketFor(ModuleIteration module); + + Changelog getChangelogFor(ModuleIteration iteration); +} diff --git a/src/main/java/org/springframework/data/release/jira/Jira.java b/src/main/java/org/springframework/data/release/jira/Jira.java new file mode 100644 index 0000000..6ac1e81 --- /dev/null +++ b/src/main/java/org/springframework/data/release/jira/Jira.java @@ -0,0 +1,226 @@ +/* + * Copyright 2013 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.net.URI; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import lombok.RequiredArgsConstructor; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +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.Tracker; +import org.springframework.data.release.model.TrainIteration; +import org.springframework.data.release.utils.Logger; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestOperations; +import org.springframework.web.util.UriTemplate; + +/** + * @author Oliver Gierke + */ +@Component +@RequiredArgsConstructor(onConstructor = @_(@Autowired)) +class Jira implements JiraConnector { + + private static final String JIRA_HOST = "https://jira.spring.io"; + private static final String BASE_URI = "/rest/api/2"; + private static final String SEARCH_TEMPLATE = JIRA_HOST + BASE_URI + + "/search?jql={jql}&fields={fields}&startAt={startAt}"; + + private final RestOperations operations; + private final Logger logger; + + /* + * (non-Javadoc) + * @see org.springframework.data.release.jira.JiraConnector#flushTickets() + */ + @Override + @CacheEvict(value = "tickets", allEntries = true) + public void reset() { + + } + + @Cacheable("release-tickets") + public Ticket getReleaseTicketFor(ModuleIteration iteration) { + + JqlQuery query = JqlQuery.from(iteration).and("summary ~ \"Release\""); + + Map parameters = new HashMap<>(); + parameters.put("jql", query); + parameters.put("fields", "summary"); + parameters.put("startAt", 0); + + JiraIssues issues = operations.exchange(SEARCH_TEMPLATE, HttpMethod.GET, null, JiraIssues.class, parameters) + .getBody(); + + JiraIssue issue = issues.getIssues().get(0); + + return new Ticket(issue.getKey(), issue.getFields().getSummary()); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.release.jira.JiraConnector#getTicketsFor(org.springframework.data.release.model.Train, org.springframework.data.release.model.Iteration, org.springframework.data.release.jira.Credentials) + */ + @Override + @Cacheable("tickets") + public Tickets getTicketsFor(TrainIteration iteration, Credentials credentials) { + + JqlQuery query = JqlQuery.from(iteration); + + HttpHeaders headers = new HttpHeaders(); + int startAt = 0; + List tickets = new ArrayList<>(); + JiraIssues issues = null; + + if (credentials != null) { + + query = query.and("assignee = currentUser()"); + + headers.set("Authorization", String.format("Basic %s", credentials.asBase64())); + + logger.log(iteration, "Retrieving tickets (for user %s)…", credentials.getUsername()); + } else { + logger.log(iteration, "Retrieving tickets…"); + } + + query = query.orderBy("updatedDate DESC"); + + do { + + Map parameters = new HashMap<>(); + parameters.put("jql", query); + parameters.put("fields", "summary,fixVersions"); + parameters.put("startAt", startAt); + + issues = operations.exchange(SEARCH_TEMPLATE, HttpMethod.GET, new HttpEntity<>(headers), JiraIssues.class, + parameters).getBody(); + + logger.log(iteration, "Got tickets %s to %s of %s.", startAt, issues.getNextStartAt(), issues.getTotal()); + + for (JiraIssue issue : issues) { + if (!issue.wasBackportedFrom(iteration.getTrain())) { + tickets.add(new Ticket(issue.getKey(), issue.getFields().getSummary())); + } + } + + startAt = issues.getNextStartAt(); + + } while (issues.hasMoreResults()); + + return new Tickets(Collections.unmodifiableList(tickets), issues.getTotal()); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.release.jira.JiraConnector#verifyBeforeRelease(org.springframework.data.release.model.Train, org.springframework.data.release.model.Iteration) + */ + @Override + public void verifyBeforeRelease(TrainIteration iteration) { + + // for each module + + // - make sure only one ticket is open + } + + /* + * + * (non-Javadoc) + * @see org.springframework.data.release.jira.JiraConnector#closeIteration(org.springframework.data.release.model.Train, org.springframework.data.release.model.Iteration, org.springframework.data.release.jira.Credentials) + */ + @Override + public void closeIteration(TrainIteration iteration, Credentials credentials) { + + // for each module + + // - close all tickets + // -- make sure only one ticket is open + // -- resolve open ticket + // -- close tickets + + // - 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 + @Cacheable("changelogs") + public Changelog getChangelogFor(ModuleIteration module) { + + Map parameters = new HashMap<>(); + parameters.put("jql", JqlQuery.from(module)); + parameters.put("fields", "summary,fixVersions"); + parameters.put("startAt", 0); + + URI searchUri = new UriTemplate(SEARCH_TEMPLATE).expand(parameters); + + logger.log(module, "Looking up JIRA issues from %s…", searchUri); + + JiraIssues issues = operations.getForObject(searchUri, JiraIssues.class); + List tickets = new ArrayList<>(); + + for (JiraIssue issue : issues) { + tickets.add(new Ticket(issue.getKey(), issue.getFields().getSummary())); + } + + logger.log(module, "Created changelog with %s entries.", tickets.size()); + + return new Changelog(module, new Tickets(tickets, tickets.size())); + } + + /* + * (non-Javadoc) + * @see org.springframework.plugin.core.Plugin#supports(java.lang.Object) + */ + @Override + public boolean supports(Project project) { + return project.uses(Tracker.JIRA); + } + + public static void main(String[] args) { + + try (ConfigurableApplicationContext context = new ClassPathXmlApplicationContext( + "META-INF/spring/spring-shell-plugin.xml")) { + + JiraConnector tracker = context.getBean(JiraConnector.class); + TrainIteration iteration = new TrainIteration(ReleaseTrains.CODD, Iteration.SR2); + ModuleIteration module = iteration.getModule("JPA"); + + // Changelog changelog = tracker.getChangelogFor(module); + // System.out.println(changelog); + + System.out.println(tracker.getReleaseTicketFor(module)); + } + } +} diff --git a/src/main/java/org/springframework/data/release/jira/JiraConfiguration.java b/src/main/java/org/springframework/data/release/jira/JiraConfiguration.java new file mode 100644 index 0000000..6a87c67 --- /dev/null +++ b/src/main/java/org/springframework/data/release/jira/JiraConfiguration.java @@ -0,0 +1,72 @@ +/* + * 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.ArrayList; +import java.util.List; + +import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.cache.concurrent.ConcurrentMapCacheManager; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.plugin.core.config.EnablePluginRegistries; +import org.springframework.web.client.RestTemplate; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * @author Oliver Gierke + */ +@Configuration +@EnableCaching +@PropertySource(value = "file:jira.properties", ignoreResourceNotFound = true) +@EnablePluginRegistries({ IssueTracker.class }) +class JiraConfiguration { + + @Bean + public CacheManager cacheManager() { + return new ConcurrentMapCacheManager(); + } + + @Bean + public ObjectMapper jacksonObjectMapper() { + + ObjectMapper mapper = new ObjectMapper(); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + + return mapper; + } + + @Bean + public RestTemplate restTemplate() { + + MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); + converter.setObjectMapper(jacksonObjectMapper()); + + List> converters = new ArrayList<>(); + converters.add(converter); + + RestTemplate template = new RestTemplate(); + template.setMessageConverters(converters); + + return template; + } +} diff --git a/src/main/java/org/springframework/data/release/jira/JiraConnector.java b/src/main/java/org/springframework/data/release/jira/JiraConnector.java new file mode 100644 index 0000000..21b638f --- /dev/null +++ b/src/main/java/org/springframework/data/release/jira/JiraConnector.java @@ -0,0 +1,41 @@ +/* + * Copyright 2013-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 org.springframework.data.release.model.Iteration; +import org.springframework.data.release.model.Train; +import org.springframework.data.release.model.TrainIteration; + +/** + * @author Oliver Gierke + */ +public interface JiraConnector extends IssueTracker { + + void reset(); + + /** + * Returns all {@link Tickets} for the given {@link Train} and {@link Iteration}. + * + * @param train must not be {@literal null}. + * @param iteration must not be {@literal null}. + * @return + */ + Tickets getTicketsFor(TrainIteration iteration, Credentials credentials); + + void verifyBeforeRelease(TrainIteration iteration); + + void closeIteration(TrainIteration iteration, Credentials credentials); +} diff --git a/src/main/java/org/springframework/data/release/jira/JiraIssue.java b/src/main/java/org/springframework/data/release/jira/JiraIssue.java new file mode 100644 index 0000000..b90f3f2 --- /dev/null +++ b/src/main/java/org/springframework/data/release/jira/JiraIssue.java @@ -0,0 +1,64 @@ +/* + * 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.List; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import org.springframework.data.release.model.Train; + +/** + * Value object to bind REST responses to. + * + * @author Oliver Gierke + */ +@Data +class JiraIssue { + + private String key; + private Fields fields; + + /** + * Returns whether the ticket is only fixed in the given {@link Train}. {@literal false} indicates the {@link Ticket} + * has been resolved for + * + * @param train + * @return + */ + public boolean wasBackportedFrom(Train train) { + + List fixVersions = fields.getFixVersions(); + return !(fixVersions.size() == 1 && fixVersions.get(0).name.contains(train.getName())); + } + + @Data + static class Fields { + + String summary; + List fixVersions; + } + + @Data + @NoArgsConstructor + @AllArgsConstructor + static class FixVersions { + + String name; + } +} diff --git a/src/main/java/org/springframework/data/release/jira/JiraIssues.java b/src/main/java/org/springframework/data/release/jira/JiraIssues.java new file mode 100644 index 0000000..d8daf87 --- /dev/null +++ b/src/main/java/org/springframework/data/release/jira/JiraIssues.java @@ -0,0 +1,51 @@ +/* + * 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.Iterator; +import java.util.List; + +import lombok.Data; + +/** + * @author Oliver Gierke + */ +@Data +class JiraIssues implements Iterable { + + private int startAt; + private int maxResults; + private int total; + + private List issues; + + public int getNextStartAt() { + return startAt + issues.size(); + } + + public boolean hasMoreResults() { + return startAt + issues.size() < total; + } + + /* + * (non-Javadoc) + * @see java.lang.Iterable#iterator() + */ + @Override + public Iterator iterator() { + return issues.iterator(); + } +} diff --git a/src/main/java/org/springframework/data/release/jira/JiraVersion.java b/src/main/java/org/springframework/data/release/jira/JiraVersion.java new file mode 100644 index 0000000..5928d33 --- /dev/null +++ b/src/main/java/org/springframework/data/release/jira/JiraVersion.java @@ -0,0 +1,59 @@ +/* + * 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 lombok.Value; + +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.Train; + +/** + * @author Oliver Gierke + */ +@Value +class JiraVersion { + + private final Module module; + private final Train train; + private final Iteration iteration; + + public JiraVersion(ModuleIteration moduleIteration) { + + this.module = moduleIteration.getModule(); + this.iteration = moduleIteration.getIteration(); + this.train = moduleIteration.getTrain(); + } + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + + Iteration iteration = this.iteration.isInitialIteration() && module.hasCustomFirstIteration() ? module + .getCustomFirstIteration() : this.iteration; + + if (iteration.isServiceIteration()) { + return String.format("%s.%s (%s %s)", module.getVersion(), iteration.getBugfixValue(), train.getName(), + iteration.getName()); + } + + return String.format("%s %s (%s)", module.getVersion(), iteration.getName(), train.getName()); + } +} diff --git a/src/main/java/org/springframework/data/release/jira/JqlQuery.java b/src/main/java/org/springframework/data/release/jira/JqlQuery.java new file mode 100644 index 0000000..d54d1c6 --- /dev/null +++ b/src/main/java/org/springframework/data/release/jira/JqlQuery.java @@ -0,0 +1,71 @@ +/* + * 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 static org.springframework.data.release.model.Projects.*; + +import java.util.ArrayList; +import java.util.List; + +import lombok.Value; + +import org.springframework.data.release.model.ModuleIteration; +import org.springframework.data.release.model.TrainIteration; +import org.springframework.util.StringUtils; + +/** + * @author Oliver Gierke + */ +@Value +class JqlQuery { + + private static final String PROJECT_VERSION_TEMPLATE = "project = %s AND fixVersion = \"%s\""; + + private final String query; + + public JqlQuery and(String clause) { + return new JqlQuery(String.format("(%s) AND %s", query, clause)); + } + + public JqlQuery orderBy(String orderBy) { + return new JqlQuery(String.format("%s ORDER BY %s", query, orderBy)); + } + + public static JqlQuery from(ModuleIteration iteration) { + + JiraVersion version = new JiraVersion(iteration); + + return new JqlQuery(String.format(PROJECT_VERSION_TEMPLATE, iteration.getProjectKey(), version)); + } + + public static JqlQuery from(TrainIteration iteration) { + + List parts = new ArrayList<>(); + + for (ModuleIteration module : iteration.getModulesExcept(BUILD)) { + + JiraVersion version = new JiraVersion(module); + parts.add(String.format(PROJECT_VERSION_TEMPLATE, module.getProjectKey(), version)); + } + + return new JqlQuery(StringUtils.collectionToDelimitedString(parts, " OR ")); + } + + @Override + public String toString() { + return query; + } +} diff --git a/src/main/java/org/springframework/data/release/jira/Ticket.java b/src/main/java/org/springframework/data/release/jira/Ticket.java new file mode 100644 index 0000000..d178bca --- /dev/null +++ b/src/main/java/org/springframework/data/release/jira/Ticket.java @@ -0,0 +1,39 @@ +/* + * 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 lombok.Value; + +/** + * Value object to represent a {@link Ticket}. + * + * @author Oliver Gierke + */ +@Value +public class Ticket { + + private final String id; + private final String summary; + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return String.format("%13s - %s", id, summary); + } +} diff --git a/src/main/java/org/springframework/data/release/jira/Tickets.java b/src/main/java/org/springframework/data/release/jira/Tickets.java new file mode 100644 index 0000000..c7d6160 --- /dev/null +++ b/src/main/java/org/springframework/data/release/jira/Tickets.java @@ -0,0 +1,61 @@ +/* + * 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.Iterator; +import java.util.List; + +import lombok.RequiredArgsConstructor; +import lombok.Value; + +import org.springframework.util.StringUtils; + +/** + * Value object to represent a list of {@link Ticket}s. + * + * @author Oliver Gierke + */ +@Value +@RequiredArgsConstructor +public class Tickets implements Iterable { + + private final List tickets; + private final int overallTotal; + + public Tickets(List tickets) { + this(tickets, tickets.size()); + } + + /* + * (non-Javadoc) + * @see java.lang.Iterable#iterator() + */ + @Override + public Iterator iterator() { + return tickets.iterator(); + } + + @Override + public String toString() { + + StringBuilder builder = new StringBuilder(); + builder.append(String.format("Train only tickets: %s of %s", tickets.size(), overallTotal)); + builder.append("\n"); + builder.append(StringUtils.collectionToDelimitedString(tickets, "\n")); + + return builder.toString(); + } +} 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..cd99321 --- /dev/null +++ b/src/main/java/org/springframework/data/release/maven/Artifact.java @@ -0,0 +1,77 @@ +/* + * 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.springframework.data.release.model.Projects.*; +import lombok.EqualsAndHashCode; +import lombok.Getter; + +import org.springframework.data.release.model.ArtifactVersion; +import org.springframework.data.release.model.ModuleIteration; +import org.springframework.util.Assert; + +/** + * Value object to represent a Maven {@link Artifact}. + * + * @author Oliver Gierke + */ +@EqualsAndHashCode +public class Artifact { + + private static final GroupId GROUP_ID = new GroupId("org.springframework.data"); + + private final ModuleIteration module; + private final Repository repository; + private final @Getter 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 REST.equals(module.getProject()) ? artifactId.concat("-webmvc") : artifactId; + } + + public ArtifactVersion getNextDevelopmentVersion() { + return version.getNextDevelopmentVersion(); + } + + /** + * 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/GroupId.java b/src/main/java/org/springframework/data/release/maven/GroupId.java new file mode 100644 index 0000000..1f47335 --- /dev/null +++ b/src/main/java/org/springframework/data/release/maven/GroupId.java @@ -0,0 +1,33 @@ +/* + * 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 lombok.Value; + +/** + * Value object to represent an artifacts group identifier. + * + * @author Oliver Gierke + */ +@Value +class GroupId { + + private final String value; + + 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 new file mode 100644 index 0000000..7e10646 --- /dev/null +++ b/src/main/java/org/springframework/data/release/maven/MavenConfig.java @@ -0,0 +1,43 @@ +/* + * 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.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.xmlbeam.ProjectionFactory; +import org.xmlbeam.XBProjector; +import org.xmlbeam.XBProjector.Flags; +import org.xmlbeam.config.DefaultXMLFactoriesConfig; +import org.xmlbeam.config.DefaultXMLFactoriesConfig.NamespacePhilosophy; + +/** + * @author Oliver Gierke + */ +@Configuration +class MavenConfig { + + @Bean + public ProjectionFactory projectionFactory() { + + DefaultXMLFactoriesConfig config = new DefaultXMLFactoriesConfig(); + config.setNamespacePhilosophy(NamespacePhilosophy.AGNOSTIC); + config.setOmitXMLDeclaration(false); + + XBProjector projector = new XBProjector(config, Flags.TO_STRING_RENDERS_XML); + + return projector; + } +} diff --git a/src/main/java/org/springframework/data/release/maven/MavenOperations.java b/src/main/java/org/springframework/data/release/maven/MavenOperations.java new file mode 100644 index 0000000..eacff6b --- /dev/null +++ b/src/main/java/org/springframework/data/release/maven/MavenOperations.java @@ -0,0 +1,258 @@ +/* + * 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.springframework.data.release.model.Phase.*; +import static org.springframework.data.release.model.Projects.*; + +import lombok.RequiredArgsConstructor; + +import java.io.File; +import java.io.IOException; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.GenericTypeResolver; +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.ModuleIteration; +import org.springframework.data.release.model.Phase; +import org.springframework.data.release.model.Project; +import org.springframework.data.release.model.Train; +import org.springframework.data.release.model.TrainIteration; +import org.springframework.data.release.utils.Logger; +import org.springframework.stereotype.Component; +import org.springframework.util.Assert; +import org.xmlbeam.ProjectionFactory; +import org.xmlbeam.io.XBFileIO; + +/** + * @author Oliver Gierke + */ +@Component +@RequiredArgsConstructor(onConstructor = @__(@Autowired) ) +public class MavenOperations { + + private static final String COMMONS_VERSION_PROPERTY = "springdata.commons"; + private static final String POM_XML = "pom.xml"; + + private final Workspace workspace; + private final ProjectionFactory projectionFactory; + private final OsCommandOperations os; + private final Logger logger; + + public Pom getMavenProject(Project project) throws IOException { + + File file = workspace.getFile(POM_XML, project); + return projectionFactory.io().file(file).read(Pom.class); + } + + /** + * Updates the POM files for all Maven projects contained in the iteration: + *
    + *
  1. Updates the BOM POM.
  2. + *
  3. Updates the dependency version to Spring Data Commons to the current release version for all projects depending + * on it.
  4. + *
  5. Switches to the Spring release Maven repository.
  6. + *
+ * If {@link Phase} is {@link Phase#CLEANUP} the changes will be rolled back. + * + * @param iteration must not be {@literal null}. + * @param phase must not be {@literal null}. + * @throws Exception + */ + public void updatePom(TrainIteration iteration, final Phase phase) throws Exception { + + Assert.notNull(iteration, "Train iteration must not be null!"); + Assert.notNull(phase, "Phase must not be null!"); + + updateBomPom(iteration, phase); + + final Repository repository = new Repository(iteration.getIteration()); + final ArtifactVersion buildVersion = iteration.getModuleVersion(BUILD); + final ArtifactVersion nextBuildVersion = buildVersion.getNextDevelopmentVersion(); + + // Fix version of shared resources to to-be-released version. + execute(workspace.getFile("parent/pom.xml", BUILD), new PomCallback() { + + @Override + public ParentPom doWith(ParentPom pom) { + pom.setSharedResourcesVersion(phase.equals(PREPARE) ? buildVersion : nextBuildVersion); + return pom; + } + }); + + for (ModuleIteration module : iteration.getModulesExcept(BUILD)) { + + final Project project = module.getProject(); + + if (!isMavenProject(project)) { + logger.log(module, "No pom.xml file found, skipping project."); + continue; + } + + execute(workspace.getFile(POM_XML, project), new PomCallback() { + + @Override + public Pom doWith(Pom pom) { + + for (Project dependency : project.getDependencies()) { + + String dependencyProperty = dependency.getDependencyProperty(); + + if (pom.getProperty(dependencyProperty) == null) { + continue; + } + + ArtifactVersion dependencyVersion = iteration.getModuleVersion(dependency); + ArtifactVersion version = CLEANUP.equals(phase) ? dependencyVersion.getNextDevelopmentVersion() + : dependencyVersion; + + logger.log(project, "Updating %s dependency version property %s to %s.", dependency.getFullName(), + dependencyProperty, version); + pom.setProperty(dependencyProperty, version); + } + + ArtifactVersion version = CLEANUP.equals(phase) ? nextBuildVersion : buildVersion; + logger.log(project, "Updating Spring Data Build Parent version to %s.", version); + pom.setParentVersion(version); + + updateRepository(project, pom, repository, phase); + + return 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(TrainIteration iteration) throws Exception { + + for (ModuleIteration moduleIteration : iteration) { + + Project project = moduleIteration.getProject(); + + if (BUILD.equals(project)) { + continue; + } + + if (!isMavenProject(project)) { + logger.log(project, "Skipping project as no pom.xml could be found in the working directory!"); + continue; + } + + logger.log(project, "Triggering distribution build…"); + + 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.log(project, "Successfully finished distribution build!"); + } + } + + private boolean isMavenProject(Project project) { + return workspace.getFile(POM_XML, project).exists(); + } + + private void updateBomPom(final TrainIteration iteration, final Phase phase) throws Exception { + + File bomPomFile = workspace.getFile("bom/pom.xml", BUILD); + + logger.log(BUILD, "Updating BOM pom.xml…"); + + execute(bomPomFile, new PomCallback() { + + @Override + public Pom doWith(Pom pom) { + + for (ModuleIteration module : iteration.getModulesExcept(BUILD)) { + + Artifact artifact = new Artifact(module); + ArtifactVersion version = artifact.getVersion(); + version = PREPARE.equals(phase) ? version : version.getNextDevelopmentVersion(); + + logger.log(BUILD, "%s", module); + + pom.setDependencyManagementVersion(artifact.getArtifactId(), version); + + for (String additionalArtifact : module.getProject().getAdditionalArtifacts()) { + pom.setDependencyManagementVersion(additionalArtifact, version); + } + } + + return pom; + } + }); + } + + private void updateRepository(Project project, Pom pom, Repository repository, Phase phase) { + + String message = "Switching to Spring repository %s (%s)."; + + if (PREPARE.equals(phase)) { + + logger.log(project, message, repository.getId(), repository.getUrl()); + + pom.setRepositoryId(repository.getSnapshotId(), repository.getId()); + pom.setRepositoryUrl(repository.getId(), repository.getUrl()); + + } else { + + logger.log(project, message, repository.getSnapshotId(), repository.getSnapshotUrl()); + + pom.setRepositoryId(repository.getId(), repository.getSnapshotId()); + pom.setRepositoryUrl(repository.getSnapshotId(), repository.getSnapshotUrl()); + } + } + + @SuppressWarnings("unchecked") + private void execute(File file, PomCallback callback) throws Exception { + + XBFileIO io = projectionFactory.io().file(file); + Class typeArgument = GenericTypeResolver.resolveTypeArgument(callback.getClass(), PomCallback.class); + + T pom = (T) io.read(typeArgument); + pom = callback.doWith(pom); + + io.write(pom); + } + + private interface PomCallback { + + public T doWith(T pom); + } +} diff --git a/src/main/java/org/springframework/data/release/maven/MavenProject.java b/src/main/java/org/springframework/data/release/maven/MavenProject.java new file mode 100644 index 0000000..6d1d7eb --- /dev/null +++ b/src/main/java/org/springframework/data/release/maven/MavenProject.java @@ -0,0 +1,42 @@ +/* + * 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 lombok.Value; + +import org.springframework.data.release.model.ArtifactVersion; +import org.springframework.data.release.model.Module; + +/** + * @author Oliver Gierke + */ +@Value +class MavenProject { + + private final Module module; + + public String getGroupId() { + return "org.springframework.data"; + } + + public String getArtifactId() { + return String.format("spring-data-%s", module.getProject().getName().toLowerCase()); + } + + public ArtifactVersion getReleaseVersion() { + return new ArtifactVersion(module.getVersion()); + } +} diff --git a/src/main/java/org/springframework/data/release/maven/ParentPom.java b/src/main/java/org/springframework/data/release/maven/ParentPom.java new file mode 100644 index 0000000..c632da6 --- /dev/null +++ b/src/main/java/org/springframework/data/release/maven/ParentPom.java @@ -0,0 +1,29 @@ +/* + * 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.xmlbeam.annotation.XBValue; +import org.xmlbeam.annotation.XBWrite; + +/** + * @author Oliver Gierke + */ +public interface ParentPom extends Pom { + + @XBWrite("/project/profiles/profile[id=\"distribute\"]/dependencies/dependency/version") + void setSharedResourcesVersion(@XBValue ArtifactVersion value); +} diff --git a/src/main/java/org/springframework/data/release/maven/Pom.java b/src/main/java/org/springframework/data/release/maven/Pom.java new file mode 100644 index 0000000..0687c2e --- /dev/null +++ b/src/main/java/org/springframework/data/release/maven/Pom.java @@ -0,0 +1,84 @@ +/* + * 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.xmlbeam.annotation.XBRead; +import org.xmlbeam.annotation.XBValue; +import org.xmlbeam.annotation.XBWrite; + +/** + * @author Oliver Gierke + */ +public interface Pom { + + @XBRead("/project") + Artifact getArtifactId(); + + @XBRead("/project/version") + ArtifactVersion getVersion(); + + @XBWrite("/project/version") + void setVersion(String version); + + @XBWrite("/project/parent/version") + void setParentVersion(ArtifactVersion version); + + @XBRead("/project/properties/{0}") + String getProperty(String property); + + @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); + + /** + * Sets the version of the dependency with the given artifact identifier to the given {@link ArtifactVersion}. + * + * @param artifactId + * @param version + */ + @XBWrite("/project/dependencies/dependency[artifactId=\"{0}\"]/version") + Pom setDependencyVersion(String artifactId, @XBValue ArtifactVersion version); + + @XBWrite("/project/dependencyManagement/dependencies/dependency[artifactId=\"{0}\"]/version") + Pom setDependencyManagementVersion(String artifactId, @XBValue ArtifactVersion version); + + public interface Repository { + + @XBRead("child::id") + String getId(); + + @XBRead("child::url") + String getUrl(); + } + + public interface Artifact { + + @XBRead("child::groupId") + String getGroupId(); + + @XBRead("child::artifactId") + String getArtifactId(); + + @XBRead("child::version") + String getVersion(); + } +} 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..1359a7a --- /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 = "https://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/misc/ReleaseOperations.java b/src/main/java/org/springframework/data/release/misc/ReleaseOperations.java new file mode 100644 index 0000000..04a9013 --- /dev/null +++ b/src/main/java/org/springframework/data/release/misc/ReleaseOperations.java @@ -0,0 +1,131 @@ +/* + * 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.misc; + +import lombok.RequiredArgsConstructor; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.release.git.GitOperations; +import org.springframework.data.release.io.Workspace; +import org.springframework.data.release.io.Workspace.LineCallback; +import org.springframework.data.release.jira.Changelog; +import org.springframework.data.release.jira.IssueTracker; +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.Train; +import org.springframework.data.release.model.TrainIteration; +import org.springframework.data.release.utils.Logger; +import org.springframework.plugin.core.PluginRegistry; +import org.springframework.stereotype.Component; +import org.springframework.util.Assert; + +/** + * @author Oliver Gierke + */ +@Component +@RequiredArgsConstructor(onConstructor = @__(@Autowired) ) +public class ReleaseOperations { + + private static final Set CHANGELOG_LOCATIONS; + + static { + + Set locations = new HashSet<>(); + locations.add("src/main/resources/changelog.txt"); // for Maven projects + locations.add("docs/src/info/changelog.txt"); // for Gradle projects + + CHANGELOG_LOCATIONS = Collections.unmodifiableSet(locations); + } + + private final PluginRegistry trackers; + private final Workspace workspace; + private final GitOperations git; + private final Logger logger; + + /** + * Creates {@link Changelog} instances for all modules of the given {@link Train} and {@link Iteration}. + * + * @param train must not be {@literal null}. + * @param iteration must not be {@literal null}. + * @throws Exception + */ + public void prepareChangelogs(TrainIteration iteration) throws Exception { + + Assert.notNull(iteration, "Iteration must not be null!"); + + for (ModuleIteration module : iteration) { + + final Changelog changelog = trackers.getPluginFor(module.getProject()).getChangelogFor(module); + + for (String location : CHANGELOG_LOCATIONS) { + + boolean processed = workspace.processFile(location, module.getProject(), new LineCallback() { + + @Override + public String doWith(String line, long number) { + + if (line.startsWith("=")) { + + StringBuilder builder = new StringBuilder(); + builder.append(line).append("\n\n"); + builder.append(changelog.toString()); + + return builder.toString(); + } else { + return line; + } + } + }); + + if (processed) { + + git.commit(module, "Updated changelog.", null); + + logger.log(module.getProject(), "Updated changelog %s.", location); + } + } + } + } + + public void updateResources(TrainIteration iteration) throws Exception { + + for (final ModuleIteration module : iteration) { + + boolean processed = workspace.processFile("src/main/resources/notice.txt", module.getProject(), + new LineCallback() { + + @Override + public String doWith(String line, long number) { + + if (number != 0) { + return line; + } + + return module.toString(); + } + }); + + if (processed) { + logger.log(module, "Updated notice.txt."); + } + } + } +} 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..2c0ff68 --- /dev/null +++ b/src/main/java/org/springframework/data/release/model/ArtifactVersion.java @@ -0,0 +1,175 @@ +/* + * 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(); + Iteration iteration = iterationVersion.getIteration(); + + if (iteration.isGAVersion()) { + return new ArtifactVersion(version, RELEASE_SUFFIX); + } + + if (iteration.isServiceIteration()) { + Version bugfixVersion = version.withBugfix(iteration.getBugfixValue()); + return new ArtifactVersion(bugfixVersion, RELEASE_SUFFIX); + } + + return new ArtifactVersion(version, iteration.getName()); + } + + /** + * Returns the release version for the current one. + * + * @return + */ + public ArtifactVersion getReleaseVersion() { + return new ArtifactVersion(version, RELEASE_SUFFIX); + } + + /** + * Returns the snapshot version of the current one. + * + * @return + */ + public ArtifactVersion getSnapshotVersion() { + return new ArtifactVersion(version, SNAPSHOT_SUFFIX); + } + + /** + * Returns whether the version is a release version. + * + * @return + */ + public boolean isReleaseVersion() { + return suffix.equals(RELEASE_SUFFIX); + } + + /** + * Returns whether the version is a milestone version. + * + * @return + */ + 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); + } + + /** + * Returns the {@link String} of the plain version (read: x.y.z, ommitting trailing bugfix zeros). + * + * @return + */ + public String toShortString() { + return version.toString(); + } +} diff --git a/src/main/java/org/springframework/data/release/model/Iteration.java b/src/main/java/org/springframework/data/release/model/Iteration.java new file mode 100644 index 0000000..236fdfa --- /dev/null +++ b/src/main/java/org/springframework/data/release/model/Iteration.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.model; + +import lombok.NonNull; +import lombok.Value; + +import org.springframework.util.Assert; + +/** + * Value object to represent an individual release train iteration. + * + * @author Oliver Gierke + */ +@Value +public class Iteration { + + public static final Iteration SR6 = new Iteration("SR6", null); + public static final Iteration SR5 = new Iteration("SR6", SR6); + public static final Iteration SR4 = new Iteration("SR4", SR5); + public static final Iteration SR3 = new Iteration("SR3", SR4); + public static final Iteration SR2 = new Iteration("SR2", SR3); + public static final Iteration SR1 = new Iteration("SR1", SR2); + public static final Iteration GA = new Iteration("GA", SR1); + public static final Iteration RC1 = new Iteration("RC1", GA); + public static final Iteration M1 = new Iteration("M1", RC1); + + /** + * The name of the iteration. + */ + private final @NonNull String name; + private final Iteration next; + + Iteration(String name, Iteration next) { + + Assert.hasText(name, "Name must not be null or empty!"); + + this.name = name; + this.next = next; + } + + public boolean isGAVersion() { + return this.equals(GA); + } + + public boolean isPublicVersion() { + return isServiceIteration() || this.equals(GA); + } + + public boolean isServiceIteration() { + return name.startsWith("SR"); + } + + public boolean isNext(Iteration iteration) { + return next.equals(iteration); + } + + public boolean isInitialIteration() { + return this.equals(M1); + } + + public int getBugfixValue() { + return name.startsWith("SR") ? Integer.parseInt(name.substring(2)) : 0; + } +} 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..09ee3bc --- /dev/null +++ b/src/main/java/org/springframework/data/release/model/IterationVersion.java @@ -0,0 +1,28 @@ +/* + * 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; + +/** + * A {@link Version} tied to an {@link Iteration}. + * + * @author Oliver Gierke + */ +public interface IterationVersion { + + Version getVersion(); + + Iteration getIteration(); +} diff --git a/src/main/java/org/springframework/data/release/model/Module.java b/src/main/java/org/springframework/data/release/model/Module.java new file mode 100644 index 0000000..35d320b --- /dev/null +++ b/src/main/java/org/springframework/data/release/model/Module.java @@ -0,0 +1,64 @@ +/* + * 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; + +import org.springframework.util.Assert; + +/** + * @author Oliver Gierke + */ +@Value +public class Module { + + private final Project project; + private final Version version; + private final Iteration customFirstIteration; + + Module(Project project, String version) { + this(project, version, null); + } + + Module(Project project, String version, String customFirstIteration) { + + Assert.notNull(project, "Project must not be null!"); + + this.project = project; + this.version = Version.parse(version); + this.customFirstIteration = customFirstIteration == null ? null + : new Iteration(customFirstIteration, Iteration.RC1); + } + + public boolean hasName(String name) { + return project.getName().equalsIgnoreCase(name); + } + + public boolean hasCustomFirstIteration() { + return customFirstIteration != null; + } + + public Module next(Transition transition) { + + Version nextVersion = Transition.MAJOR.equals(transition) ? version.nextMajor() : version.nextMinor(); + return new Module(project, nextVersion.toString()); + } + + @Override + public String toString() { + return String.format("Spring Data %s %s - %s", project.getName(), version, project.getKey()); + } +} diff --git a/src/main/java/org/springframework/data/release/model/ModuleIteration.java b/src/main/java/org/springframework/data/release/model/ModuleIteration.java new file mode 100644 index 0000000..91d605a --- /dev/null +++ b/src/main/java/org/springframework/data/release/model/ModuleIteration.java @@ -0,0 +1,84 @@ +/* + * 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.Getter; +import lombok.RequiredArgsConstructor; + +/** + * @author Oliver Gierke + */ +@RequiredArgsConstructor +@EqualsAndHashCode +public class ModuleIteration implements IterationVersion { + + private final @Getter Module module; + private final Iteration iteration; + private final @Getter Train train; + + public ProjectKey getProjectKey() { + 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(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.release.model.IterationVersion#getIteration() + */ + public Iteration getIteration() { + return this.iteration.isInitialIteration() && this.module.hasCustomFirstIteration() ? module + .getCustomFirstIteration() : this.iteration; + } + + /** + * Returns the {@link String} representation of the logical version of the {@link ModuleIteration}. + * + * @return + */ + public String getVersionString() { + + StringBuilder builder = new StringBuilder(); + builder.append(ArtifactVersion.from(this).toShortString()); + + if (!iteration.isServiceIteration()) { + builder.append(" ").append(iteration.getName()); + } + + return builder.toString(); + } + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return String.format("%s %s", module.getProject().getFullName(), getVersionString()); + } +} diff --git a/src/main/java/org/springframework/data/release/model/Phase.java b/src/main/java/org/springframework/data/release/model/Phase.java new file mode 100644 index 0000000..e35b9fa --- /dev/null +++ b/src/main/java/org/springframework/data/release/model/Phase.java @@ -0,0 +1,24 @@ +/* + * 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 enum Phase { + + PREPARE, CLEANUP; +} diff --git a/src/main/java/org/springframework/data/release/model/Project.java b/src/main/java/org/springframework/data/release/model/Project.java new file mode 100644 index 0000000..9df3281 --- /dev/null +++ b/src/main/java/org/springframework/data/release/model/Project.java @@ -0,0 +1,80 @@ +/* + * 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.Getter; +import lombok.ToString; + +import java.util.Collections; +import java.util.List; + +import org.springframework.util.Assert; + +/** + * @author Oliver Gierke + */ +@ToString +@EqualsAndHashCode +public class Project { + + private final @Getter ProjectKey key; + private final @Getter String name; + private final @Getter List dependencies; + private final Tracker tracker; + private final @Getter List additionalArtifacts; + + Project(String key, String name, List dependencies) { + this(key, name, Tracker.JIRA, dependencies, Collections.emptyList()); + } + + Project(String key, String name, List dependencies, List additionalArtifacts) { + this(key, name, Tracker.JIRA, dependencies, additionalArtifacts); + } + + Project(String key, String name, Tracker tracker, List dependencies, List additionalArtifacts) { + + this.key = new ProjectKey(key); + this.name = name; + this.dependencies = dependencies; + this.tracker = tracker; + this.additionalArtifacts = additionalArtifacts; + } + + public boolean uses(Tracker tracker) { + return this.tracker.equals(tracker); + } + + public String getFullName() { + return "Spring Data ".concat(name); + } + + public String getDependencyProperty() { + return "springdata.".concat(name.toLowerCase()); + } + + /** + * Returns whether the current project depends on the given one. + * + * @param project must not be {@literal null}. + * @return + */ + public boolean dependsOn(Project project) { + + Assert.notNull(project, "Project must not be null!"); + return dependencies.contains(project); + } +} diff --git a/src/main/java/org/springframework/data/release/model/ProjectKey.java b/src/main/java/org/springframework/data/release/model/ProjectKey.java new file mode 100644 index 0000000..9140b4a --- /dev/null +++ b/src/main/java/org/springframework/data/release/model/ProjectKey.java @@ -0,0 +1,33 @@ +/* + * Copyright 2013 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.NonNull; +import lombok.Value; + +/** + * @author Oliver Gierke + */ +@Value +public class ProjectKey { + + private final @NonNull String key; + + @Override + public String toString() { + return key; + } +} diff --git a/src/main/java/org/springframework/data/release/model/Projects.java b/src/main/java/org/springframework/data/release/model/Projects.java new file mode 100644 index 0000000..264fe3d --- /dev/null +++ b/src/main/java/org/springframework/data/release/model/Projects.java @@ -0,0 +1,52 @@ +/* + * 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 java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * @author Oliver Gierke + */ +public class Projects { + + public static final Project COMMONS, BUILD, REST, JPA, MONGO_DB, NEO4J, SOLR, COUCHBASE, CASSANDRA, ELASTICSEARCH, + REDIS, GEMFIRE; + public static final List PROJECTS; + + static { + + BUILD = new Project("DATABUILD", "Build", Tracker.GITHUB, Collections.emptyList(), Collections.emptyList()); + COMMONS = new Project("DATACMNS", "Commons", Arrays.asList(BUILD)); + JPA = new Project("DATAJPA", "JPA", Arrays.asList(COMMONS)); + MONGO_DB = new Project("DATAMONGO", "MongoDB", Arrays.asList(COMMONS), + Arrays.asList("spring-data-mongodb-cross-store", "spring-data-mongodb-log4j")); + NEO4J = new Project("DATAGRAPH", "Neo4j", Arrays.asList(COMMONS)); + SOLR = new Project("DATASOLR", "Solr", Arrays.asList(COMMONS)); + COUCHBASE = new Project("DATACOUCH", "Couchbase", Arrays.asList(COMMONS)); + CASSANDRA = new Project("DATACASS", "Cassandra", Arrays.asList(COMMONS), Arrays.asList("spring-cql")); + ELASTICSEARCH = new Project("DATAES", "Elasticsearch", Arrays.asList(COMMONS)); + REDIS = new Project("DATAREDIS", "Redis", Collections.emptyList()); + GEMFIRE = new Project("SGF", "Gemfire", Arrays.asList(COMMONS)); + + REST = new Project("DATAREST", "REST", Arrays.asList(COMMONS, JPA, MONGO_DB, NEO4J, GEMFIRE, SOLR, CASSANDRA), + Arrays.asList("spring-data-rest-core", "spring-data-rest-core")); + + PROJECTS = Arrays.asList(BUILD, COMMONS, JPA, MONGO_DB, NEO4J, SOLR, COUCHBASE, CASSANDRA, ELASTICSEARCH, REDIS, + GEMFIRE, REST); + } +} diff --git a/src/main/java/org/springframework/data/release/model/Release.java b/src/main/java/org/springframework/data/release/model/Release.java new file mode 100644 index 0000000..a6bc81b --- /dev/null +++ b/src/main/java/org/springframework/data/release/model/Release.java @@ -0,0 +1,33 @@ +/* + * Copyright 2013 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; + +import org.joda.time.LocalDate; + +/** + * @author Oliver Gierke + */ +@Value +public class Release { + + private final String id; + private final ProjectKey projectKey; + private final String name; + private final String description; + private final LocalDate date; +} diff --git a/src/main/java/org/springframework/data/release/model/ReleaseTrains.java b/src/main/java/org/springframework/data/release/model/ReleaseTrains.java new file mode 100644 index 0000000..49611ef --- /dev/null +++ b/src/main/java/org/springframework/data/release/model/ReleaseTrains.java @@ -0,0 +1,107 @@ +/* + * 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 static org.springframework.data.release.model.Projects.*; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * @author Oliver Gierke + */ +public class ReleaseTrains { + + public static final List TRAINS; + public static final Train CODD, DIJKSTRA, EVANS, FOWLER; + + static { + + CODD = codd(); + DIJKSTRA = dijkstra(); + EVANS = DIJKSTRA.next("Evans", Transition.MINOR); + FOWLER = EVANS.next("Fowler", Transition.MINOR); + + // Trains + + TRAINS = Arrays.asList(CODD, DIJKSTRA, EVANS, FOWLER); + + // Train names + + List names = new ArrayList<>(TRAINS.size()); + + for (Train train : TRAINS) { + names.add(train.getName()); + } + } + + 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.0"); + + 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"); + Module neo4j = new Module(NEO4J, "3.1"); + Module solr = new Module(SOLR, "1.2"); + Module couchbase = new Module(COUCHBASE, "1.1"); + Module cassandra = new Module(CASSANDRA, "1.0"); + Module elasticsearch = new Module(ELASTICSEARCH, "1.0", "M2"); + Module gemfire = new Module(GEMFIRE, "1.4"); + Module redis = new Module(REDIS, "1.3"); + + Module rest = new Module(REST, "2.1"); + + return new Train("Dijkstra", build, commons, jpa, mongoDb, neo4j, solr, couchbase, cassandra, elasticsearch, + gemfire, redis, rest); + } + + public static Train getTrainByName(String name) { + + for (Train train : TRAINS) { + if (train.getName().equalsIgnoreCase(name)) { + return train; + } + } + + return null; + } + + public static Project getProjectByName(String name) { + + for (Project project : PROJECTS) { + if (project.getName().equalsIgnoreCase(name)) { + return project; + } + } + + return null; + } +} diff --git a/src/main/java/org/springframework/data/release/model/Tracker.java b/src/main/java/org/springframework/data/release/model/Tracker.java new file mode 100644 index 0000000..4383523 --- /dev/null +++ b/src/main/java/org/springframework/data/release/model/Tracker.java @@ -0,0 +1,24 @@ +/* + * 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 enum Tracker { + + JIRA, GITHUB; +} diff --git a/src/main/java/org/springframework/data/release/model/Train.java b/src/main/java/org/springframework/data/release/model/Train.java new file mode 100644 index 0000000..ab42e45 --- /dev/null +++ b/src/main/java/org/springframework/data/release/model/Train.java @@ -0,0 +1,218 @@ +/* + * 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 static org.springframework.data.release.model.Iteration.*; + +import lombok.EqualsAndHashCode; +import lombok.ToString; +import lombok.Value; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import org.springframework.shell.support.util.OsUtils; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * @author Oliver Gierke + */ +@Value +public class Train implements Iterable { + + private final String name;; + private final List modules; + private final Iterations iterations; + + public Train(String name, Module... modules) { + + this.name = name; + this.modules = Arrays.asList(modules); + this.iterations = Iterations.DEFAULT; + } + + public Train(String name, List modules) { + + this.name = name; + this.modules = Collections.unmodifiableList(modules); + this.iterations = Iterations.DEFAULT; + } + + /* + * (non-Javadoc) + * @see java.lang.Iterable#iterator() + */ + @Override + public Iterator iterator() { + return modules.iterator(); + } + + public Module getModule(String name) { + + for (Module module : this) { + if (module.getProject().getName().equals(name)) { + return module; + } + } + + return null; + } + + public Module getModule(Project project) { + + for (Module module : this) { + if (module.getProject().equals(project)) { + return module; + } + } + + return null; + } + + public Train next(String name, Transition transition) { + + List nextModules = new ArrayList<>(); + + for (Module module : modules) { + nextModules.add(module.next(transition)); + } + + return new Train(name, nextModules); + } + + public ModuleIteration getModuleIteration(Iteration iteration, String moduleName) { + + for (Module module : this) { + if (module.hasName(moduleName)) { + return new ModuleIteration(module, iteration, this); + } + } + + return null; + } + + public Iterable getModuleIterations(Iteration iteration) { + return getModuleIterations(iteration, new Project[0]); + } + + List 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)); + } + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + + StringBuilder builder = new StringBuilder(); + + builder.append(name).append(OsUtils.LINE_SEPARATOR).append(OsUtils.LINE_SEPARATOR); + builder.append(StringUtils.collectionToDelimitedString(modules, OsUtils.LINE_SEPARATOR)); + + return builder.toString(); + } + + /** + * Value object to represent a set of {@link Iteration}s. + * + * @author Oliver Gierke + */ + @EqualsAndHashCode + @ToString + public static class Iterations implements Iterable { + + public static Iterations DEFAULT = new Iterations(M1, RC1, GA, SR1, SR2, SR3, SR4, SR5, SR6); + + private final List iterations; + + /** + * Creates a new {@link Iterations} from the given {@link Iteration}. + * + * @param iterations + */ + Iterations(Iteration... iterations) { + this.iterations = Arrays.asList(iterations); + } + + /** + * Returns the iteration with the given name. + * + * @param name must not be {@literal null} or empty. + * @return + */ + Iteration getIterationByName(String name) { + + Assert.hasText(name, "Name must not be null or empty!"); + + for (Iteration iteration : this) { + if (iteration.getName().equalsIgnoreCase(name)) { + return iteration; + } + } + + return null; + } + + Iteration getPreviousIteration(Iteration iteration) { + + for (Iteration candidate : iterations) { + if (candidate.isNext(iteration)) { + return candidate; + } + } + + throw new IllegalArgumentException(String.format("Could not find previous iteration for %s!", iteration)); + } + + /* + * (non-Javadoc) + * @see java.lang.Iterable#iterator() + */ + @Override + public Iterator iterator() { + return iterations.iterator(); + } + } +} diff --git a/src/main/java/org/springframework/data/release/model/TrainIteration.java b/src/main/java/org/springframework/data/release/model/TrainIteration.java new file mode 100644 index 0000000..7c5ae52 --- /dev/null +++ b/src/main/java/org/springframework/data/release/model/TrainIteration.java @@ -0,0 +1,70 @@ +/* + * 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; + +import java.util.Iterator; + +/** + * @author Oliver Gierke + */ +@Value +public class TrainIteration implements Iterable { + + private final Train train; + private final Iteration iteration; + + /* + * (non-Javadoc) + * @see java.lang.Iterable#iterator() + */ + @Override + public Iterator iterator() { + return train.getModuleIterations(iteration).iterator(); + } + + public ArtifactVersion getModuleVersion(Project project) { + return train.getModuleVersion(project, iteration); + } + + public ModuleIteration getModule(String name) { + return train.getModuleIteration(iteration, name); + } + + public ModuleIteration getModule(Project project) { + return train.getModuleIteration(iteration, project.getName()); + } + + public Iterable getModulesExcept(Project... exclusions) { + return train.getModuleIterations(iteration, exclusions); + } + + public ModuleIteration getPreviousIteration(ModuleIteration module) { + + Iteration previousIteration = train.getIterations().getPreviousIteration(iteration); + return train.getModuleIteration(previousIteration, module.getProject().getName()); + } + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return String.format("%s %s", train.getName(), iteration.getName()); + } +} diff --git a/src/main/java/org/springframework/data/release/model/Transition.java b/src/main/java/org/springframework/data/release/model/Transition.java new file mode 100644 index 0000000..d5266af --- /dev/null +++ b/src/main/java/org/springframework/data/release/model/Transition.java @@ -0,0 +1,24 @@ +/* + * 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 enum Transition { + + MAJOR, MINOR; +} diff --git a/src/main/java/org/springframework/data/release/model/Version.java b/src/main/java/org/springframework/data/release/model/Version.java new file mode 100644 index 0000000..f62d073 --- /dev/null +++ b/src/main/java/org/springframework/data/release/model/Version.java @@ -0,0 +1,218 @@ +package org.springframework.data.release.model; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * Value object to represent a Version consisting of major, minor and bugfix part. + * + * @author Oliver Gierke + */ +public class Version implements Comparable { + + private final int major; + private final int minor; + private final int bugfix; + private final int build; + + /** + * Creates a new {@link Version} from the given integer values. At least one value has to be given but a maximum of 4. + * + * @param parts must not be {@literal null} or empty. + */ + public Version(int... parts) { + + Assert.notNull(parts); + Assert.isTrue(parts.length > 0 && parts.length < 5); + + this.major = parts[0]; + this.minor = parts.length > 1 ? parts[1] : 0; + this.bugfix = parts.length > 2 ? parts[2] : 0; + this.build = parts.length > 3 ? parts[3] : 0; + + Assert.isTrue(major >= 0, "Major version must be greater or equal zero!"); + Assert.isTrue(minor >= 0, "Minor version must be greater or equal zero!"); + Assert.isTrue(bugfix >= 0, "Bugfix version must be greater or equal zero!"); + Assert.isTrue(build >= 0, "Build version must be greater or equal zero!"); + } + + /** + * Parses the given string representation of a version into a {@link Version} object. + * + * @param version must not be {@literal null} or empty. + * @return + */ + public static Version parse(String version) { + + Assert.hasText(version); + + String[] parts = version.trim().split("\\."); + int[] intParts = new int[parts.length]; + + for (int i = 0; i < parts.length; i++) { + intParts[i] = Integer.parseInt(parts[i]); + } + + return new Version(intParts); + } + + /** + * Returns whether the current {@link Version} is greater (newer) than the given one. + * + * @param version + * @return + */ + public boolean isGreaterThan(Version version) { + return compareTo(version) > 0; + } + + /** + * Returns whether the current {@link Version} is greater (newer) or the same as the given one. + * + * @param version + * @return + */ + public boolean isGreaterThanOrEqualTo(Version version) { + return compareTo(version) >= 0; + } + + /** + * Returns whether the current {@link Version} is the same as the given one. + * + * @param version + * @return + */ + public boolean is(Version version) { + return equals(version); + } + + /** + * Returns whether the current {@link Version} is less (older) than the given one. + * + * @param version + * @return + */ + public boolean isLessThan(Version version) { + return compareTo(version) < 0; + } + + /** + * Returns whether the current {@link Version} is less (older) or equal to the current one. + * + * @param version + * @return + */ + public boolean isLessThanOrEqualTo(Version version) { + return compareTo(version) <= 0; + } + + public Version nextMajor() { + return new Version(this.major + 1); + } + + public Version nextMinor() { + 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); + } + + /* + * (non-Javadoc) + * @see java.lang.Comparable#compareTo(java.lang.Object) + */ + public int compareTo(Version that) { + + if (that == null) { + return 1; + } + + if (major != that.major) { + return major - that.major; + } + + if (minor != that.minor) { + return minor - that.minor; + } + + if (bugfix != that.bugfix) { + return bugfix - that.bugfix; + } + + if (build != that.build) { + return build - that.build; + } + + return 0; + } + + /* + * (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + + if (this == obj) { + return true; + } + + if (!(obj instanceof Version)) { + return false; + } + + Version that = (Version) obj; + + return this.major == that.major && this.minor == that.minor && this.bugfix == that.bugfix + && this.build == that.build; + } + + /* + * (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + + int result = 17; + result += 31 * major; + result += 31 * minor; + result += 31 * bugfix; + result += 31 * build; + return result; + } + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + + List digits = new ArrayList(); + digits.add(major); + digits.add(minor); + + if (build != 0 || bugfix != 0) { + digits.add(bugfix); + } + + if (build != 0) { + digits.add(build); + } + + return StringUtils.collectionToDelimitedString(digits, "."); + } +} diff --git a/src/main/java/org/springframework/data/release/utils/CommandUtils.java b/src/main/java/org/springframework/data/release/utils/CommandUtils.java new file mode 100644 index 0000000..2aae3b0 --- /dev/null +++ b/src/main/java/org/springframework/data/release/utils/CommandUtils.java @@ -0,0 +1,57 @@ +/* + * Copyright 2015 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.utils; + +import java.util.concurrent.Future; + +import org.springframework.data.release.io.CommandResult; + +/** + * @author Oliver Gierke + */ +public class CommandUtils { + + public static CommandResult getCommandResult(Future future) throws Exception { + + CommandResult result = future.get(); + + if (result.hasError()) { + throw new CommandException(result); + } + + return result; + } + + public static class CommandException extends RuntimeException { + + private final CommandResult result; + + public CommandException(CommandResult result) { + + super(result.getException()); + this.result = result; + } + + /* + * (non-Javadoc) + * @see java.lang.Throwable#getMessage() + */ + @Override + public String getMessage() { + return String.format("Command execution failed: %s.", result); + } + } +} diff --git a/src/main/java/org/springframework/data/release/utils/Logger.java b/src/main/java/org/springframework/data/release/utils/Logger.java new file mode 100644 index 0000000..94c854c --- /dev/null +++ b/src/main/java/org/springframework/data/release/utils/Logger.java @@ -0,0 +1,49 @@ +/* + * 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.utils; + +import org.springframework.data.release.model.ModuleIteration; +import org.springframework.data.release.model.Project; +import org.springframework.data.release.model.TrainIteration; +import org.springframework.shell.support.logging.HandlerUtils; +import org.springframework.stereotype.Component; + +/** + * @author Oliver Gierke + */ +@Component +public class Logger { + + private static final String PREFIX_TEMPLATE = "%s > %s"; + + private final java.util.logging.Logger LOGGER = HandlerUtils.getLogger(getClass()); + + public void log(ModuleIteration module, String template, Object... args) { + log(module.getProject(), template, args); + } + + public void log(Project project, String template, Object... args) { + log(project.getName(), template, args); + } + + public void log(TrainIteration iteration, String template, Object... args) { + log(iteration.toString(), template, args); + } + + private void log(String context, String template, Object... args) { + LOGGER.info(String.format(PREFIX_TEMPLATE, context, String.format(template, args))); + } +} diff --git a/src/main/resources/META-INF/spring/spring-shell-plugin.xml b/src/main/resources/META-INF/spring/spring-shell-plugin.xml new file mode 100644 index 0000000..c61e0b2 --- /dev/null +++ b/src/main/resources/META-INF/spring/spring-shell-plugin.xml @@ -0,0 +1,10 @@ + + + + + + diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml new file mode 100644 index 0000000..f1b93eb --- /dev/null +++ b/src/main/resources/logback.xml @@ -0,0 +1,16 @@ + + + + + + %d %5p %40.40c:%4L - %m%n + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/org/springframework/data/release/cli/banner.txt b/src/main/resources/org/springframework/data/release/cli/banner.txt new file mode 100644 index 0000000..80590fc --- /dev/null +++ b/src/main/resources/org/springframework/data/release/cli/banner.txt @@ -0,0 +1,8 @@ + _____ _ _____ _ _____ _ _____ _ _ _ + / ____| (_) | __ \ | | | __ \ | | / ____| | | | | + | (___ _ __ _ __ _ _ __ __ _ | | | | __ _| |_ __ _ | |__) |___| | ___ __ _ ___ ___ | (___ | |__ ___| | | + \___ \| '_ \| '__| | '_ \ / _` | | | | |/ _` | __/ _` | | _ // _ \ |/ _ \/ _` / __|/ _ \ \___ \| '_ \ / _ \ | | + ____) | |_) | | | | | | | (_| | | |__| | (_| | || (_| | | | \ \ __/ | __/ (_| \__ \ __/ ____) | | | | __/ | | + |_____/| .__/|_| |_|_| |_|\__, | |_____/ \__,_|\__\__,_| |_| \_\___|_|\___|\__,_|___/\___| |_____/|_| |_|\___|_|_| + | | __/ | + |_| |___/ diff --git a/src/test/java/org/springframework/data/release/AbstractIntegrationTests.java b/src/test/java/org/springframework/data/release/AbstractIntegrationTests.java new file mode 100644 index 0000000..0268b99 --- /dev/null +++ b/src/test/java/org/springframework/data/release/AbstractIntegrationTests.java @@ -0,0 +1,29 @@ +/* + * 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; + +import org.junit.runner.RunWith; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * @author Oliver Gierke + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = TestConfig.class) +public abstract class AbstractIntegrationTests { + +} diff --git a/src/test/java/org/springframework/data/release/ArtifactUnitTests.java b/src/test/java/org/springframework/data/release/ArtifactUnitTests.java new file mode 100644 index 0000000..32af713 --- /dev/null +++ b/src/test/java/org/springframework/data/release/ArtifactUnitTests.java @@ -0,0 +1,41 @@ +/* + * 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; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +import org.junit.Test; +import org.springframework.data.release.maven.Artifact; +import org.springframework.data.release.model.ArtifactVersion; +import org.springframework.data.release.model.Iteration; +import org.springframework.data.release.model.ReleaseTrains; + +/** + * @author Oliver Gierke + */ +public class ArtifactUnitTests { + + @Test + public void testname() { + + Artifact artifact = new Artifact(ReleaseTrains.DIJKSTRA.getModuleIteration(Iteration.M1, "JPA")); + + assertThat(artifact.getArtifactId(), is("spring-data-jpa")); + assertThat(artifact.getVersion(), is(ArtifactVersion.parse("1.6.0.M1"))); + assertThat(artifact.getNextDevelopmentVersion(), is(ArtifactVersion.parse("1.6.0.BUILD-SNAPSHOT"))); + } +} diff --git a/src/test/java/org/springframework/data/release/TestConfig.java b/src/test/java/org/springframework/data/release/TestConfig.java new file mode 100644 index 0000000..4ad5fa5 --- /dev/null +++ b/src/test/java/org/springframework/data/release/TestConfig.java @@ -0,0 +1,28 @@ +/* + * 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; + +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +/** + * @author Oliver Gierke + */ +@Configuration +@ComponentScan +public class TestConfig { + +} diff --git a/src/test/java/org/springframework/data/release/cli/ReleaseCommandsIntegrationTests.java b/src/test/java/org/springframework/data/release/cli/ReleaseCommandsIntegrationTests.java new file mode 100644 index 0000000..dc41076 --- /dev/null +++ b/src/test/java/org/springframework/data/release/cli/ReleaseCommandsIntegrationTests.java @@ -0,0 +1,42 @@ +/* + * Copyright 2014-2015 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 static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +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 + */ +public class ReleaseCommandsIntegrationTests extends AbstractIntegrationTests { + + @Autowired ReleaseCommands releaseCommands; + @Autowired GitOperations git; + + @Test + public void predictsReleaseTrainCorrectly() 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 new file mode 100644 index 0000000..e87726b --- /dev/null +++ b/src/test/java/org/springframework/data/release/git/GitOperationsIntegrationTests.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.git; + +import static org.springframework.data.release.model.Projects.*; + +import org.junit.Ignore; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.release.AbstractIntegrationTests; +import org.springframework.data.release.model.ReleaseTrains; +import org.springframework.util.StringUtils; + +/** + * @author Oliver Gierke + */ +public class GitOperationsIntegrationTests extends AbstractIntegrationTests { + + @Autowired GitOperations gitOperations; + + @Test + @Ignore + public void testname() throws Exception { + gitOperations.update(ReleaseTrains.CODD); + } + + @Test + @Ignore + public void showTags() throws Exception { + + Tags tags = gitOperations.getTags(COMMONS); + System.out.println(StringUtils.collectionToDelimitedString(tags.asList(), "\n")); + } + + @Test + public void foo() throws Exception { + + gitOperations.update(ReleaseTrains.EVANS); + } +} diff --git a/src/test/java/org/springframework/data/release/git/GitProjectUnitTests.java b/src/test/java/org/springframework/data/release/git/GitProjectUnitTests.java new file mode 100644 index 0000000..d9abd50 --- /dev/null +++ b/src/test/java/org/springframework/data/release/git/GitProjectUnitTests.java @@ -0,0 +1,38 @@ +/* + * 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.ReleaseTrains; +import org.springframework.data.release.model.Train; + +/** + * @author Oliver Gierke + */ +public class GitProjectUnitTests { + + @Test + public void testname() { + + Train codd = ReleaseTrains.CODD; + GitProject project = new GitProject(codd.getModule("Commons").getProject(), new GitServer()); + + assertThat(project.getProjectUri(), is("https://www.github.com/spring-projects/spring-data-commons")); + } +} diff --git a/src/test/java/org/springframework/data/release/io/CommonsExecOsCommandOperationsIntegegrationTests.java b/src/test/java/org/springframework/data/release/io/CommonsExecOsCommandOperationsIntegegrationTests.java new file mode 100644 index 0000000..94e2b40 --- /dev/null +++ b/src/test/java/org/springframework/data/release/io/CommonsExecOsCommandOperationsIntegegrationTests.java @@ -0,0 +1,58 @@ +/* + * Copyright 2015 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 static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.release.AbstractIntegrationTests; +import org.springframework.data.release.model.Projects; + +/** + * @author Oliver Gierke + */ +public class CommonsExecOsCommandOperationsIntegegrationTests extends AbstractIntegrationTests { + + @Autowired OsCommandOperations operations; + + @Test + public void testname() throws Exception { + + // CommandResult result = operations + // .executeCommand("export GIT_TRACE=1 && git clone https://github.com/spring-projects/spring-data-build").get(); + + // CommandResult result = operations.executeCommand("git pull", Projects.BUILD).get(); + + CommandResult result = operations.executeCommand("git remote -v", Projects.BUILD).get(); + // .get(); + + if (result.hasError()) { + + System.out.println(result.getStatus()); + System.out.println(result.getOutput()); + System.out.println(result.getException().getMessage()); + + throw result.getException(); + } else { + System.out.println(result.getOutput()); + } + + assertThat(result.hasError(), is(false)); + + } +} diff --git a/src/test/java/org/springframework/data/release/jira/JiraVersionUnitTests.java b/src/test/java/org/springframework/data/release/jira/JiraVersionUnitTests.java new file mode 100644 index 0000000..71d236b --- /dev/null +++ b/src/test/java/org/springframework/data/release/jira/JiraVersionUnitTests.java @@ -0,0 +1,71 @@ +/* + * 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 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.ModuleIteration; +import org.springframework.data.release.model.ReleaseTrains; + +/** + * Unit tests for {@link JiraVersion}. + * + * @author Oliver Gierke + */ +public class JiraVersionUnitTests { + + @Test + public void rendersJiraGaVersionCorrectly() { + + assertIterationVersion(Iteration.M1, "1.8 M1 (Dijkstra)"); + assertIterationVersion(Iteration.RC1, "1.8 RC1 (Dijkstra)"); + assertIterationVersion(Iteration.GA, "1.8 GA (Dijkstra)"); + + assertIterationVersion(Iteration.SR1, "1.8.1 (Dijkstra SR1)"); + assertIterationVersion(Iteration.SR2, "1.8.2 (Dijkstra SR2)"); + assertIterationVersion(Iteration.SR3, "1.8.3 (Dijkstra SR3)"); + assertIterationVersion(Iteration.SR4, "1.8.4 (Dijkstra SR4)"); + } + + @Test + public void usesCustomModuleIterationStartVersion() { + + ModuleIteration module = ReleaseTrains.DIJKSTRA.getModuleIteration(Iteration.M1, "Elasticsearch"); + + JiraVersion version = new JiraVersion(module); + assertThat(version.toString(), is("1.0 M2 (Dijkstra)")); + } + + @Test + public void doesNotUseCustomIterationOnNonFirstiterations() { + + ModuleIteration module = ReleaseTrains.DIJKSTRA.getModuleIteration(Iteration.RC1, "Elasticsearch"); + + JiraVersion version = new JiraVersion(module); + assertThat(version.toString(), is("1.0 RC1 (Dijkstra)")); + } + + private void assertIterationVersion(Iteration iteration, String expected) { + + ModuleIteration module = ReleaseTrains.DIJKSTRA.getModuleIteration(iteration, "Commons"); + + JiraVersion version = new JiraVersion(module); + assertThat(version.toString(), is(expected)); + } +} diff --git a/src/test/java/org/springframework/data/release/maven/MavenIntegrationTests.java b/src/test/java/org/springframework/data/release/maven/MavenIntegrationTests.java new file mode 100644 index 0000000..98df45e --- /dev/null +++ b/src/test/java/org/springframework/data/release/maven/MavenIntegrationTests.java @@ -0,0 +1,64 @@ +/* + * Copyright 2014-2015 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 java.io.IOException; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.ClassPathResource; +import org.springframework.data.release.AbstractIntegrationTests; +import org.springframework.data.release.io.Workspace; +import org.springframework.data.release.model.ArtifactVersion; +import org.xmlbeam.ProjectionFactory; +import org.xmlbeam.io.XBFileIO; + +/** + * @author Oliver Gierke + */ +public class MavenIntegrationTests extends AbstractIntegrationTests { + + @Autowired Workspace workspace; + @Autowired ProjectionFactory projection; + + public @Rule TemporaryFolder folder = new TemporaryFolder(); + + @Test + public void modifiesParentPomCorrectly() throws IOException { + + XBFileIO io = projection.io().file(new ClassPathResource("parent-pom.xml").getFile()); + + ParentPom pom = io.read(ParentPom.class); + pom.setSharedResourcesVersion(ArtifactVersion.parse("1.2.0.RELEASE")); + + // System.out.println(projection.asString(pom)); + } + + @Test + public void updatesRepositoriesCorrectly() throws Exception { + + XBFileIO io = projection.io().file(new ClassPathResource("sample-pom.xml").getFile()); + + Pom pom = io.read(Pom.class); + + pom.setRepositoryId("spring-libs-snapshot", "spring-libs-release"); + pom.setRepositoryUrl("spring-libs-release", "https://repo.spring.io/libs-release"); + + // System.out.println(projection.asString(pom)); + } +} diff --git a/src/test/java/org/springframework/data/release/model/ArtifactVersionUnitTests.java b/src/test/java/org/springframework/data/release/model/ArtifactVersionUnitTests.java new file mode 100644 index 0000000..f910cea --- /dev/null +++ b/src/test/java/org/springframework/data/release/model/ArtifactVersionUnitTests.java @@ -0,0 +1,93 @@ +/* + * 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 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.Version; + +/** + * @author Oliver Gierke + */ +public class ArtifactVersionUnitTests { + + @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/IterationUnitTests.java b/src/test/java/org/springframework/data/release/model/IterationUnitTests.java new file mode 100644 index 0000000..5277b87 --- /dev/null +++ b/src/test/java/org/springframework/data/release/model/IterationUnitTests.java @@ -0,0 +1,24 @@ +/* + * 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 class IterationUnitTests { + +} diff --git a/src/test/java/org/springframework/data/release/model/ModuleIterationUnitTests.java b/src/test/java/org/springframework/data/release/model/ModuleIterationUnitTests.java new file mode 100644 index 0000000..a6f1b7b --- /dev/null +++ b/src/test/java/org/springframework/data/release/model/ModuleIterationUnitTests.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.model; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +import org.junit.Test; + +/** + * @author Oliver Gierke + */ +public class ModuleIterationUnitTests { + + @Test + public void abbreviatesTrailingZerosForNonServiceReleases() { + + TrainIteration iteration = new TrainIteration(ReleaseTrains.DIJKSTRA, Iteration.M1); + ModuleIteration module = iteration.getModule(Projects.JPA); + + assertThat(module.getVersionString(), is("1.6 M1")); + } + + @Test + public void doesNotListIterationSuffixForServiceReleases() { + + TrainIteration iteration = new TrainIteration(ReleaseTrains.DIJKSTRA, Iteration.SR1); + ModuleIteration module = iteration.getModule(Projects.JPA); + + assertThat(module.getVersionString(), is("1.6.1")); + } +} 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; +} diff --git a/src/test/resources/parent-pom.xml b/src/test/resources/parent-pom.xml new file mode 100644 index 0000000..ee13def --- /dev/null +++ b/src/test/resources/parent-pom.xml @@ -0,0 +1,745 @@ + + + + + + 4.0.0 + + spring-data-parent + pom + + + org.springframework.data.build + spring-data-build + 1.6.2.BUILD-SNAPSHOT + ../pom.xml + + + Spring Data Build - General parent module + Global parent pom.xml to be used by Spring Data modules + http://www.spring.io/spring-data + 2011-2015 + + + Pivotal Software, Inc. + http://www.spring.io + + + + + ogierke + Oliver Gierke + ogierke at pivotal.io + Pivotal Software, Inc. + http://www.spring.io + + Project lead + + +1 + + + + + + Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0 + + Copyright 2008-2013 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. + + + + + + + true + true + UTF-8 + ${basedir} + 1.6 + ${project.artifactId} + + 1.1.3 + 1.8.5 + 1.0 + 1.3 + 2.5.1 + 2.7 + 4.12 + 1.1.2 + 1.10.19 + 3.6.3 + 1.7.10 + 4.0.9.RELEASE + 1.2 + 1.2.7 + + Fowler-BUILD-SNAPSHOT + + + + + + + + + + ci + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + + package-javadoc + + jar + + package + + + + + + + + + + + + + + distribute + + + ${project.build.directory}/shared-resources + true + true + + + + + org.springframework.data.build + spring-data-build-resources + 1.6.2.BUILD-SNAPSHOT + provided + zip + + + + + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + unpack-shared-resources + + unpack-dependencies + + generate-resources + + + + ${project.groupId} + spring-data-build-resources + zip + true + ${shared.resources} + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + + aggregate-javadoc + + aggregate + + package + + + + + + + + maven-antrun-plugin + 1.7 + + + + copy-documentation-resources + generate-resources + + + + + + + + + + + + + + + + + + + + run + + + + + collect-schema-files + process-resources + + + + + + + + + + + + run + + + + + rename-reference-docs + process-resources + + + + + + + + run + + + + + + + + + + + + + + + + org.asciidoctor + asciidoctor-maven-plugin + 1.5.2 + + + org.asciidoctor + asciidoctorj-pdf + 1.5.0-alpha.6 + + + org.asciidoctor + asciidoctorj-epub3 + 1.5.0-alpha.4 + + + + + + html + generate-resources + + process-asciidoc + + + html5 + ${project.root}/target/site/reference/html + false + prettify + + true + font + true + spring.css + + + + + + epub + generate-resources + + process-asciidoc + + + epub3 + coderay + + + + + pdf + generate-resources + + process-asciidoc + + + pdf + coderay + + + + + + + ${project.root}/src/main/asciidoc + index.adoc + book + + ${project.version} + ${project.name} + ${project.version} + ${aspectj} + ${querydsl} + ${spring} + ${releasetrain} + true + 3 + true + + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + 2.4 + + + static + + single + + package + + + ${shared.resources}/assemblies/static-resources.xml + + static-resources + false + + + + + + + org.codehaus.mojo + wagon-maven-plugin + 1.0-beta-5 + + ${project.build.directory} + + + + + + + upload-schema + deploy + + upload + + + ${project.root}/target/schemas + *.xsd,.autoschemaln + static-dot-s2 + scp://static.springsource.org + /var/www/domains/springsource.org/www/htdocs/autorepo/schema/${dist.id}/${project.version} + true + + + + + + + upload-static-resources + deploy + + upload + + + ${project.build.directory}/static-resources + ** + static-dot-s2 + scp://static.springsource.org + /var/www/domains/springsource.org/www/htdocs/autorepo/docs/${dist.id}/${project.version} + true + + + + + + + + + + + + + spring41 + + + 4.1.6.RELEASE + + + + + + + spring41-next + + + 4.1.7.BUILD-SNAPSHOT + + + + + spring-libs-snapshot + http://repo.spring.io/libs-snapshot + + + + + + + + spring42-next + + + 4.2.0.BUILD-SNAPSHOT + + + + + spring-libs-snapshot + http://repo.spring.io/libs-snapshot + + + + + + + querydsl-next + + 3.6.2.BUILD-SNAPSHOT + + + + oss-nexus-snapshots + http://oss.sonatype.org/content/repositories/snapshots + + + + + + + + + + org.springframework + spring-framework-bom + ${spring} + pom + import + + + + + + + + + org.hamcrest + hamcrest-library + ${hamcrest} + test + + + + org.hamcrest + hamcrest-core + ${hamcrest} + test + + + + junit + junit + ${junit} + test + + + + org.mockito + mockito-core + ${mockito} + test + + + + org.springframework + spring-test + test + + + + + org.slf4j + slf4j-api + ${slf4j} + + + + org.slf4j + jcl-over-slf4j + ${slf4j} + runtime + + + + ch.qos.logback + logback-classic + ${logback} + test + + + + + + + + + org.apache.maven.wagon + wagon-ssh + 2.5 + + + + + + + + + + + org.codehaus.mojo + wagon-maven-plugin + 1.0-beta-5 + + + + org.asciidoctor + asciidoctor-maven-plugin + 0.1.4 + + + + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + 2.8 + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + ${source.level} + ${source.level} + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.17 + + false + + **/*Tests.java + + + + + + org.apache.maven.plugins + maven-jar-plugin + 2.4 + + true + + + + + maven-source-plugin + 2.2.1 + + + attach-sources + + jar + + + + + + + com.springsource.bundlor + com.springsource.bundlor.maven + 1.0.0.RELEASE + + ${bundlor.enabled} + ${bundlor.failOnWarnings} + + + + bundlor + + bundlor + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.9.1 + + true +
${project.name}
+ ${source.level} + true + ${shared.resources}/javadoc + ${shared.resources}/javadoc/overview.html + ${shared.resources}/javadoc/spring-javadoc.css + + true + -Xdoclint:none + + http://docs.spring.io/spring/docs/3.2.x/javadoc-api/ + http://docs.spring.io/spring-data/data-commons/docs/current/api/ + http://docs.oracle.com/javase/6/docs/api + +
+
+ + + org.apache.maven.plugins + maven-deploy-plugin + 2.8.1 + + true + + + +
+
+ + + + spring-libs-release + https://repo.spring.io/libs-release + + false + + + + + + + spring-plugins-release + https://repo.spring.io/plugins-release + + + +
diff --git a/src/test/resources/sample-pom.xml b/src/test/resources/sample-pom.xml new file mode 100644 index 0000000..d51e1eb --- /dev/null +++ b/src/test/resources/sample-pom.xml @@ -0,0 +1,112 @@ + + 4.0.0 + org.springframework.data.build + spring-data-release-cli + 1.0.0.BUILD-SNAPSHOT + + + org.springframework.boot + spring-boot-starter-parent + 1.0.0.RC4 + + + + org.springframework.shell.Bootstrap + + + + + profile + + + spring-libs-snapshot + http://repo.spring.io/libs-snapshot + + + + + + + + + org.springframework.boot + spring-boot-starter-logging + + + + org.springframework + spring-web + + + + com.fasterxml.jackson.core + jackson-databind + + + + joda-time + joda-time + + + + org.springframework.shell + spring-shell + 1.1.0.BUILD-SNAPSHOT + + + + org.xmlbeam + xmlprojector + 1.1.0 + + + + org.projectlombok + lombok + 1.12.4 + provided + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.7 + 1.7 + + + + + + + + org.codehaus.mojo + appassembler-maven-plugin + 1.7 + + + + org.springframework.shell.Bootstrap + spring-data-release-shell + + + + + + + + + + + spring-libs-snapshot + http://repo.spring.io/libs-snapshot + + + + \ No newline at end of file