From 45ce12081c5f748513a457880305e28f34a20f9e Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Tue, 20 May 2014 13:50:43 +0200 Subject: [PATCH] Additional functionality for the Git module. Added functionality to reset a release train which actively resets to the latest state of the remote branch. Added functionality to push changes to the remote server and push tags individually. Added functionality to create commits for modules. Release operations now create two commits for the changelog update and the pom modifications. Release conclude does so for the preparation of the next development iteration. Added git.author infrastructure property to make sure we use a real user for the git commits. --- infrastructure.properties | 4 +- .../data/release/cli/ReleaseCommands.java | 6 +- .../data/release/git/Commit.java | 52 +++++++++ .../data/release/git/GitCommands.java | 27 ++++- .../data/release/git/GitOperations.java | 108 +++++++++++++++--- .../data/release/misc/ReleaseOperations.java | 30 +++-- 6 files changed, 197 insertions(+), 30 deletions(-) create mode 100644 src/main/java/org/springframework/data/release/git/Commit.java diff --git a/infrastructure.properties b/infrastructure.properties index 1f36307..c6ec028 100644 --- a/infrastructure.properties +++ b/infrastructure.properties @@ -1 +1,3 @@ -io.workDir=~/temp/spring-data-shell \ No newline at end of file +io.workDir=~/temp/spring-data-shell + +git.author=Oliver Gierke \ No newline at end of file diff --git a/src/main/java/org/springframework/data/release/cli/ReleaseCommands.java b/src/main/java/org/springframework/data/release/cli/ReleaseCommands.java index 8de7c1e..ac4b94b 100644 --- a/src/main/java/org/springframework/data/release/cli/ReleaseCommands.java +++ b/src/main/java/org/springframework/data/release/cli/ReleaseCommands.java @@ -106,6 +106,8 @@ public class ReleaseCommands implements CommandMarker { maven.updatePom(iteration, Phase.PREPARE); gradle.updateProject(iteration, Phase.PREPARE); + + git.commit(iteration, "Prepare %s.", null); } @CliCommand(value = "release conclude") @@ -113,11 +115,9 @@ public class ReleaseCommands implements CommandMarker { git.tagRelease(iteration); - // - post release pom updates - maven.updatePom(iteration, Phase.CLEANUP); gradle.updateProject(iteration, Phase.CLEANUP); - // - push + git.commit(iteration, "After release cleanups.", null); } } diff --git a/src/main/java/org/springframework/data/release/git/Commit.java b/src/main/java/org/springframework/data/release/git/Commit.java new file mode 100644 index 0000000..d7bfd98 --- /dev/null +++ b/src/main/java/org/springframework/data/release/git/Commit.java @@ -0,0 +1,52 @@ +/* + * Copyright 2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.release.git; + +import lombok.EqualsAndHashCode; +import lombok.RequiredArgsConstructor; + +import org.springframework.data.release.jira.Ticket; + +/** + * @author Oliver Gierke + */ +@EqualsAndHashCode +@RequiredArgsConstructor +public class Commit { + + private final Ticket ticket; + private final String summary; + private final String details; + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + + StringBuilder builder = new StringBuilder(); + + builder.append(ticket.getId()).append(" - ").append(summary).append("\n"); + + if (details != null) { + builder.append("\n"); + builder.append(details).append("\n"); + } + + return builder.toString(); + } +} diff --git a/src/main/java/org/springframework/data/release/git/GitCommands.java b/src/main/java/org/springframework/data/release/git/GitCommands.java index e78077b..db07d85 100644 --- a/src/main/java/org/springframework/data/release/git/GitCommands.java +++ b/src/main/java/org/springframework/data/release/git/GitCommands.java @@ -63,12 +63,35 @@ public class GitCommands implements CommandMarker { * @throws Exception */ @CliCommand("git reset") - public void reset(@CliOption(key = { "", "train" }, mandatory = true) String trainName) throws Exception { - git.reset(ReleaseTrains.getTrainByName(trainName)); + public void reset(@CliOption(key = "", mandatory = true) TrainIteration iteration) throws Exception { + git.reset(iteration); } @CliCommand("git prepare") public void prepare(@CliOption(key = "", mandatory = true) TrainIteration iteration) throws Exception { git.prepare(iteration); } + + /** + * Pushes all changes of all modules of the given {@link TrainIteration} to the remote server. If {@code tags} is + * given, only the tags are pushed. + * + * @param iteration + * @param tags + * @throws Exception + */ + @CliCommand("git push") + public void push(// + @CliOption(key = "", mandatory = true) TrainIteration iteration, // + @CliOption(key = "tags", specifiedDefaultValue = "true", unspecifiedDefaultValue = "false") String tags) + throws Exception { + + boolean pushTags = Boolean.parseBoolean(tags); + + if (pushTags) { + git.pushTags(iteration.getTrain()); + } else { + git.push(iteration); + } + } } diff --git a/src/main/java/org/springframework/data/release/git/GitOperations.java b/src/main/java/org/springframework/data/release/git/GitOperations.java index d067486..55f7fb7 100644 --- a/src/main/java/org/springframework/data/release/git/GitOperations.java +++ b/src/main/java/org/springframework/data/release/git/GitOperations.java @@ -24,6 +24,7 @@ import java.util.concurrent.Future; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.env.Environment; import org.springframework.data.release.io.CommandResult; import org.springframework.data.release.io.OsCommandOperations; import org.springframework.data.release.io.Workspace; @@ -52,10 +53,11 @@ import org.springframework.util.StringUtils; public class GitOperations { private final GitServer server = new GitServer(); - private final OsCommandOperations osCommandOperations; + private final OsCommandOperations os; private final Workspace workspace; private final Logger logger; private final PluginRegistry issueTracker; + private final Environment environment; public GitProject getGitProject(Project project) { return new GitProject(project, server); @@ -67,12 +69,15 @@ public class GitOperations { * @param train must not be {@literal null}. * @throws Exception */ - public void reset(Train train) throws Exception { + public void reset(TrainIteration train) throws Exception { Assert.notNull(train, "Train must not be null!"); - for (Module module : train) { - osCommandOperations.executeCommand("git reset --hard", module.getProject()).get(); + for (ModuleIteration module : train) { + + Branch branch = Branch.from(module); + + os.executeCommand(String.format("git reset --hard origin/%s", branch), module.getProject()).get(); } } @@ -99,7 +104,7 @@ public class GitOperations { artifactVersion, project)); } - osCommandOperations.executeCommand(String.format("git checkout %s", tag), project).get(); + os.executeCommand(String.format("git checkout %s", tag), project).get(); } logger.log(iteration, "Successfully checked out projects."); @@ -114,7 +119,7 @@ public class GitOperations { update(module.getProject()).get(); String checkoutCommand = String.format("git checkout %s && git pull origin %s", branch, branch); - osCommandOperations.executeCommand(checkoutCommand, module.getProject()).get(); + os.executeCommand(checkoutCommand, module.getProject()).get(); } } @@ -131,6 +136,22 @@ public class GitOperations { } } + public void push(TrainIteration iteration) throws Exception { + + for (ModuleIteration module : iteration) { + + Branch branch = Branch.from(module); + os.executeCommand(String.format("git push origin %s", branch), module.getProject()).get(); + } + } + + public void pushTags(Train train) throws Exception { + + for (Module module : train) { + os.executeCommand("git push --tags", module.getProject()).get(); + } + } + public Future update(Project project) throws Exception { GitProject gitProject = new GitProject(project, server); @@ -140,8 +161,8 @@ public class GitOperations { logger.log(project, "Found existing repository %s. Obtaining latest changes…", repositoryName); - return osCommandOperations.executeCommand( - "git checkout master && git reset --hard && git fetch --tags && git pull origin master", project); + return os.executeCommand("git checkout master && git reset --hard && git fetch --tags && git pull origin master", + project); } else { @@ -150,13 +171,13 @@ public class GitOperations { File projectDirectory = workspace.getProjectDirectory(project); String command = String.format("git clone %s %s", gitProject.getProjectUri(), projectDirectory.getName()); - return osCommandOperations.executeCommand(command); + return os.executeCommand(command); } } public Tags getTags(Project project) throws Exception { - String result = osCommandOperations.executeForResult("git tag -l", project); + String result = os.executeForResult("git tag -l", project); List tags = new ArrayList<>(); for (String line : result.split("\n")) { @@ -177,15 +198,74 @@ public class GitOperations { Project project = module.getProject(); String checkoutCommand = String.format("git checkout %s", branch); - osCommandOperations.executeCommand(checkoutCommand, project).get(); + os.executeCommand(checkoutCommand, project).get(); String updateCommand = String.format("git pull origin %s", branch); - osCommandOperations.executeCommand(updateCommand, project).get(); + os.executeCommand(updateCommand, project).get(); String hash = getReleaseHash(module); Tag tag = getTags(project).createTag(module); String tagCommand = String.format("git tag %s %s", tag, hash); - osCommandOperations.executeCommand(tagCommand, project).get(); + os.executeCommand(tagCommand, project).get(); + } + } + + /** + * Commits all changes currently made to all modules of the given {@link TrainIteration}. The summary can contain a + * single {@code %s} placeholder which the version of the current module will get replace into. + * + * @param iteration must not be {@literal null}. + * @param summary must not be {@literal null} or empty. + * @param details can be {@literal null} or empty. + * @throws Exception + */ + public void commit(TrainIteration iteration, String summary, String details) throws Exception { + + Assert.notNull(iteration, "Train iteration must not be null!"); + Assert.hasText(summary, "Summary must not be null or empty!"); + + for (ModuleIteration module : iteration) { + + if (summary.contains("%s")) { + summary = String.format(summary, module.getVersionString()); + } + + commit(module, summary, details); + } + } + + /** + * Commits the given files for the given {@link ModuleIteration} using the given summary and details for the commit + * message. If no files are given, all pending changes are commited. + * + * @param module must not be {@literal null}. + * @param summary must not be {@literal null} or empty. + * @param details can be {@literal null} or empty. + * @param files can be empty. + * @throws Exception + */ + public void commit(ModuleIteration module, String summary, String details, File... files) throws Exception { + + Assert.notNull(module, "Module iteration must not be null!"); + Assert.hasText(summary, "Summary must not be null or empty!"); + + Project project = module.getProject(); + IssueTracker tracker = issueTracker.getPluginFor(project); + Ticket ticket = tracker.getReleaseTicketFor(module); + + Commit commit = new Commit(ticket, summary, details); + String author = environment.getProperty("git.author"); + String commitCommand = String.format("git commit -m \"%s\" --author \"%s\"", commit, author); + + if (files.length != 0) { + + for (File file : files) { + os.executeCommand(String.format("git add %s", file.getAbsolutePath()), project).get(); + } + + os.executeCommand(commitCommand, project).get(); + } else { + os.executeCommand(commitCommand.concat(" -a"), project).get(); } } @@ -193,7 +273,7 @@ public class GitOperations { Project project = module.getProject(); - String result = osCommandOperations.executeForResult("git log --pretty=format:'%h %s'", project); + String result = os.executeForResult("git log --pretty=format:'%h %s'", project); Ticket releaseTicket = issueTracker.getPluginFor(project).getReleaseTicketFor(module); String trigger = String.format("%s - Release", releaseTicket.getId()); diff --git a/src/main/java/org/springframework/data/release/misc/ReleaseOperations.java b/src/main/java/org/springframework/data/release/misc/ReleaseOperations.java index fc78f47..e5c34b1 100644 --- a/src/main/java/org/springframework/data/release/misc/ReleaseOperations.java +++ b/src/main/java/org/springframework/data/release/misc/ReleaseOperations.java @@ -15,6 +15,7 @@ */ package org.springframework.data.release.misc; +import java.io.File; import java.util.Collections; import java.util.HashSet; import java.util.Set; @@ -22,6 +23,7 @@ import java.util.Set; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.release.git.GitOperations; import org.springframework.data.release.io.Workspace; import org.springframework.data.release.io.Workspace.LineCallback; import org.springframework.data.release.jira.Changelog; @@ -56,6 +58,7 @@ public class ReleaseOperations { private final PluginRegistry trackers; private final Workspace workspace; + private final GitOperations git; private final Logger logger; /** @@ -94,6 +97,10 @@ public class ReleaseOperations { }); if (processed) { + + File file = workspace.getFile(location, module.getProject()); + git.commit(module, "Updated changelog.", null, file); + logger.log(module.getProject(), "Updated changelog %s.", location); } } @@ -104,20 +111,23 @@ public class ReleaseOperations { for (final ModuleIteration module : iteration) { - workspace.processFile("src/main/resources/notice.txt", module.getProject(), new LineCallback() { + boolean processed = workspace.processFile("src/main/resources/notice.txt", module.getProject(), + new LineCallback() { - @Override - public String doWith(String line, long number) { + @Override + public String doWith(String line, long number) { - if (number != 0) { - return line; - } + if (number != 0) { + return line; + } - return module.toString(); - } - }); + return module.toString(); + } + }); - logger.log(module, "Updated notice.txt."); + if (processed) { + logger.log(module, "Updated notice.txt."); + } } } }