diff --git a/release-tools/readme.md b/release-tools/readme.md index bf0a83a..1696e55 100644 --- a/release-tools/readme.md +++ b/release-tools/readme.md @@ -82,8 +82,18 @@ Workflow: * Check for dependency upgrades `$ dependency check $trainIteration` -Reports upgradable dependencies for Build and Modules and creates `dependency-upgrade-build.properties` file. -Edit `dependency-upgrade-build.properties` to specify the dependency version to upgrade. Removing a line will omit that dependency upgrade. +Reports upgradable dependencies for Build and Modules and +creates `dependency-upgrade-build.properties` file. +Edit `dependency-upgrade-build.properties` to specify the dependency version to upgrade. +Removing a line will omit that dependency upgrade. -* Apply dependency upgrade with `$ dependency upgrade $trainIteration`. Applies dependency upgrades currently only to Spring Data Build. -* Report store-specific dependencies to Spring Boot's current upgrade ticket ([sample](https://github.com/spring-projects/spring-boot/issues/24036)) `$ dependency report $trainIteration` +* Apply dependency upgrade with `$ dependency upgrade $trainIteration`. Applies dependency + upgrades currently only to Spring Data Build. +* Report store-specific dependencies to Spring Boot's current upgrade + ticket ([sample](https://github.com/spring-projects/spring-boot/issues/24036)) `$ dependency report $trainIteration` + +#### CI Properties Distribution + +To distribute `ci/pipeline.properties` across all modules use: + +`$ infra distribute ci-properties $trainIteration` diff --git a/release-tools/src/main/java/org/springframework/data/release/dependency/DependencyOperations.java b/release-tools/src/main/java/org/springframework/data/release/dependency/DependencyOperations.java index 981544f..b6776c9 100644 --- a/release-tools/src/main/java/org/springframework/data/release/dependency/DependencyOperations.java +++ b/release-tools/src/main/java/org/springframework/data/release/dependency/DependencyOperations.java @@ -15,8 +15,10 @@ */ package org.springframework.data.release.dependency; +import lombok.AccessLevel; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; +import lombok.experimental.FieldDefaults; import java.io.ByteArrayInputStream; import java.io.File; @@ -42,7 +44,6 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; -import org.springframework.data.release.CliComponent; import org.springframework.data.release.build.Pom; import org.springframework.data.release.git.GitOperations; import org.springframework.data.release.io.Workspace; @@ -59,6 +60,7 @@ import org.springframework.data.release.utils.Logger; import org.springframework.data.util.Streamable; import org.springframework.http.ResponseEntity; import org.springframework.plugin.core.PluginRegistry; +import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.springframework.web.client.RestOperations; @@ -72,8 +74,9 @@ import org.xmlbeam.io.XBStreamInput; * * @author Mark Paluch */ -@CliComponent +@Component @RequiredArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) public class DependencyOperations { public static final Pattern REPO_MAVEN_ORG_DIR_LISTING = Pattern @@ -341,7 +344,7 @@ public class DependencyOperations { action.accept(dependency, dependencyVersion); - gitOperations.commit(module, upgradeTicket, upgradeTicketSummary, Optional.empty()); + gitOperations.commit(module, upgradeTicket, upgradeTicketSummary, Optional.empty(), true); ticketsToClose.add(upgradeTicket); }); diff --git a/release-tools/src/main/java/org/springframework/data/release/dependency/InfrastructureCommands.java b/release-tools/src/main/java/org/springframework/data/release/dependency/InfrastructureCommands.java index 230422a..ac9e371 100644 --- a/release-tools/src/main/java/org/springframework/data/release/dependency/InfrastructureCommands.java +++ b/release-tools/src/main/java/org/springframework/data/release/dependency/InfrastructureCommands.java @@ -57,6 +57,7 @@ public class InfrastructureCommands extends TimedCommand { DependencyOperations operations; ExecutorService executor; GitOperations git; + InfrastructureOperations infra; Logger logger; @CliCommand(value = "infra maven check") @@ -120,4 +121,15 @@ public class InfrastructureCommands extends TimedCommand { return DependencyUpgradeProposals.fromProperties(iteration, properties); } + @CliCommand(value = "infra distribute ci-properties") + public void distributeCiProperties(@CliOption(key = "", mandatory = true) TrainIteration iteration) + throws IOException, InterruptedException { + + logger.log(iteration, "Distributing CI properties for Spring Data…"); + + git.checkout(iteration.getTrain(), true); + + infra.distributeCiProperties(iteration); + } + } diff --git a/release-tools/src/main/java/org/springframework/data/release/dependency/InfrastructureOperations.java b/release-tools/src/main/java/org/springframework/data/release/dependency/InfrastructureOperations.java new file mode 100644 index 0000000..efdaad1 --- /dev/null +++ b/release-tools/src/main/java/org/springframework/data/release/dependency/InfrastructureOperations.java @@ -0,0 +1,102 @@ +/* + * Copyright 2022 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 + * + * https://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.dependency; + +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; + +import java.io.File; +import java.util.Optional; +import java.util.concurrent.ExecutorService; + +import org.apache.commons.io.FileUtils; + +import org.springframework.data.release.git.Branch; +import org.springframework.data.release.git.GitOperations; +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.Projects; +import org.springframework.data.release.model.Train; +import org.springframework.data.release.model.TrainIteration; +import org.springframework.data.release.utils.ExecutionUtils; +import org.springframework.data.util.Streamable; +import org.springframework.stereotype.Component; + +/** + * @author Mark Paluch + */ +@Component +@RequiredArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +public class InfrastructureOperations { + + public static final String CI_PROPERTIES = "ci/pipeline.properties"; + + Workspace workspace; + GitOperations git; + ExecutorService executor; + + /** + * Distribute {@link #CI_PROPERTIES} from {@link Projects#BUILD} to all modules within {@link TrainIteration}. + * + * @param iteration + */ + void distributeCiProperties(TrainIteration iteration) { + + File master = workspace.getFile(CI_PROPERTIES, Projects.BUILD); + + if (!master.exists()) { + throw new IllegalStateException(String.format("CI Properties file %s does not exist", master)); + } + + ExecutionUtils.run(executor, iteration, module -> { + + Project project = module.getProject(); + Branch branch = Branch.from(module); + + git.update(project); + git.checkout(project, branch); + }); + + verifyExistingPropertyFiles(iteration.getTrain(), master); + + ExecutionUtils.run(executor, Streamable.of(iteration.getModulesExcept(Projects.BUILD)), module -> { + + File target = workspace.getFile(CI_PROPERTIES, module.getProject()); + target.delete(); + + FileUtils.copyFile(master, target); + + git.add(module.getProject(), CI_PROPERTIES); + git.commit(module, "Update CI properties.", Optional.empty(), false); + // git.push(iteration); + }); + } + + private void verifyExistingPropertyFiles(Train train, File master) { + + for (Module module : train) { + File target = workspace.getFile(CI_PROPERTIES, module.getProject()); + + if (!target.exists()) { + throw new IllegalStateException(String.format("CI Properties file %s does not exist", master)); + } + } + } + +} diff --git a/release-tools/src/main/java/org/springframework/data/release/git/GitOperations.java b/release-tools/src/main/java/org/springframework/data/release/git/GitOperations.java index f51bfd4..1111b2b 100644 --- a/release-tools/src/main/java/org/springframework/data/release/git/GitOperations.java +++ b/release-tools/src/main/java/org/springframework/data/release/git/GitOperations.java @@ -29,6 +29,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import java.util.stream.StreamSupport; +import org.eclipse.jgit.api.AddCommand; import org.eclipse.jgit.api.CheckoutCommand; import org.eclipse.jgit.api.CherryPickResult; import org.eclipse.jgit.api.CherryPickResult.CherryPickStatus; @@ -37,6 +38,7 @@ import org.eclipse.jgit.api.CreateBranchCommand.SetupUpstreamMode; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.LogCommand; import org.eclipse.jgit.api.ResetCommand.ResetType; +import org.eclipse.jgit.api.errors.EmptyCommitException; import org.eclipse.jgit.api.errors.RefNotFoundException; import org.eclipse.jgit.errors.UnsupportedCredentialItem; import org.eclipse.jgit.lib.ObjectId; @@ -633,7 +635,7 @@ public class GitOperations { () -> String.format("No issue tracker found for project %s!", project)); Ticket ticket = tracker.getReleaseTicketFor(module); - commit(module, ticket, summary, details); + commit(module, ticket, summary, details, true); } /** @@ -644,7 +646,28 @@ public class GitOperations { * @param summary must not be {@literal null} or empty. * @param details can be {@literal null} or empty. */ - public void commit(ModuleIteration module, Ticket ticket, String summary, Optional details) { + public void commit(ModuleIteration module, String summary, Optional details, boolean all) { + + 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.getRequiredPluginFor(project, + () -> String.format("No issue tracker found for project %s!", project)); + Ticket ticket = tracker.getReleaseTicketFor(module); + + commit(module, ticket, summary, details, all); + } + + /** + * 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 committed. + * + * @param module must not be {@literal null}. + * @param summary must not be {@literal null} or empty. + * @param details can be {@literal null} or empty. + */ + public void commit(ModuleIteration module, Ticket ticket, String summary, Optional details, boolean all) { Assert.notNull(module, "Module iteration must not be null!"); Assert.hasText(summary, "Summary must not be null or empty!"); @@ -654,6 +677,7 @@ public class GitOperations { Commit commit = new Commit(ticket, summary, details); String author = gitProperties.getAuthor(); String email = gitProperties.getEmail(); + boolean allowEmpty = all; logger.log(module, "git commit -m \"%s\" %s --author=\"%s <%s>\"", commit.getSummary(), gpg.isGpgAvailable() ? "-S" + gpg.getKeyname() : "", author, email); @@ -664,7 +688,8 @@ public class GitOperations { .setMessage(commit.toString())// .setAuthor(author, email)// .setCommitter(author, email)// - .setAll(true); + .setAllowEmpty(allowEmpty) // + .setAll(all); if (gpg.isGpgAvailable()) { commitCommand.setSign(true).setSigningKey(gpg.getKeyname()) @@ -673,6 +698,31 @@ public class GitOperations { commitCommand.setSign(false); } + try { + commitCommand.call(); + } catch (EmptyCommitException e) { + // allowed if not all + } + }); + } + + /** + * Adds the {@code filepattern} to the staging area. + * + * @param project must not be {@literal null}. + * @param filepattern must not be {@literal null} or empty. + */ + public void add(Project project, String filepattern) { + + Assert.notNull(project, "Project must not be null!"); + + logger.log(project, "git add \"filepattern\""); + + doWithGit(project, git -> { + + AddCommand commitCommand = git.add()// + .addFilepattern(filepattern); + commitCommand.call(); }); }