|
1 | 1 | /*
|
2 |
| - * Copyright 2019-2020 the original author or authors. |
| 2 | + * Copyright 2019-2022 the original author or authors. |
3 | 3 | *
|
4 | 4 | * Licensed under the Apache License, Version 2.0 (the "License");
|
5 | 5 | * you may not use this file except in compliance with the License.
|
|
17 | 17 | package org.springframework.gradle.github.milestones;
|
18 | 18 |
|
19 | 19 | import java.io.IOException;
|
| 20 | +import java.time.Instant; |
20 | 21 | import java.util.List;
|
| 22 | +import java.util.Optional; |
| 23 | +import java.util.regex.Matcher; |
| 24 | +import java.util.regex.Pattern; |
21 | 25 |
|
22 | 26 | import com.google.common.reflect.TypeToken;
|
23 | 27 | import com.google.gson.Gson;
|
@@ -89,6 +93,128 @@ public boolean isOpenIssuesForMilestoneNumber(RepositoryRef repositoryRef, long
|
89 | 93 | }
|
90 | 94 | }
|
91 | 95 |
|
| 96 | + /** |
| 97 | + * Check if the given milestone is due today or past due. |
| 98 | + * |
| 99 | + * @param repositoryRef The repository owner/name |
| 100 | + * @param milestoneTitle The title of the milestone whose due date should be checked |
| 101 | + * @return true if the given milestone is due today or past due, false otherwise |
| 102 | + */ |
| 103 | + public boolean isMilestoneDueToday(RepositoryRef repositoryRef, String milestoneTitle) { |
| 104 | + String url = this.baseUrl + "/repos/" + repositoryRef.getOwner() + "/" + repositoryRef.getName() |
| 105 | + + "/milestones?per_page=100"; |
| 106 | + Request request = new Request.Builder().get().url(url).build(); |
| 107 | + try { |
| 108 | + Response response = this.client.newCall(request).execute(); |
| 109 | + if (!response.isSuccessful()) { |
| 110 | + throw new RuntimeException("Could not find milestone with title " + milestoneTitle + " for repository " |
| 111 | + + repositoryRef + ". Response " + response); |
| 112 | + } |
| 113 | + List<Milestone> milestones = this.gson.fromJson(response.body().charStream(), |
| 114 | + new TypeToken<List<Milestone>>() { |
| 115 | + }.getType()); |
| 116 | + for (Milestone milestone : milestones) { |
| 117 | + if (milestoneTitle.equals(milestone.getTitle())) { |
| 118 | + Instant now = Instant.now(); |
| 119 | + return milestone.getDueOn() != null && now.isAfter(milestone.getDueOn().toInstant()); |
| 120 | + } |
| 121 | + } |
| 122 | + if (milestones.size() <= 100) { |
| 123 | + throw new RuntimeException("Could not find open milestone with title " + milestoneTitle |
| 124 | + + " for repository " + repositoryRef + " Got " + milestones); |
| 125 | + } |
| 126 | + throw new RuntimeException( |
| 127 | + "It is possible there are too many open milestones open (only 100 are supported). Could not find open milestone with title " |
| 128 | + + milestoneTitle + " for repository " + repositoryRef + " Got " + milestones); |
| 129 | + } |
| 130 | + catch (IOException e) { |
| 131 | + throw new RuntimeException( |
| 132 | + "Could not find open milestone with title " + milestoneTitle + " for repository " + repositoryRef, |
| 133 | + e); |
| 134 | + } |
| 135 | + } |
| 136 | + |
| 137 | + /** |
| 138 | + * Calculate the next release version based on the current version. |
| 139 | + * |
| 140 | + * The current version must conform to the pattern MAJOR.MINOR.PATCH-SNAPSHOT. If the |
| 141 | + * current version is a snapshot of a patch release, then the patch release will be |
| 142 | + * returned. For example, if the current version is 5.6.1-SNAPSHOT, then 5.6.1 will be |
| 143 | + * returned. If the current version is a snapshot of a version that is not GA (i.e the |
| 144 | + * PATCH segment is 0), then GitHub will be queried to find the next milestone or |
| 145 | + * release candidate. If no pre-release versions are found, then the next version will |
| 146 | + * be assumed to be the GA. |
| 147 | + * @param repositoryRef The repository owner/name |
| 148 | + * @param currentVersion The current project version |
| 149 | + * @return the next matching milestone/release candidate or null if none exist |
| 150 | + */ |
| 151 | + public String getNextReleaseMilestone(RepositoryRef repositoryRef, String currentVersion) { |
| 152 | + Pattern snapshotPattern = Pattern.compile("^([0-9]+)\\.([0-9]+)\\.([0-9]+)-SNAPSHOT$"); |
| 153 | + Matcher snapshotVersion = snapshotPattern.matcher(currentVersion); |
| 154 | + |
| 155 | + if (snapshotVersion.find()) { |
| 156 | + String patchSegment = snapshotVersion.group(3); |
| 157 | + String currentVersionNoIdentifier = currentVersion.replace("-SNAPSHOT", ""); |
| 158 | + if (patchSegment.equals("0")) { |
| 159 | + String nextPreRelease = getNextPreRelease(repositoryRef, currentVersionNoIdentifier); |
| 160 | + return nextPreRelease != null ? nextPreRelease : currentVersionNoIdentifier; |
| 161 | + } |
| 162 | + else { |
| 163 | + return currentVersionNoIdentifier; |
| 164 | + } |
| 165 | + } |
| 166 | + else { |
| 167 | + throw new IllegalStateException( |
| 168 | + "Cannot calculate next release version because the current project version does not conform to the expected format"); |
| 169 | + } |
| 170 | + } |
| 171 | + |
| 172 | + /** |
| 173 | + * Calculate the next pre-release version (milestone or release candidate) based on |
| 174 | + * the current version. |
| 175 | + * |
| 176 | + * The current version must conform to the pattern MAJOR.MINOR.PATCH. If no matching |
| 177 | + * milestone or release candidate is found in GitHub then it will return null. |
| 178 | + * @param repositoryRef The repository owner/name |
| 179 | + * @param currentVersionNoIdentifier The current project version without any |
| 180 | + * identifier |
| 181 | + * @return the next matching milestone/release candidate or null if none exist |
| 182 | + */ |
| 183 | + private String getNextPreRelease(RepositoryRef repositoryRef, String currentVersionNoIdentifier) { |
| 184 | + String url = this.baseUrl + "/repos/" + repositoryRef.getOwner() + "/" + repositoryRef.getName() |
| 185 | + + "/milestones?per_page=100"; |
| 186 | + Request request = new Request.Builder().get().url(url).build(); |
| 187 | + try { |
| 188 | + Response response = this.client.newCall(request).execute(); |
| 189 | + if (!response.isSuccessful()) { |
| 190 | + throw new RuntimeException( |
| 191 | + "Could not get milestones for repository " + repositoryRef + ". Response " + response); |
| 192 | + } |
| 193 | + List<Milestone> milestones = this.gson.fromJson(response.body().charStream(), |
| 194 | + new TypeToken<List<Milestone>>() { |
| 195 | + }.getType()); |
| 196 | + Optional<String> nextPreRelease = milestones.stream().map(Milestone::getTitle) |
| 197 | + .filter(m -> m.startsWith(currentVersionNoIdentifier + "-")) |
| 198 | + .min((m1, m2) -> { |
| 199 | + Pattern preReleasePattern = Pattern.compile("^.*-([A-Z]+)([0-9]+)$"); |
| 200 | + Matcher matcher1 = preReleasePattern.matcher(m1); |
| 201 | + Matcher matcher2 = preReleasePattern.matcher(m2); |
| 202 | + matcher1.find(); |
| 203 | + matcher2.find(); |
| 204 | + if (!matcher1.group(1).equals(matcher2.group(1))) { |
| 205 | + return m1.compareTo(m2); |
| 206 | + } |
| 207 | + else { |
| 208 | + return Integer.valueOf(matcher1.group(2)).compareTo(Integer.valueOf(matcher2.group(2))); |
| 209 | + } |
| 210 | + }); |
| 211 | + return nextPreRelease.orElse(null); |
| 212 | + } |
| 213 | + catch (IOException e) { |
| 214 | + throw new RuntimeException("Could not find open milestones with for repository " + repositoryRef, e); |
| 215 | + } |
| 216 | + } |
| 217 | + |
92 | 218 | // public boolean isOpenIssuesForMilestoneName(String owner, String repository, String milestoneName) {
|
93 | 219 | //
|
94 | 220 | // }
|
|
0 commit comments