commit 092663da0103aba78d928ae830c62dbe2a6b87f4 Author: Oliver Gierke Date: Mon Mar 17 09:05:46 2014 +0100 Initial commit. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6465f46 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +target/ +.settings/ +.project +.classpath +.springBeans +jira.properties +spring-shell.log \ No newline at end of file diff --git a/files/sample-pom-modified.xml b/files/sample-pom-modified.xml new file mode 100644 index 0000000..c72c270 --- /dev/null +++ b/files/sample-pom-modified.xml @@ -0,0 +1,136 @@ + + 4.0.0 + org.springframework.data.build + spring-data-release-cli + 5.0 + + + org.springframework.boot + spring-boot-starter-parent + 1.0.0.RC4 + + + + 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.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 + + + + diff --git a/files/sample-pom.xml b/files/sample-pom.xml new file mode 100644 index 0000000..4156fd6 --- /dev/null +++ b/files/sample-pom.xml @@ -0,0 +1,137 @@ + + 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 + + + + + + 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 diff --git a/infrastructure.properties b/infrastructure.properties new file mode 100644 index 0000000..1f36307 --- /dev/null +++ b/infrastructure.properties @@ -0,0 +1 @@ +io.workDir=~/temp/spring-data-shell \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..14451bb --- /dev/null +++ b/pom.xml @@ -0,0 +1,147 @@ + + 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 + + + + + + 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.apache.commons + commons-exec + 1.2 + + + + org.projectlombok + lombok + 1.12.4 + provided + + + + org.springframework.boot + spring-boot-starter-test + + + + + + + + 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 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/cli/JiraCommands.java b/src/main/java/org/springframework/data/release/cli/JiraCommands.java new file mode 100644 index 0000000..eae1b32 --- /dev/null +++ b/src/main/java/org/springframework/data/release/cli/JiraCommands.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 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.JiraConnector; +import org.springframework.data.release.model.Iteration; +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 JiraCommands implements CommandMarker { + + private final JiraConnector connector; + private final Credentials credentials; + + /** + * @param connector + * @param environment + */ + @Autowired + public JiraCommands(JiraConnector connector, Environment environment) { + + String username = environment.getProperty("jira.username", (String) null); + String password = environment.getProperty("jira.password", (String) null); + + this.connector = connector; + this.credentials = StringUtils.hasText(username) ? new Credentials(username, password) : null; + } + + @CliCommand("jira evict") + public void jiraEvict() { + connector.reset(); + } + + @CliCommand(value = "jira tickets") + public String jira( + // + @CliOption(key = { "", "train" }, mandatory = true, help = "The name of the release train.") String trainName, // + @CliOption(key = "iteration", mandatory = true, help = "An iteration key (one of M1, RC1, GA).") String iterationName, // + @CliOption(key = "for-current-user", specifiedDefaultValue = "true", unspecifiedDefaultValue = "false") boolean forCurrentUser) { + + if (forCurrentUser && credentials == null) { + return "No authentication specified! Use 'jira authenticate' first!"; + } + + Train train = ReleaseTrains.getTrainByName(trainName); + Iteration iteration = train.getIterations().getIterationByName(iterationName); + + return connector.getTicketsFor(train, iteration, forCurrentUser ? credentials : null).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..2344df7 --- /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" }) String trainName) { + + if (StringUtils.hasText(trainName)) { + return ReleaseTrains.getTrainByName(trainName).toString(); + } + + List names = new ArrayList<>(); + + for (Train train : ReleaseTrains.TRAINS) { + names.add(train.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..1e83624 --- /dev/null +++ b/src/main/java/org/springframework/data/release/cli/ReleaseCommands.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.cli; + +import java.io.IOException; + +import lombok.RequiredArgsConstructor; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.release.maven.MavenOperations; +import org.springframework.data.release.maven.Pom; +import org.springframework.data.release.model.Module; +import org.springframework.data.release.model.Project; +import org.springframework.data.release.model.ReleaseTrains; +import org.springframework.data.release.model.Train; +import org.springframework.shell.core.CommandMarker; +import org.springframework.shell.core.annotation.CliCommand; +import org.springframework.stereotype.Component; + +/** + * @author Oliver Gierke + */ +@Component +@RequiredArgsConstructor(onConstructor = @__(@Autowired)) +public class ReleaseCommands implements CommandMarker { + + private final MavenOperations mavenOperations; + + @CliCommand("release predict") + public String predictTrainAndIteration() throws IOException { + + Project commons = ReleaseTrains.COMMONS; + Pom pom = mavenOperations.getMavenProject(commons); + + for (Train train : ReleaseTrains.TRAINS) { + + Module module = train.getModule(commons); + + if (!pom.getVersion().startsWith(module.getVersion().toMajorMinorBugfix())) { + continue; + } + + return train.getName(); + } + + return 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/git/GiCommands.java b/src/main/java/org/springframework/data/release/git/GiCommands.java new file mode 100644 index 0000000..2e8068d --- /dev/null +++ b/src/main/java/org/springframework/data/release/git/GiCommands.java @@ -0,0 +1,54 @@ +/* + * 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.io.IOException; + +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.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 GiCommands implements CommandMarker { + + private final GitOperations gitOperations; + + @CliCommand("git update") + public void checkout(@CliOption(key = { "train" }, mandatory = true) String trainName) throws IOException, + InterruptedException { + + gitOperations.update(ReleaseTrains.getProjectByName(trainName)); + } + + @CliCommand("git tags") + public String tags(@CliOption(key = { "project" }, mandatory = true) String projectName) throws IOException { + + Project project = ReleaseTrains.getProjectByName(projectName); + + return StringUtils.collectionToDelimitedString(gitOperations.getTags(project), "\n"); + } +} diff --git a/src/main/java/org/springframework/data/release/git/GitOperations.java b/src/main/java/org/springframework/data/release/git/GitOperations.java new file mode 100644 index 0000000..130d858 --- /dev/null +++ b/src/main/java/org/springframework/data/release/git/GitOperations.java @@ -0,0 +1,98 @@ +/* + * 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.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Logger; + +import lombok.RequiredArgsConstructor; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.release.io.CommandExecution; +import org.springframework.data.release.io.OsCommandOperations; +import org.springframework.data.release.io.Workspace; +import org.springframework.data.release.model.Module; +import org.springframework.data.release.model.Project; +import org.springframework.data.release.model.Train; +import org.springframework.shell.support.logging.HandlerUtils; +import org.springframework.stereotype.Component; + +/** + * @author Oliver Gierke + */ +@Component +@RequiredArgsConstructor(onConstructor = @__(@Autowired)) +public class GitOperations { + + private static final Logger LOGGER = HandlerUtils.getLogger(GitOperations.class); + + private final GitServer server = new GitServer(); + private final OsCommandOperations osCommandOperations; + private final Workspace workspace; + + public GitProject getGitProject(Project project) { + return new GitProject(project, server); + } + + public void update(Train train) throws IOException, InterruptedException { + + List executions = new ArrayList<>(); + + for (Module module : train) { + executions.add(update(module.getProject())); + } + + for (CommandExecution execution : executions) { + execution.waitForResult(); + } + } + + public CommandExecution update(Project project) throws IOException { + + GitProject gitProject = new GitProject(project, server); + String repositoryName = gitProject.getRepositoryName(); + + if (workspace.hasProjectDirectory(project)) { + + LOGGER.info(String.format("Found existing repository %s. Obtaining latest changes…", repositoryName)); + return osCommandOperations.executeCommand("git pull origin master", project); + + } else { + + File projectDirectory = workspace.getProjectDirectory(project); + + LOGGER.info(String.format("No repository found for project %s. Cloning repository from %s…", repositoryName, + gitProject.getProjectUri())); + return osCommandOperations.executeCommand(String.format("git clone %s %s", gitProject.getProjectUri(), + projectDirectory.getName())); + } + } + + public List getTags(Project project) throws IOException { + + CommandExecution command = osCommandOperations.executeCommand("git tag -l", project); + List tags = new ArrayList<>(); + + for (String line : command.waitAndGetOutput().split("\n")) { + tags.add(new Tag(line)); + } + + return tags; + } +} 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..5a65558 --- /dev/null +++ b/src/main/java/org/springframework/data/release/git/GitProject.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.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; + + public String getRepositoryName() { + return String.format("%s-%s", PROJECT_PREFIX, project.getName().toLowerCase()); + } + + 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..a9ea28b --- /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://www.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..4010eb1 --- /dev/null +++ b/src/main/java/org/springframework/data/release/git/Tag.java @@ -0,0 +1,27 @@ +/* + * 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.Value; + +/** + * @author Oliver Gierke + */ +@Value +public class Tag { + + private final String name; +} diff --git a/src/main/java/org/springframework/data/release/io/CommandExecution.java b/src/main/java/org/springframework/data/release/io/CommandExecution.java new file mode 100644 index 0000000..84d5946 --- /dev/null +++ b/src/main/java/org/springframework/data/release/io/CommandExecution.java @@ -0,0 +1,66 @@ +/* + * Copyright 2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.release.io; + +import java.io.StringWriter; + +import lombok.RequiredArgsConstructor; + +import org.apache.commons.exec.DefaultExecuteResultHandler; +import org.apache.commons.io.IOUtils; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * @author Oliver Gierke + */ +@RequiredArgsConstructor(onConstructor = @__(@Autowired)) +public class CommandExecution { + + private final DefaultExecuteResultHandler resultHandler; + private final StringWriter writer; + + private String output; + + public Exception getException() { + return resultHandler.getException(); + } + + public int getExitValue() { + return resultHandler.getExitValue(); + } + + public String waitAndGetOutput() { + + if (output != null) { + return output; + } + + try { + waitForResult(); + } catch (InterruptedException e) { + throw new IllegalStateException(e); + } + + this.output = writer.toString(); + IOUtils.closeQuietly(writer); + + return output; + } + + public void waitForResult() throws InterruptedException { + resultHandler.waitFor(); + } +} diff --git a/src/main/java/org/springframework/data/release/io/IoConfigAwareOsCommandOperations.java b/src/main/java/org/springframework/data/release/io/IoConfigAwareOsCommandOperations.java new file mode 100644 index 0000000..a53c6f4 --- /dev/null +++ b/src/main/java/org/springframework/data/release/io/IoConfigAwareOsCommandOperations.java @@ -0,0 +1,91 @@ +/* + * Copyright 2011-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.release.io; + +import java.io.File; +import java.io.IOException; +import java.io.StringWriter; + +import lombok.RequiredArgsConstructor; + +import org.apache.commons.exec.CommandLine; +import org.apache.commons.exec.DefaultExecuteResultHandler; +import org.apache.commons.exec.DefaultExecutor; +import org.apache.commons.exec.PumpStreamHandler; +import org.apache.commons.io.output.WriterOutputStream; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.release.model.Project; +import org.springframework.stereotype.Component; + +/** + * Implementation of {@link OsCommandOperations} interface. + * + * @author Stefan Schmidt + * @author Oliver Gierke + * @since 1.2.0 + */ +@Component +@RequiredArgsConstructor(onConstructor = @__(@Autowired)) +class IoConfigAwareOsCommandOperations implements OsCommandOperations { + + private final Workspace workspace; + + /* + * (non-Javadoc) + * @see org.springframework.shell.commands.OsOperations#executeCommand(java.lang.String) + */ + public CommandExecution executeCommand(String command) throws IOException { + return executeCommand(command, (String) null); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.release.io.OsCommandOperations#executeCommand(java.lang.String, org.springframework.data.release.model.Project) + */ + @Override + public CommandExecution executeCommand(String command, Project project) throws IOException { + return executeCommand(command, workspace.getProjectDirectory(project)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.release.io.OsCommandOperations#executeCommand(java.lang.String, java.io.File) + */ + @Override + public CommandExecution executeCommand(String command, String subfolder) throws IOException { + + File workingDirectory = workspace.getWorkingDirectory(); + File executionDirectory = subfolder == null ? workingDirectory : new File(workingDirectory, subfolder); + + return executeCommand(command, executionDirectory); + } + + private CommandExecution executeCommand(String command, File executionDirectory) throws IOException { + + StringWriter writer = new StringWriter(); + WriterOutputStream outputStream = new WriterOutputStream(writer); + + CommandLine commandLine = CommandLine.parse(command); + DefaultExecuteResultHandler executeResultHandler = new DefaultExecuteResultHandler(); + + DefaultExecutor executor = new DefaultExecutor(); + executor.setWorkingDirectory(executionDirectory); + executor.setStreamHandler(new PumpStreamHandler(outputStream, null)); + executor.execute(commandLine, executeResultHandler); + + return new CommandExecution(executeResultHandler, writer); + } +} diff --git a/src/main/java/org/springframework/data/release/io/OsCommandOperations.java b/src/main/java/org/springframework/data/release/io/OsCommandOperations.java new file mode 100644 index 0000000..43fdf69 --- /dev/null +++ b/src/main/java/org/springframework/data/release/io/OsCommandOperations.java @@ -0,0 +1,41 @@ +/* + * 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 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 + */ + CommandExecution executeCommand(String command) throws IOException; + + CommandExecution executeCommand(String command, Project project) throws IOException; + + CommandExecution executeCommand(String command, String subfolder) throws IOException; +} 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..21d79ac --- /dev/null +++ b/src/main/java/org/springframework/data/release/io/Workspace.java @@ -0,0 +1,74 @@ +/* + * Copyright 2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.release.io; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +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; + +/** + * @author Oliver Gierke + */ +@Component +@RequiredArgsConstructor(onConstructor = @__(@Autowired)) +public class Workspace { + + public static final String WORK_DIR_PROPERTY = "io.workDir"; + + private final Environment environment; + + public File getWorkingDirectory() { + + String workDir = environment.getProperty("io.workDir"); + return new File(workDir.replace("~", System.getProperty("user.home"))); + } + + public File getProjectDirectory(Project project) { + return new File(getWorkingDirectory(), project.getName()); + } + + public boolean hasProjectDirectory(Project project) { + return getProjectDirectory(project).exists(); + } + + public File getFile(String name, Project project) { + return new File(new File(getWorkingDirectory(), project.getName()), name); + } + + public boolean exists(String subfolder) { + return new File(getWorkingDirectory(), subfolder).exists(); + } + + @PostConstruct + public void setUp() throws IOException { + + Path path = getWorkingDirectory().toPath(); + + if (!Files.exists(path)) { + Files.createDirectories(path); + } + } +} 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/JiraConfiguration.java b/src/main/java/org/springframework/data/release/jira/JiraConfiguration.java new file mode 100644 index 0000000..d795836 --- /dev/null +++ b/src/main/java/org/springframework/data/release/jira/JiraConfiguration.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.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.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) +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..ceb8146 --- /dev/null +++ b/src/main/java/org/springframework/data/release/jira/JiraConnector.java @@ -0,0 +1,40 @@ +/* + * 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; + +/** + * @author Oliver Gierke + */ +public interface JiraConnector { + + 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(Train train, Iteration iteration, Credentials credentials); + + void verifyBeforeRelease(Train train, Iteration iteration); + + void closeIteration(Train train, Iteration 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..b2fea87 --- /dev/null +++ b/src/main/java/org/springframework/data/release/jira/JiraVersion.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.jira; + +import lombok.Value; + +import org.springframework.data.release.model.Iteration; +import org.springframework.data.release.model.Module; +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; + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + + Iteration iteration = 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..268f8a0 --- /dev/null +++ b/src/main/java/org/springframework/data/release/jira/JqlQuery.java @@ -0,0 +1,63 @@ +/* + * 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 lombok.Value; + +import org.springframework.data.release.model.Iteration; +import org.springframework.data.release.model.Module; +import org.springframework.data.release.model.Train; +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(Train train, Iteration iteration) { + + List parts = new ArrayList<>(); + + for (Module module : train) { + + JiraVersion version = new JiraVersion(module, train, iteration); + parts.add(String.format(PROJECT_VERSION_TEMPLATE, module.getProject().getKey(), 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/RestJiraConnector.java b/src/main/java/org/springframework/data/release/jira/RestJiraConnector.java new file mode 100644 index 0000000..cea4c09 --- /dev/null +++ b/src/main/java/org/springframework/data/release/jira/RestJiraConnector.java @@ -0,0 +1,148 @@ +/* + * 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.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Logger; + +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.data.release.model.Iteration; +import org.springframework.data.release.model.Train; +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; + +/** + * @author Oliver Gierke + */ +@Component +@RequiredArgsConstructor(onConstructor = @_(@Autowired)) +class RestJiraConnector implements JiraConnector { + + protected final Logger LOGGER = Logger.getLogger(getClass().getName()); + + 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; + + /* + * (non-Javadoc) + * @see org.springframework.data.release.jira.JiraConnector#flushTickets() + */ + @Override + @CacheEvict(value = "tickets", allEntries = true) + public void reset() { + + } + + /* + * (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(Train train, Iteration iteration, Credentials credentials) { + + JqlQuery query = JqlQuery.from(train, 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.info(String.format("Retrieving tickets for %s %s (for user %s).", train.getName(), iteration.getName(), + credentials.getUsername())); + } else { + LOGGER.info(String.format("Retrieving tickets for %s %s.", train.getName(), iteration.getName())); + } + + 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.info(String.format("Got tickets %s to %s of %s.", startAt, issues.getNextStartAt(), issues.getTotal())); + + for (JiraIssue issue : issues) { + if (!issue.wasBackportedFrom(train)) { + 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(Train train, Iteration 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(Train train, Iteration 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 + } +} 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..a56a7fc --- /dev/null +++ b/src/main/java/org/springframework/data/release/jira/Tickets.java @@ -0,0 +1,55 @@ +/* + * 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.Value; + +import org.springframework.util.StringUtils; + +/** + * Value object to represent a list of {@link Ticket}s. + * + * @author Oliver Gierke + */ +@Value +public class Tickets implements Iterable { + + private final List tickets; + private final int overallTotal; + + /* + * (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/MavenConfig.java b/src/main/java/org/springframework/data/release/maven/MavenConfig.java new file mode 100644 index 0000000..93a8fef --- /dev/null +++ b/src/main/java/org/springframework/data/release/maven/MavenConfig.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.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 +public class MavenConfig { + + @Bean + public ProjectionFactory projectionFactory() { + + DefaultXMLFactoriesConfig config = new DefaultXMLFactoriesConfig(); + config.setNamespacePhilosophy(NamespacePhilosophy.AGNOSTIC); + return new XBProjector(config, Flags.TO_STRING_RENDERS_XML); + } +} 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..b0c1ff3 --- /dev/null +++ b/src/main/java/org/springframework/data/release/maven/MavenOperations.java @@ -0,0 +1,46 @@ +/* + * 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 java.io.File; +import java.io.IOException; + +import lombok.RequiredArgsConstructor; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.release.io.Workspace; +import org.springframework.data.release.model.Project; +import org.springframework.stereotype.Component; +import org.xmlbeam.ProjectionFactory; + +/** + * @author Oliver Gierke + */ +@Component +@RequiredArgsConstructor(onConstructor = @__(@Autowired)) +public class MavenOperations { + + private static final String POM_XML = "pom.xml"; + + private final Workspace workspace; + private final ProjectionFactory projectionFactory; + + public Pom getMavenProject(Project project) throws IOException { + + File file = workspace.getFile(POM_XML, project); + return projectionFactory.io().file(file).read(Pom.class); + } +} 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..3594aaa --- /dev/null +++ b/src/main/java/org/springframework/data/release/maven/MavenProject.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.maven; + +import lombok.Value; + +import org.springframework.data.release.model.Module; + +/** + * @author Oliver Gierke + */ +@Value +public 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 MavenVersion getVersion() { + return new MavenVersion(module.getVersion()); + } +} diff --git a/src/main/java/org/springframework/data/release/maven/MavenVersion.java b/src/main/java/org/springframework/data/release/maven/MavenVersion.java new file mode 100644 index 0000000..afa0e48 --- /dev/null +++ b/src/main/java/org/springframework/data/release/maven/MavenVersion.java @@ -0,0 +1,37 @@ +/* + * 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.Version; + +/** + * @author Oliver Gierke + */ +@Value +public class MavenVersion { + + private final Version version; + + public String getReleaseVersion() { + return String.format("%s.RELEASE", version.toMajorMinorBugfix()); + } + + public String getSnapshotVersion() { + return String.format("%s.BUILD-SNAPSHOT", version.toMajorMinorBugfix()); + } +} 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..d52894e --- /dev/null +++ b/src/main/java/org/springframework/data/release/maven/Pom.java @@ -0,0 +1,58 @@ +/* + * 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.xmlbeam.annotation.XBRead; +import org.xmlbeam.annotation.XBWrite; + +/** + * @author Oliver Gierke + */ +public interface Pom { + + @XBRead("/project") + Artifact getArtifactId(); + + @XBRead("/project/version") + String getVersion(); + + @XBWrite("/project/version") + void setVersion(String version); + + @XBRead("/project/repositories/repository[id=\"spring-libs-snapshot\"]") + Repository getSpringRepository(); + + 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/model/Iteration.java b/src/main/java/org/springframework/data/release/model/Iteration.java new file mode 100644 index 0000000..f0c0b47 --- /dev/null +++ b/src/main/java/org/springframework/data/release/model/Iteration.java @@ -0,0 +1,54 @@ +/* + * 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; + +/** + * Value object to represent an individual release train iteration. + * + * @author Oliver Gierke + */ +@Value +public class Iteration { + + public static final Iteration SR4 = new Iteration("SR4", null); + 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; + + public boolean isPublicVersion() { + return isServiceIteration() || this.equals(GA); + } + + public boolean isServiceIteration() { + return name.startsWith("SR"); + } + + public int getBugfixValue() { + return name.startsWith("SR") ? Integer.parseInt(name.substring(2)) : 0; + } +} diff --git a/src/main/java/org/springframework/data/release/model/Iterations.java b/src/main/java/org/springframework/data/release/model/Iterations.java new file mode 100644 index 0000000..c74f410 --- /dev/null +++ b/src/main/java/org/springframework/data/release/model/Iterations.java @@ -0,0 +1,76 @@ +/* + * 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 java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +import lombok.Value; + +import org.springframework.util.Assert; + +/** + * Value object to represent a set of {@link Iteration}s. + * + * @author Oliver Gierke + */ +@Value +public class Iterations implements Iterable { + + public static Iterations DEFAULT = new Iterations(M1, RC1, GA, SR1, SR2, SR3, SR4); + + 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 + */ + public 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; + } + + /* + * (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/Module.java b/src/main/java/org/springframework/data/release/model/Module.java new file mode 100644 index 0000000..5f21ce9 --- /dev/null +++ b/src/main/java/org/springframework/data/release/model/Module.java @@ -0,0 +1,60 @@ +/* + * 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; + + public Module(Project project, String version) { + this(project, version, null); + } + + public 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 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..d880f87 --- /dev/null +++ b/src/main/java/org/springframework/data/release/model/ModuleIteration.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.model; + +import lombok.Value; + +/** + * @author Oliver Gierke + */ +@Value +public class ModuleIteration { + + private final Module module; + private final Iteration iteration; + private final Train train; + + public ProjectKey getProjectKey() { + return module.getProject().getKey(); + } + + public String getJiraVersionName() { + + Iteration iteration = module.hasCustomFirstIteration() ? module.getCustomFirstIteration() : this.iteration; + + return String.format("%s %s (%s)", module.getVersion(), iteration.getName(), train.getName()); + } + +} 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..aea9a4c --- /dev/null +++ b/src/main/java/org/springframework/data/release/model/Project.java @@ -0,0 +1,34 @@ +/* + * 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; + +/** + * @author Oliver Gierke + */ +@Value +public class Project { + + private final ProjectKey key; + private final String name; + + public Project(String key, String name) { + + this.key = new ProjectKey(key); + this.name = name; + } +} 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/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..0398781 --- /dev/null +++ b/src/main/java/org/springframework/data/release/model/ReleaseTrains.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.model; + +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; + public static final Project COMMONS; + + private static final Project JPA, MONGO_DB, NEO4J, SOLR, COUCHBASE, CASSANDRA, ELASTICSEARCH, REDIS, GEMFIRE, REST; + private static final List PROJECTS; + + static { + + COMMONS = new Project("DATACMNS", "Commons"); + JPA = new Project("DATAJPA", "JPA"); + MONGO_DB = new Project("DATAMONGO", "MongoDB"); + NEO4J = new Project("DATAGRAPH", "Neo4j"); + SOLR = new Project("DATASOLR", "Solr"); + COUCHBASE = new Project("DATACOUCH", "Couchbase"); + CASSANDRA = new Project("DATACASS", "Cassandra"); + ELASTICSEARCH = new Project("DATAES", "Elasticsearch"); + REDIS = new Project("DATAREDIS", "Redis"); + GEMFIRE = new Project("SGF", "Gemfire"); + + REST = new Project("DATAREST", "REST"); + + PROJECTS = Arrays.asList(COMMONS, JPA, MONGO_DB, NEO4J, SOLR, COUCHBASE, CASSANDRA, ELASTICSEARCH, REDIS, GEMFIRE, + REST); + + CODD = codd(); + DIJKSTRA = dijkstra(); + EVANS = DIJKSTRA.next("Evans", Transition.MINOR); + FOWLER = EVANS.next("Fowler", Transition.MAJOR); + + // 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 commons = new Module(COMMONS, "1.7"); + Module jpa = new Module(JPA, "1.5"); + Module mongoDb = new Module(MONGO_DB, "1.4"); + Module neo4j = new Module(NEO4J, "3.0"); + Module solr = new Module(SOLR, "1.1"); + + Module rest = new Module(REST, "2.1"); + + return new Train("Codd", commons, jpa, mongoDb, neo4j, solr, rest); + } + + private static Train dijkstra() { + + 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", 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/Train.java b/src/main/java/org/springframework/data/release/model/Train.java new file mode 100644 index 0000000..53609df --- /dev/null +++ b/src/main/java/org/springframework/data/release/model/Train.java @@ -0,0 +1,105 @@ +/* + * 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.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import lombok.Value; + +import org.springframework.shell.support.util.OsUtils; +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); + } + + @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(); + } +} 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..689f773 --- /dev/null +++ b/src/main/java/org/springframework/data/release/model/Version.java @@ -0,0 +1,210 @@ +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 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/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/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..a3987c2 --- /dev/null +++ b/src/test/java/org/springframework/data/release/cli/ReleaseCommandsIntegrationTests.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.cli; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +import java.io.IOException; + +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.release.AbstractIntegrationTests; + +/** + * @author Oliver Gierke + */ +public class ReleaseCommandsIntegrationTests extends AbstractIntegrationTests { + + @Autowired ReleaseCommands releaseCommands; + + @Test + public void foo() throws IOException { + assertThat(releaseCommands.predictTrainAndIteration(), is("Dijkstra")); + } +} 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..6cd6b30 --- /dev/null +++ b/src/test/java/org/springframework/data/release/git/GitOperationsIntegrationTests.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 java.io.IOException; +import java.util.List; + +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 + public void testname() throws IOException, InterruptedException { + gitOperations.update(ReleaseTrains.CODD); + } + + @Test + public void showTags() throws IOException { + + List tags = gitOperations.getTags(ReleaseTrains.COMMONS); + System.out.println(StringUtils.collectionToDelimitedString(tags, "\n")); + } +} 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/OsConfigurationIntegrationTests.java b/src/test/java/org/springframework/data/release/io/OsConfigurationIntegrationTests.java new file mode 100644 index 0000000..cc8b87d --- /dev/null +++ b/src/test/java/org/springframework/data/release/io/OsConfigurationIntegrationTests.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.io; + +import java.io.File; + +import org.junit.Ignore; +import org.junit.Test; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +/** + * @author Oliver Gierke + */ +public class OsConfigurationIntegrationTests { + + @Test + @Ignore + public void testname() { + + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(OsConfiguration.class); + File file = new File(context.getEnvironment().getProperty("io.workDir")); + } +} 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..3ee1160 --- /dev/null +++ b/src/test/java/org/springframework/data/release/jira/JiraVersionUnitTests.java @@ -0,0 +1,60 @@ +/* + * 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.Module; +import org.springframework.data.release.model.ReleaseTrains; + +/** + * @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() { + + Module commons = ReleaseTrains.DIJKSTRA.getModule("Elasticsearch"); + + JiraVersion version = new JiraVersion(commons, ReleaseTrains.DIJKSTRA, Iteration.M1); + assertThat(version.toString(), is("1.0 M2 (Dijkstra)")); + } + + private void assertIterationVersion(Iteration iteration, String expected) { + + Module commons = ReleaseTrains.DIJKSTRA.getModule("Commons"); + + JiraVersion version = new JiraVersion(commons, ReleaseTrains.DIJKSTRA, iteration); + assertThat(version.toString(), is(expected)); + } +}