From bf33e7ef7eff9475c2bced34d84b1764f5ed5c4d Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Tue, 16 Mar 2021 19:01:45 +0000 Subject: [PATCH] Automatically supersede existing upgrade issue when running Bomr Closes gh-25345 --- .../boot/build/bom/bomr/UpgradeBom.java | 30 ++++++++- .../bom/bomr/github/GitHubRepository.java | 14 ++++- .../boot/build/bom/bomr/github/Issue.java | 61 +++++++++++++++++++ .../bomr/github/StandardGitHubRepository.java | 23 ++++--- 4 files changed, 117 insertions(+), 11 deletions(-) create mode 100644 buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/github/Issue.java diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/UpgradeBom.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/UpgradeBom.java index daff5bba05f..8489de88b29 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/UpgradeBom.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/UpgradeBom.java @@ -23,6 +23,7 @@ import java.io.Reader; import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; +import java.util.LinkedHashSet; import java.util.List; import java.util.Optional; import java.util.Properties; @@ -41,6 +42,7 @@ import org.gradle.api.tasks.options.Option; import org.springframework.boot.build.bom.BomExtension; import org.springframework.boot.build.bom.bomr.github.GitHub; import org.springframework.boot.build.bom.bomr.github.GitHubRepository; +import org.springframework.boot.build.bom.bomr.github.Issue; import org.springframework.boot.build.bom.bomr.github.Milestone; import org.springframework.util.StringUtils; @@ -83,6 +85,7 @@ public class UpgradeBom extends DefaultTask { "Unknown label(s): " + StringUtils.collectionToCommaDelimitedString(unknownLabels)); } Milestone milestone = determineMilestone(repository); + List existingUpgradeIssues = repository.findIssues(issueLabels, milestone); List upgrades = new InteractiveUpgradeResolver( new MavenMetadataVersionResolver(Arrays.asList("https://repo1.maven.org/maven2/")), this.bom.getUpgrade().getPolicy(), getServices().get(UserInputHandler.class)) @@ -92,10 +95,22 @@ public class UpgradeBom extends DefaultTask { UpgradeApplicator upgradeApplicator = new UpgradeApplicator(buildFile, gradleProperties); for (Upgrade upgrade : upgrades) { String title = "Upgrade to " + upgrade.getLibrary().getName() + " " + upgrade.getVersion(); - System.out.println(title); + Issue existingUpgradeIssue = findExistingUpgradeIssue(existingUpgradeIssues, upgrade); + if (existingUpgradeIssue != null) { + System.out.println(title + " (supersedes #" + existingUpgradeIssue.getNumber() + " " + + existingUpgradeIssue.getTitle() + ")"); + } + else { + System.out.println(title); + } try { Path modified = upgradeApplicator.apply(upgrade); - int issueNumber = repository.openIssue(title, issueLabels, milestone); + int issueNumber = repository.openIssue(title, + (existingUpgradeIssue != null) ? "Supersedes #" + existingUpgradeIssue.getNumber() : "", + issueLabels, milestone); + if (existingUpgradeIssue != null) { + existingUpgradeIssue.label(Arrays.asList("type: task", "status: superseded")); + } if (new ProcessBuilder().command("git", "add", modified.toFile().getAbsolutePath()).start() .waitFor() != 0) { throw new IllegalStateException("git add failed"); @@ -114,6 +129,17 @@ public class UpgradeBom extends DefaultTask { } } + private Issue findExistingUpgradeIssue(List existingUpgradeIssues, Upgrade upgrade) { + String toMatch = "Upgrade to " + upgrade.getLibrary().getName(); + for (Issue existingUpgradeIssue : existingUpgradeIssues) { + if (existingUpgradeIssue.getTitle().substring(0, existingUpgradeIssue.getTitle().lastIndexOf(' ')) + .equals(toMatch)) { + return existingUpgradeIssue; + } + } + return null; + } + private GitHub createGitHub() { Properties bomrProperties = new Properties(); try (Reader reader = new FileReader(new File(System.getProperty("user.home"), ".bomr.properties"))) { diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/github/GitHubRepository.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/github/GitHubRepository.java index 3c49ba56d55..98b47a414eb 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/github/GitHubRepository.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/github/GitHubRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2021 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. @@ -29,11 +29,12 @@ public interface GitHubRepository { * Opens a new issue with the given title. The given {@code labels} will be applied to * the issue and it will be assigned to the given {@code milestone}. * @param title the title of the issue + * @param body the body of the issue * @param labels the labels to apply to the issue * @param milestone the milestone to assign the issue to * @return the number of the new issue */ - int openIssue(String title, List labels, Milestone milestone); + int openIssue(String title, String body, List labels, Milestone milestone); /** * Returns the labels in the repository. @@ -47,4 +48,13 @@ public interface GitHubRepository { */ List getMilestones(); + /** + * Finds issues that have the given {@code labels} and are assigned to the given + * {@code milestone}. + * @param labels issue labels + * @param milestone assigned milestone + * @return the matching issues + */ + List findIssues(List labels, Milestone milestone); + } diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/github/Issue.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/github/Issue.java new file mode 100644 index 00000000000..c19b223d3d0 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/github/Issue.java @@ -0,0 +1,61 @@ +/* + * Copyright 2012-2021 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.boot.build.bom.bomr.github; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.springframework.web.client.RestTemplate; + +/** + * Minimal representation of a GitHub issue. + * + * @author Andy Wilkinson + */ +public class Issue { + + private final RestTemplate rest; + + private final int number; + + private final String title; + + Issue(RestTemplate rest, int number, String title) { + this.rest = rest; + this.number = number; + this.title = title; + } + + public int getNumber() { + return this.number; + } + + public String getTitle() { + return this.title; + } + + /** + * Labels the issue with the given {@code labels}. Any existing labels are removed. + * @param labels the labels to apply to the issue + */ + public void label(List labels) { + Map> body = Collections.singletonMap("labels", labels); + this.rest.put("issues/" + this.number + "/labels", body); + } + +} diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/github/StandardGitHubRepository.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/github/StandardGitHubRepository.java index 6eced352719..819a79a7bb6 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/github/StandardGitHubRepository.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/github/StandardGitHubRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2021 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. @@ -40,16 +40,17 @@ final class StandardGitHubRepository implements GitHubRepository { @Override @SuppressWarnings("rawtypes") - public int openIssue(String title, List labels, Milestone milestone) { - Map body = new HashMap<>(); - body.put("title", title); + public int openIssue(String title, String body, List labels, Milestone milestone) { + Map requestBody = new HashMap<>(); + requestBody.put("title", title); if (milestone != null) { - body.put("milestone", milestone.getNumber()); + requestBody.put("milestone", milestone.getNumber()); } if (!labels.isEmpty()) { - body.put("labels", labels); + requestBody.put("labels", labels); } - ResponseEntity response = this.rest.postForEntity("issues", body, Map.class); + requestBody.put("body", body); + ResponseEntity response = this.rest.postForEntity("issues", requestBody, Map.class); return (Integer) response.getBody().get("number"); } @@ -64,6 +65,14 @@ final class StandardGitHubRepository implements GitHubRepository { (milestone) -> new Milestone((String) milestone.get("title"), (Integer) milestone.get("number"))); } + @Override + public List findIssues(List labels, Milestone milestone) { + return get( + "issues?per_page=100&state=all&labels=" + String.join(",", labels) + "&milestone=" + + milestone.getNumber(), + (issue) -> new Issue(this.rest, (Integer) issue.get("number"), (String) issue.get("title"))); + } + @SuppressWarnings({ "rawtypes", "unchecked" }) private List get(String name, Function, T> mapper) { ResponseEntity response = this.rest.getForEntity(name, List.class);